Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/aws-serverless/src/integration/aws/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { defineIntegration } from '@sentry/core';
*/
export const awsIntegration = defineIntegration(() => {
return {
name: 'Aws',
name: 'Aws' as const,
setupOnce() {
registerInstrumentations({
instrumentations: [new AwsInstrumentation()],
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-serverless/src/integration/awslambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const AWS_CLOUDWATCH_CONTEXT_FIELDS = ['log_group', 'log_stream', 'url'] as cons

const _awsLambdaIntegration = ((options: AwsLambdaOptions = {}) => {
return {
name: 'AwsLambda',
name: 'AwsLambda' as const,
setupOnce() {
instrumentAwsLambda(options);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ interface BreadcrumbsOptions {
/** maxStringLength gets capped to prevent 100 breadcrumbs exceeding 1MB event payload size */
const MAX_ALLOWED_STRING_LENGTH = 1024;

const INTEGRATION_NAME = 'Breadcrumbs';
const INTEGRATION_NAME = 'Breadcrumbs' as const;

const _breadcrumbsIntegration = ((options: Partial<BreadcrumbsOptions> = {}) => {
const _options = {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/browserapierrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const DEFAULT_EVENT_TARGET =
',',
);

const INTEGRATION_NAME = 'BrowserApiErrors';
const INTEGRATION_NAME = 'BrowserApiErrors' as const;

type XMLHttpRequestProp = 'onload' | 'onerror' | 'onprogress' | 'onreadystatechange';

Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/browsersession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const browserSessionIntegration = defineIntegration((options: BrowserSess
const lifecycle = options.lifecycle ?? 'route';

return {
name: 'BrowserSession',
name: 'BrowserSession' as const,
setupOnce() {
if (typeof WINDOW.document === 'undefined') {
DEBUG_BUILD &&
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/contextlines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;

const DEFAULT_LINES_OF_CONTEXT = 7;

const INTEGRATION_NAME = 'ContextLines';
const INTEGRATION_NAME = 'ContextLines' as const;

// TODO(v11): Use `dataCollection.frameContextLines` default (5)
interface ContextLinesOptions {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/culturecontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CultureContext, IntegrationFn } from '@sentry/core/browser';
import { defineIntegration, safeSetSpanJSONAttributes } from '@sentry/core/browser';
import { WINDOW } from '../helpers';

const INTEGRATION_NAME = 'CultureContext';
const INTEGRATION_NAME = 'CultureContext' as const;

const _cultureContextIntegration = (() => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from
*/
export const launchDarklyIntegration = defineIntegration(() => {
return {
name: 'LaunchDarkly',
name: 'LaunchDarkly' as const,

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
return _INTERNAL_copyFlagsFromScopeToEvent(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { EvaluationDetails, HookContext, HookHints, JsonValue, OpenFeatureH

export const openFeatureIntegration = defineIntegration(() => {
return {
name: 'OpenFeature',
name: 'OpenFeature' as const,

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
return _INTERNAL_copyFlagsFromScopeToEvent(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import type { FeatureGate, StatsigClient } from './types';
export const statsigIntegration = defineIntegration(
({ featureFlagClient: statsigClient }: { featureFlagClient: StatsigClient }) => {
return {
name: 'Statsig',
name: 'Statsig' as const,

setup(_client: Client) {
statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type UnleashIntegrationOptions = {
export const unleashIntegration = defineIntegration(
({ featureFlagClientClass: unleashClientClass }: UnleashIntegrationOptions) => {
return {
name: 'Unleash',
name: 'Unleash' as const,

setupOnce() {
const unleashClientPrototype = unleashClientClass.prototype as UnleashClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const STREAMING_CONTENT_TYPES = ['text/event-stream', 'application/x-ndjson', 'a
*/
export const fetchStreamPerformanceIntegration = defineIntegration(() => {
return {
name: 'FetchStreamPerformance',
name: 'FetchStreamPerformance' as const,

setup() {
// End the stream span when the response body finishes resolving
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/globalhandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type GlobalHandlersIntegrationsOptionKeys = 'onerror' | 'onunhandledrejection';

type GlobalHandlersIntegrations = Record<GlobalHandlersIntegrationsOptionKeys, boolean>;

const INTEGRATION_NAME = 'GlobalHandlers';
const INTEGRATION_NAME = 'GlobalHandlers' as const;

const _globalHandlersIntegration = ((options: Partial<GlobalHandlersIntegrations> = {}) => {
const _options = {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/graphqlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ interface GraphQLOperation {
operationName?: string;
}

const INTEGRATION_NAME = 'GraphQLClient';
const INTEGRATION_NAME = 'GraphQLClient' as const;

const _graphqlClientIntegration = ((options: GraphQLClientOptions) => {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/httpclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { DEBUG_BUILD } from '../debug-build';
export type HttpStatusCodeRange = [number, number] | number;
export type HttpRequestTarget = string | RegExp;

const INTEGRATION_NAME = 'HttpClient';
const INTEGRATION_NAME = 'HttpClient' as const;

interface HttpClientOptions {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/httpcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getHttpRequestData, WINDOW } from '../helpers';
*/
export const httpContextIntegration = defineIntegration(() => {
return {
name: 'HttpContext',
name: 'HttpContext' as const,
preprocessEvent(event) {
// if none of the information we want exists, don't bother
if (!WINDOW.navigator && !WINDOW.location && !WINDOW.document) {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/linkederrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface LinkedErrorsOptions {
const DEFAULT_KEY = 'cause';
const DEFAULT_LIMIT = 5;

const INTEGRATION_NAME = 'LinkedErrors';
const INTEGRATION_NAME = 'LinkedErrors' as const;

const _linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => {
const limit = options.limit || DEFAULT_LIMIT;
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/reportingobserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;

const INTEGRATION_NAME = 'ReportingObserver';
const INTEGRATION_NAME = 'ReportingObserver' as const;

interface Report {
[key: string]: unknown;
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/spanstreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { DEBUG_BUILD } from '../debug-build';

export const spanStreamingIntegration = defineIntegration(() => {
return {
name: 'SpanStreaming',
name: 'SpanStreaming' as const,

beforeSetup(client) {
// If users only set spanStreamingIntegration, without traceLifecycle, we set it to "stream" for them.
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/spotlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type SpotlightConnectionOptions = {
sidecarUrl?: string;
};

export const INTEGRATION_NAME = 'SpotlightBrowser';
export const INTEGRATION_NAME = 'SpotlightBrowser' as const;

export const SPOTLIGHT_IGNORE_SPANS = [{ op: 'ui.interaction.click', name: '#sentry-spotlight' }];

Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/view-hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const viewHierarchyIntegration = defineIntegration((options: Options = {}
}

return {
name: 'ViewHierarchy',
name: 'ViewHierarchy' as const,
processEvent: (event, hint) => {
// only capture for error events
if (event.type !== undefined || options.shouldAttach?.(event, hint) === false) {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/webVitals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
trackLcpAsSpan,
} from '@sentry/browser-utils';

export const WEB_VITALS_INTEGRATION_NAME = 'WebVitals';
export const WEB_VITALS_INTEGRATION_NAME = 'WebVitals' as const;

export type WebVitalName = 'cls' | 'inp' | 'lcp';

Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/integrations/webWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { eventFromUnknownInput } from '../eventbuilder';
import { WINDOW } from '../helpers';
import { _eventFromRejectionWithPrimitive, _getUnhandledRejectionError } from './globalhandlers';

export const INTEGRATION_NAME = 'WebWorker';
export const INTEGRATION_NAME = 'WebWorker' as const;

interface WebWorkerMessage {
_sentryMessage: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/profiling/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
takeProfileFromGlobalCache,
} from './utils';

const INTEGRATION_NAME = 'BrowserProfiling';
const INTEGRATION_NAME = 'BrowserProfiling' as const;

const _browserProfilingIntegration = (() => {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/bun/src/integrations/bunRuntimeMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { performance } from 'perf_hooks';
import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics } from '@sentry/core';
import { _INTERNAL_normalizeCollectionInterval, type NodeRuntimeMetricsOptions } from '@sentry/node';

const INTEGRATION_NAME = 'BunRuntimeMetrics';
const INTEGRATION_NAME = 'BunRuntimeMetrics' as const;
const DEFAULT_INTERVAL_MS = 30_000;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/bun/src/integrations/bunserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@sentry/core';
import type { ServeOptions } from 'bun';

const INTEGRATION_NAME = 'BunServer';
const INTEGRATION_NAME = 'BunServer' as const;

const _bunServerIntegration = (() => {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/src/integrations/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
stringMatchesSomePattern,
} from '@sentry/core';

const INTEGRATION_NAME = 'Fetch';
const INTEGRATION_NAME = 'Fetch' as const;

const HAS_CLIENT_MAP = new WeakMap<Client, boolean>();

Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/src/integrations/hono.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@sentry/core';
import { DEBUG_BUILD } from '../debug-build';

const INTEGRATION_NAME = 'Hono';
const INTEGRATION_NAME = 'Hono' as const;

interface HonoError extends Error {
status?: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/src/integrations/httpServer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Client, IntegrationFn, MaxRequestBodySize } from '@sentry/core';
import { captureBodyFromWinterCGRequest, defineIntegration, getIsolationScope } from '@sentry/core';

const INTEGRATION_NAME = 'HttpServer';
const INTEGRATION_NAME = 'HttpServer' as const;

export interface HttpServerIntegrationOptions {
/**
Expand Down
60 changes: 59 additions & 1 deletion packages/core/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,64 @@ export function addIntegration(integration: Integration): void {
* Define an integration function that can be used to create an integration instance.
* Note that this by design hides the implementation details of the integration, as they are considered internal.
*/
export function defineIntegration<Fn extends IntegrationFn>(fn: Fn): (...args: Parameters<Fn>) => Integration {
export function defineIntegration<Fn extends IntegrationFn>(
fn: Fn,
): (...args: Parameters<Fn>) => Integration & { name: ReturnType<Fn>['name'] } {
return fn;
}

/**
* Wrap a parent integration with an extended integration.
* Any passed integration function will call the parent integration function first, if it exists.
*
* Example usage:
*
* @example
* ```typescript
* const parentIntegration = defineIntegration(() => ({
* name: 'ParentIntegration',
* setupOnce: () => {
* console.log('ParentIntegration setupOnce');
* },
* }));
*
* const extendedIntegration = extendIntegration(parentIntegration, {
* setupOnce: () => {
* console.log('ExtendedIntegration setupOnce');
* },
* });
* ```
*/
export function extendIntegration<
Base extends Integration,
Extended extends Record<string, unknown> & Partial<Integration>,
>(integration: Base, extendedIntegration: Extended): Omit<Base, keyof Extended> & Extended {
// The extension overrides the base for any shared key (object spread + the wrapping below), so the
// result type drops the overridden base keys rather than intersecting them — `Base & Extended` would
// wrongly intersect shared keys (e.g. a re-typed property collapses to `never`).
const wrappedIntegration = {
...integration,
...extendedIntegration,
} as Omit<Base, keyof Extended> & Extended;

// Make sure that functions that are extended also call the base functions, if defined
// oxlint-disable-next-line guard-for-in
for (const key in extendedIntegration) {
const baseValue = integration[key as keyof Base];
const extendedValue = extendedIntegration[key];

if (typeof baseValue === 'function' && typeof extendedValue === 'function') {
const baseBound = baseValue.bind(wrappedIntegration) as typeof baseValue;
const extendedBound = extendedValue.bind(wrappedIntegration) as typeof extendedValue;

const wrappedFunction = function (this: unknown, ...args: unknown[]): unknown {
baseBound(...args);

@Lms24 Lms24 Jun 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: we could warn if we see that the return type of baseBound is not undefined

but yeah, as we discussed offline, it's not required to use this helper should we ever extend an integration with process* hooks, so not a blocker for me

return extendedBound(...args);
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
sentry[bot] marked this conversation as resolved.
} as (Omit<Base, keyof Extended> & Extended)[Extract<keyof Extended, string>];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe this can be extracted as a type to make it more readable


wrappedIntegration[key] = wrappedFunction;
}
}

return wrappedIntegration;
}
2 changes: 1 addition & 1 deletion packages/core/src/integrations/captureconsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface CaptureConsoleOptions {
handled?: boolean;
}

const INTEGRATION_NAME = 'CaptureConsole';
const INTEGRATION_NAME = 'CaptureConsole' as const;

const _captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => {
const levels = options.levels || CONSOLE_LEVELS;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integrations/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type GlobalObjectWithUtil = typeof GLOBAL_OBJ & {
};
};

const INTEGRATION_NAME = 'Console';
const INTEGRATION_NAME = 'Console' as const;

/**
* Captures calls to the `console` API as breadcrumbs in Sentry.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integrations/conversationId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { IntegrationFn } from '../types/integration';
import type { Span } from '../types/span';
import { spanToJSON } from '../utils/spanUtils';

const INTEGRATION_NAME = 'ConversationId';
const INTEGRATION_NAME = 'ConversationId' as const;

const _conversationIdIntegration = (() => {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integrations/dedupe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { StackFrame } from '../types/stackframe';
import { debug } from '../utils/debug-logger';
import { getFramesFromEvent } from '../utils/stacktrace';

const INTEGRATION_NAME = 'Dedupe';
const INTEGRATION_NAME = 'Dedupe' as const;

const _dedupeIntegration = (() => {
let previousEvent: Event | undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/integrations/eventFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface EventFiltersOptions {
disableErrorDefaults: boolean;
}

const INTEGRATION_NAME = 'EventFilters';
const INTEGRATION_NAME = 'EventFilters' as const;

/**
* An integration that filters out events (errors and transactions) based on:
Expand Down Expand Up @@ -86,7 +86,7 @@ export const eventFiltersIntegration = defineIntegration((options: Partial<Event
export const inboundFiltersIntegration = defineIntegration(((options: Partial<EventFiltersOptions> = {}) => {
return {
...eventFiltersIntegration(options),
name: 'InboundFilters',
name: 'InboundFilters' as const,
};
}) satisfies IntegrationFn);

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integrations/extraerrordata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { normalize } from '../utils/normalize';
import { setSkipNormalizationHint } from '../utils/normalizationHints';
import { truncate } from '../utils/string';

const INTEGRATION_NAME = 'ExtraErrorData';
const INTEGRATION_NAME = 'ExtraErrorData' as const;

interface ExtraErrorDataOptions {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integrations/featureFlags/growthbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type GrowthBookClassLike = new (...args: unknown[]) => GrowthBookLike;
export const growthbookIntegration: IntegrationFn = defineIntegration(
({ growthbookClass }: { growthbookClass: GrowthBookClassLike }) => {
return {
name: 'GrowthBook',
name: 'GrowthBook' as const,

setupOnce() {
const proto = growthbookClass.prototype as GrowthBookLike;
Expand Down
Loading
Loading