From be53ef7395fc16716e19fba04574284f5c06e635 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 12 Aug 2026 15:07:04 +0200 Subject: [PATCH 1/2] ref(core): Serve span-start APIs per entry point `@sentry/core/browser` now exports `startSpan`, `startInactiveSpan`, `startSpanManual` and `startIdleSpan` variants that install `spanStreamingIntegration` on the client before starting the span. `@sentry/core` and `@sentry/core/server` keep serving the plain ones. This is what lets `@sentry/browser`'s `init()` stop referencing the integration in a follow-up, so error-only bundles can tree-shake the whole span streaming graph. `init()` still pushes it here, so nothing changes behaviourally or size-wise yet. Because both variants share their names, the root entry's star exports of `server-exports` and `browser-exports` become ambiguous; `index.ts` pins them to the plain variants with explicit re-exports. The same applies to every meta-framework `index.types.ts` that star-exports both its client and server entry. Co-Authored-By: Claude Opus 5 (1M context) --- packages/astro/src/index.types.ts | 6 + .../src/index.bundle.tracing.logs.metrics.ts | 2 +- ...le.tracing.replay.feedback.logs.metrics.ts | 2 +- .../index.bundle.tracing.replay.feedback.ts | 2 +- ...ndex.bundle.tracing.replay.logs.metrics.ts | 2 +- .../src/index.bundle.tracing.replay.ts | 2 +- packages/browser/src/index.bundle.tracing.ts | 2 +- packages/browser/src/index.ts | 2 +- packages/browser/src/sdk.ts | 7 +- packages/core/src/browser-exports.ts | 17 +++ packages/core/src/fetch.ts | 3 +- packages/core/src/index.ts | 8 ++ .../src/integrations/browserSpanStreaming.ts} | 16 ++- .../src/integrations/express/patch-layer.ts | 3 +- .../integrations/http/client-subscriptions.ts | 2 +- .../integrations/http/server-subscription.ts | 3 +- .../core/src/integrations/mcp-server/spans.ts | 2 +- .../src/integrations/mcp-server/transport.ts | 3 +- packages/core/src/integrations/postgresjs.ts | 3 +- packages/core/src/integrations/supabase.ts | 3 +- packages/core/src/server-exports.ts | 8 ++ packages/core/src/shared-exports.ts | 1 - packages/core/src/tracing/browserSpanApi.ts | 89 +++++++++++++++ packages/core/src/tracing/index.ts | 5 +- packages/core/src/trpc.ts | 2 +- packages/core/test/exports.test.ts | 40 +++++++ .../browserSpanStreaming.test.ts} | 106 ++++++------------ packages/core/test/lib/fetch.test.ts | 2 +- .../integrations/express/patch-layer.test.ts | 3 + .../mcp-server/mcpServerWrapper.test.ts | 2 +- .../mcp-server/piiFiltering.test.ts | 2 +- .../mcp-server/semanticConventions.test.ts | 2 +- .../transportInstrumentation.test.ts | 2 +- .../test/lib/integrations/postgresjs.test.ts | 2 +- .../test/lib/integrations/supabase.test.ts | 5 +- .../tracing/dynamicSamplingContext.test.ts | 2 +- packages/core/test/lib/tracing/trace.test.ts | 4 +- packages/core/test/lib/trpc.test.ts | 2 +- packages/effect/src/index.types.ts | 6 + packages/nuxt/src/index.types.ts | 6 + packages/react-router/src/index.types.ts | 6 + packages/remix/src/index.types.ts | 6 + packages/solidstart/src/index.types.ts | 6 + packages/sveltekit/src/index.types.ts | 6 + .../tanstackstart-react/src/index.types.ts | 6 + 45 files changed, 294 insertions(+), 117 deletions(-) rename packages/{browser/src/integrations/spanstreaming.ts => core/src/integrations/browserSpanStreaming.ts} (75%) create mode 100644 packages/core/src/tracing/browserSpanApi.ts create mode 100644 packages/core/test/exports.test.ts rename packages/{browser/test/integrations/spanstreaming.test.ts => core/test/integrations/browserSpanStreaming.test.ts} (59%) diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index 3472adfad925..0a387e30a89c 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -21,6 +21,12 @@ export declare function init(options: Options | clientSdk.BrowserOptions | NodeO export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/browser/src/index.bundle.tracing.logs.metrics.ts b/packages/browser/src/index.bundle.tracing.logs.metrics.ts index aaf81b4134cd..a3fc7e93dbc7 100644 --- a/packages/browser/src/index.bundle.tracing.logs.metrics.ts +++ b/packages/browser/src/index.bundle.tracing.logs.metrics.ts @@ -29,7 +29,7 @@ export { elementTimingIntegration } from '@sentry/browser-utils'; export { reportPageLoaded } from './tracing/reportPageLoaded'; export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.logs.metrics.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.logs.metrics.ts index 09df291955aa..5a9dbec2c268 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.logs.metrics.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.logs.metrics.ts @@ -30,7 +30,7 @@ export { elementTimingIntegration } from '@sentry/browser-utils'; export { reportPageLoaded } from './tracing/reportPageLoaded'; export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 14e4526e3fe2..5cb44950c491 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -36,7 +36,7 @@ export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { reportPageLoaded } from './tracing/reportPageLoaded'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.bundle.tracing.replay.logs.metrics.ts b/packages/browser/src/index.bundle.tracing.replay.logs.metrics.ts index 6b8275b4de08..105ca0258045 100644 --- a/packages/browser/src/index.bundle.tracing.replay.logs.metrics.ts +++ b/packages/browser/src/index.bundle.tracing.replay.logs.metrics.ts @@ -29,7 +29,7 @@ export { elementTimingIntegration } from '@sentry/browser-utils'; export { reportPageLoaded } from './tracing/reportPageLoaded'; export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index ac533aea3862..71c1fe6d0e7f 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -35,7 +35,7 @@ export { elementTimingIntegrationShim as elementTimingIntegration }; export { reportPageLoaded } from './tracing/reportPageLoaded'; export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 076ebcf4e428..a6b6dc85b7b2 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -37,7 +37,7 @@ export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { reportPageLoaded } from './tracing/reportPageLoaded'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index b8627c86741c..5ebdc2db5b68 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -37,7 +37,7 @@ export { elementTimingIntegration, getAbsoluteUrl } from '@sentry/browser-utils' export { normalizeStringifyValue } from './normalizeStringifyValue'; export { reportPageLoaded } from './tracing/reportPageLoaded'; export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; -export { spanStreamingIntegration } from './integrations/spanstreaming'; +export { spanStreamingIntegration } from '@sentry/core/browser'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; export { userTimingIntegration } from './integrations/usertiming'; diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 08a540fa34d5..61584ef961b0 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -7,6 +7,7 @@ import { getIntegrationsToSetup, initAndBind, setNormalizeStringifier, + spanStreamingIntegration, stackParserFromStackParserOptions, } from '@sentry/core/browser'; import type { BrowserClientOptions, BrowserOptions } from './client'; @@ -19,10 +20,6 @@ import { globalHandlersIntegration } from './integrations/globalhandlers'; import { httpContextIntegration } from './integrations/httpcontext'; import { linkedErrorsIntegration } from './integrations/linkederrors'; import { spotlightBrowserIntegration } from './integrations/spotlight'; -import { - spanStreamingIntegration, - INTEGRATION_NAME as SPAN_STREAMING_INTEGRATION_NAME, -} from './integrations/spanstreaming'; import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport } from './transports/fetch'; import { normalizeStringifyValue } from './normalizeStringifyValue'; @@ -122,7 +119,7 @@ export function init(options: BrowserOptions = {}): Client | undefined { if ( (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) && options.traceLifecycle !== 'static' && - !integrations.some(integration => integration.name === SPAN_STREAMING_INTEGRATION_NAME) + !integrations.some(integration => integration.name === 'SpanStreaming') ) { integrations.push(spanStreamingIntegration()); } diff --git a/packages/core/src/browser-exports.ts b/packages/core/src/browser-exports.ts index 97f33eaa9433..1df55682ac77 100644 --- a/packages/core/src/browser-exports.ts +++ b/packages/core/src/browser-exports.ts @@ -3,6 +3,23 @@ * * @module */ +// These share their names with the plain variants in `server-exports`, but additionally make sure +// the browser span streaming integration is installed on the client before the span starts. That +// indirection is what lets error-only browser bundles tree-shake span streaming away entirely: +// `init()` never references the integration, so it's only retained when span-creating code is. +// +// `@sentry/core` pins these names to the plain variants (see `index.ts`); `@sentry/core/browser` +// serves the guarded ones. Browser-facing packages therefore upgrade by importing from +// `@sentry/core/browser` — no call site changes. +export { + startSpan, + startInactiveSpan, + startSpanManual, + startIdleSpan, + _INTERNAL_ensureBrowserSpanStreaming, +} from './tracing/browserSpanApi'; +export { spanStreamingIntegration } from './integrations/browserSpanStreaming'; + export { getComponentName, getLocationHref, diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index cfcba668dac5..36d8b493c25f 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -3,7 +3,8 @@ import { HTTP_METHOD, SERVER_ADDRESS, URL_FRAGMENT, URL_FULL, URL_QUERY } from ' import type { Client } from './client'; import { getClient } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; -import { setHttpStatus, SPAN_STATUS_ERROR, spanIsIgnored, startInactiveSpan } from './tracing'; +import { setHttpStatus, SPAN_STATUS_ERROR, spanIsIgnored } from './tracing'; +import { startInactiveSpan } from './tracing/trace'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import { hasSpanStreamingEnabled } from './tracing/spans/hasSpanStreamingEnabled'; import type { FetchBreadcrumbHint } from './types/breadcrumb'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fb5104301bac..631b772aad6b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,3 +6,11 @@ export * from './shared-exports'; export { warnOnRemovedBuildOptions } from './build-time-plugins/warnOnRemovedBuildOptions'; export * from './server-exports'; export * from './browser-exports'; + +// `server-exports` and `browser-exports` both export these names (plain vs. browser-guarded +// variants). Explicit re-exports shadow the otherwise-ambiguous star exports and pin the root entry +// to the plain/server variants. Without them, importing any of these from `@sentry/core` is a +// link-time `SyntaxError: ... contains conflicting star exports`. +export { startSpan, startInactiveSpan, startSpanManual } from './tracing/trace'; +export { startIdleSpan } from './tracing/idleSpan'; +export { spanStreamingIntegration } from './integrations/spanStreaming'; diff --git a/packages/browser/src/integrations/spanstreaming.ts b/packages/core/src/integrations/browserSpanStreaming.ts similarity index 75% rename from packages/browser/src/integrations/spanstreaming.ts rename to packages/core/src/integrations/browserSpanStreaming.ts index 2c3c69aeaa1f..6c8ae324082d 100644 --- a/packages/browser/src/integrations/spanstreaming.ts +++ b/packages/core/src/integrations/browserSpanStreaming.ts @@ -1,13 +1,11 @@ -import type { IntegrationFn } from '@sentry/core/browser'; -import { - captureSpan, - debug, - defineIntegration, - hasSpanStreamingEnabled, - SpanBuffer, - spanIsSampled, -} from '@sentry/core/browser'; +import type { IntegrationFn } from '../types/integration'; import { DEBUG_BUILD } from '../debug-build'; +import { defineIntegration } from '../integration'; +import { captureSpan } from '../tracing/spans/captureSpan'; +import { hasSpanStreamingEnabled } from '../tracing/spans/hasSpanStreamingEnabled'; +import { SpanBuffer } from '../tracing/spans/spanBuffer'; +import { debug } from '../utils/debug-logger'; +import { spanIsSampled } from '../utils/spanUtils'; export const INTEGRATION_NAME = 'SpanStreaming' as const; diff --git a/packages/core/src/integrations/express/patch-layer.ts b/packages/core/src/integrations/express/patch-layer.ts index cff2853e673e..26f897284476 100644 --- a/packages/core/src/integrations/express/patch-layer.ts +++ b/packages/core/src/integrations/express/patch-layer.ts @@ -31,7 +31,8 @@ import { SENTRY_OP } from '@sentry/conventions/attributes'; import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import { DEBUG_BUILD } from '../../debug-build'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; -import { SPAN_STATUS_ERROR, startSpanManual, withActiveSpan } from '../../tracing'; +import { SPAN_STATUS_ERROR, withActiveSpan } from '../../tracing'; +import { startSpanManual } from '../../tracing/trace'; import { debug } from '../../utils/debug-logger'; import type { SpanAttributes } from '../../types/span'; import { getActiveSpan } from '../../utils/spanUtils'; diff --git a/packages/core/src/integrations/http/client-subscriptions.ts b/packages/core/src/integrations/http/client-subscriptions.ts index 19386f7d95f9..04411c89270f 100644 --- a/packages/core/src/integrations/http/client-subscriptions.ts +++ b/packages/core/src/integrations/http/client-subscriptions.ts @@ -21,9 +21,9 @@ import { isTracingSuppressed, SPAN_STATUS_ERROR, SPAN_STATUS_UNSET, - startInactiveSpan, withActiveSpan, } from '../../tracing'; +import { startInactiveSpan } from '../../tracing/trace'; import { debug } from '../../utils/debug-logger'; import { LRUMap } from '../../utils/lru'; import { getOutgoingRequestSpanData, setIncomingResponseSpanData } from './get-outgoing-span-data'; diff --git a/packages/core/src/integrations/http/server-subscription.ts b/packages/core/src/integrations/http/server-subscription.ts index ee7f09ab47d4..dde11ea4d597 100644 --- a/packages/core/src/integrations/http/server-subscription.ts +++ b/packages/core/src/integrations/http/server-subscription.ts @@ -31,7 +31,8 @@ import { parseStringToURLObject, stripUrlQueryAndFragment } from '../../utils/ur import { recordRequestSession } from './record-request-session'; import { generateSpanId, generateTraceId } from '../../utils/propagationContext'; import { continueTrace } from '../../tracing/trace'; -import { getSpanStatusFromHttpCode, SPAN_STATUS_ERROR, startSpanManual } from '../../tracing'; +import { getSpanStatusFromHttpCode, SPAN_STATUS_ERROR } from '../../tracing'; +import { startSpanManual } from '../../tracing/trace'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts index f7e1b42325d5..00c15f17e2e1 100644 --- a/packages/core/src/integrations/mcp-server/spans.ts +++ b/packages/core/src/integrations/mcp-server/spans.ts @@ -11,7 +11,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../../semanticAttributes'; -import { startSpan } from '../../tracing'; +import { startSpan } from '../../tracing/trace'; import { buildTransportAttributes, buildTypeSpecificAttributes } from './attributeExtraction'; import { MCP_FUNCTION_ORIGIN_VALUE, diff --git a/packages/core/src/integrations/mcp-server/transport.ts b/packages/core/src/integrations/mcp-server/transport.ts index be292c6fb849..0222c820e406 100644 --- a/packages/core/src/integrations/mcp-server/transport.ts +++ b/packages/core/src/integrations/mcp-server/transport.ts @@ -6,7 +6,8 @@ */ import { getIsolationScope, withIsolationScope } from '../../currentScopes'; -import { startInactiveSpan, withActiveSpan } from '../../tracing'; +import { withActiveSpan } from '../../tracing'; +import { startInactiveSpan } from '../../tracing/trace'; import { isObjectLike } from '../../utils/is'; import { fill } from '../../utils/object'; import { MCP_PROTOCOL_VERSION_ATTRIBUTE } from './attributes'; diff --git a/packages/core/src/integrations/postgresjs.ts b/packages/core/src/integrations/postgresjs.ts index 5418481ee57f..ecde1dbe3f63 100644 --- a/packages/core/src/integrations/postgresjs.ts +++ b/packages/core/src/integrations/postgresjs.ts @@ -4,7 +4,8 @@ import { DEBUG_BUILD } from '../debug-build'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; -import { SPAN_STATUS_ERROR, startSpanManual } from '../tracing'; +import { SPAN_STATUS_ERROR } from '../tracing'; +import { startSpanManual } from '../tracing/trace'; import type { Span } from '../types/span'; import { debug } from '../utils/debug-logger'; import { isObjectLike } from '../utils/is'; diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index 3db2e0e3e7ac..1f405ed4b098 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -10,7 +10,8 @@ import { DEBUG_BUILD } from '../debug-build'; import { captureException } from '../exports'; import { defineIntegration } from '../integration'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; -import { setHttpStatus, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startSpan } from '../tracing'; +import { setHttpStatus, SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '../tracing'; +import { startSpan } from '../tracing/trace'; import type { IntegrationFn } from '../types/integration'; import type { WebFetchHeaders } from '../types/webfetchapi'; import { debug } from '../utils/debug-logger'; diff --git a/packages/core/src/server-exports.ts b/packages/core/src/server-exports.ts index 21469232c101..54aa689b53e6 100644 --- a/packages/core/src/server-exports.ts +++ b/packages/core/src/server-exports.ts @@ -4,6 +4,14 @@ * @module */ +// The span-start APIs and the span streaming integration exist in two variants under the same name: +// the plain ones here, and span-streaming-aware ones in `browser-exports`. See `browser-exports.ts`. +export { startSpan, startInactiveSpan, startSpanManual } from './tracing/trace'; +// `startIdleSpan` has no server consumer, but `@sentry/core` re-exports it — routing it through here +// keeps the root entry's surface unchanged. +export { startIdleSpan } from './tracing/idleSpan'; +export { spanStreamingIntegration } from './integrations/spanStreaming'; + export type { ServerRuntimeClientOptions } from './server-runtime-client'; export { ServerRuntimeClient } from './server-runtime-client'; export type { ServerRuntimeOptions } from './types/options'; diff --git a/packages/core/src/shared-exports.ts b/packages/core/src/shared-exports.ts index 91bcd6d02058..8f70b468803b 100644 --- a/packages/core/src/shared-exports.ts +++ b/packages/core/src/shared-exports.ts @@ -177,7 +177,6 @@ export type { MetricOptions } from './metrics/public-api'; export { createConsolaReporter } from './integrations/consola'; export { SpanBuffer } from './tracing/spans/spanBuffer'; export { hasSpanStreamingEnabled } from './tracing/spans/hasSpanStreamingEnabled'; -export { spanStreamingIntegration } from './integrations/spanStreaming'; export type { FeatureFlag } from './utils/featureFlags'; export { _INTERNAL_copyFlagsFromScopeToEvent, diff --git a/packages/core/src/tracing/browserSpanApi.ts b/packages/core/src/tracing/browserSpanApi.ts new file mode 100644 index 000000000000..c46651a92e20 --- /dev/null +++ b/packages/core/src/tracing/browserSpanApi.ts @@ -0,0 +1,89 @@ +import type { Client } from '../client'; +import { getClient } from '../currentScopes'; +import { spanStreamingIntegration } from '../integrations/browserSpanStreaming'; +import type { Span } from '../types/span'; +import type { StartSpanOptions } from '../types/startSpanOptions'; +import { startIdleSpan as coreStartIdleSpan } from './idleSpan'; +import { hasSpanStreamingEnabled } from './spans/hasSpanStreamingEnabled'; +import { + startInactiveSpan as coreStartInactiveSpan, + startSpan as coreStartSpan, + startSpanManual as coreStartSpanManual, +} from './trace'; + +/** + * Browser variants of the span-start APIs. + * + * These exist purely so that the span streaming integration is only reachable from code that can + * actually start a span. `@sentry/browser`'s `init()` deliberately doesn't reference spanStreamingIntegration, + * so error-only apps tree-shake the entire span streaming graph away without needing + * the `__SENTRY_TRACING__` flag. + * + * The names deliberately match the core originals: `@sentry/core/browser` serves these guarded + * variants, `@sentry/core` serves the plain ones, so browser-facing packages upgrade by changing + * only the import specifier. + */ + +const installed = new WeakSet(); + +/** + * Lazily install the browser span streaming integration. + * + * Defaults to the current client; pass one explicitly from integration hooks, where the client being + * set up isn't necessarily the current one. + * + * @internal + */ +export function _INTERNAL_ensureBrowserSpanStreaming(client: Client | undefined = getClient()): void { + // The `WeakSet` is an allocation optimization, not a semantic gate — `addIntegration()` is already + // idempotent by integration name, including against a user-supplied instance. + if (!client || installed.has(client) || !hasSpanStreamingEnabled(client)) { + return; + } + + installed.add(client); + client.addIntegration(spanStreamingIntegration()); +} + +/** + * Wraps a function with a span and finishes the span after the function is done. + * + * See {@link startSpan} in `@sentry/core` for details. + */ +export function startSpan(options: StartSpanOptions, callback: (span: Span) => T): T { + _INTERNAL_ensureBrowserSpanStreaming(); + return coreStartSpan(options, callback); +} + +/** + * Similar to `startSpan`, but forces the span to be ended manually. + * + * See {@link startSpanManual} in `@sentry/core` for details. + */ +export function startSpanManual(options: StartSpanOptions, callback: (span: Span, finish: () => void) => T): T { + _INTERNAL_ensureBrowserSpanStreaming(); + return coreStartSpanManual(options, callback); +} + +/** + * Creates a span that is not set as active. + * + * See {@link startInactiveSpan} in `@sentry/core` for details. + */ +export function startInactiveSpan(options: StartSpanOptions): Span { + _INTERNAL_ensureBrowserSpanStreaming(); + return coreStartInactiveSpan(options); +} + +/** + * Starts an idle span that automatically ends once no activity happens for a while. + * + * See {@link startIdleSpan} in `@sentry/core` for details. + * + * Typed via `typeof` because `IdleSpanOptions` is intentionally not part of the public type surface, + * and re-declaring the signature here would have to widen it. + */ +export const startIdleSpan: typeof coreStartIdleSpan = (startSpanOptions, options) => { + _INTERNAL_ensureBrowserSpanStreaming(); + return coreStartIdleSpan(startSpanOptions, options); +}; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index fc6239f287c7..354fab2da5a4 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -5,7 +5,7 @@ export { markSpanAsTracerProviderSpan, spanIsTracerProviderSpan, } from './utils'; -export { startIdleSpan, TRACING_DEFAULTS } from './idleSpan'; +export { TRACING_DEFAULTS } from './idleSpan'; export { SentrySpan } from './sentrySpan'; export { _INTERNAL_setDeferSegmentSpanCapture } from './deferSegmentSpanCapture'; export { SentryNonRecordingSpan } from './sentryNonRecordingSpan'; @@ -17,6 +17,9 @@ export { SPAN_STATUS_OK, SPAN_STATUS_UNSET, } from './spanstatus'; +// Note: `startSpan`, `startInactiveSpan`, `startSpanManual` and `startIdleSpan` are deliberately +// not re-exported here. They exist in a plain variant (`server-exports`) and a span-streaming-aware +// variant (`browser-exports`) under the same name — see `browser-exports.ts` for why. export { startSpan, startInactiveSpan, diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index 8fe3137ff4a9..0985cfe64bcd 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -9,7 +9,7 @@ import { WEB_SERVER_RPC_SPAN_OP } from '@sentry/conventions/op'; import { getClient, withIsolationScope } from './currentScopes'; import { captureException } from './exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from './semanticAttributes'; -import { startSpanManual } from './tracing'; +import { startSpanManual } from './tracing/trace'; import { normalize } from './utils/normalize'; import { setNormalizationDepthOverrideHint } from './utils/normalizationHints'; diff --git a/packages/core/test/exports.test.ts b/packages/core/test/exports.test.ts new file mode 100644 index 000000000000..5a87bc09931f --- /dev/null +++ b/packages/core/test/exports.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import * as browserEntry from '../src/browser'; +import * as rootEntry from '../src/index'; +import { spanStreamingIntegration as browserSpanStreamingIntegration } from '../src/integrations/browserSpanStreaming'; +import { spanStreamingIntegration as plainSpanStreamingIntegration } from '../src/integrations/spanStreaming'; +import * as serverEntry from '../src/server'; +import * as browserSpanApi from '../src/tracing/browserSpanApi'; +import { startIdleSpan as plainStartIdleSpan } from '../src/tracing/idleSpan'; +import { + startInactiveSpan as plainStartInactiveSpan, + startSpan as plainStartSpan, + startSpanManual as plainStartSpanManual, +} from '../src/tracing/trace'; + +// `server-exports` and `browser-exports` export these names with different implementations, which +// makes them ambiguous star exports at the root entry. `index.ts` disambiguates them with explicit +// re-exports — if someone adds another `export *` that also carries one of these names, or drops the +// explicit re-export, the root entry silently flips to the wrong variant (or fails to link). +describe('entry point resolution', () => { + const cases = [ + ['startSpan', plainStartSpan, browserSpanApi.startSpan], + ['startInactiveSpan', plainStartInactiveSpan, browserSpanApi.startInactiveSpan], + ['startSpanManual', plainStartSpanManual, browserSpanApi.startSpanManual], + ['startIdleSpan', plainStartIdleSpan, browserSpanApi.startIdleSpan], + ['spanStreamingIntegration', plainSpanStreamingIntegration, browserSpanStreamingIntegration], + ] as const; + + it.each(cases)('`%s`: the root entry serves the plain variant', (name, plain) => { + expect(rootEntry[name]).toBe(plain); + }); + + it.each(cases)('`%s`: the server entry serves the plain variant', (name, plain) => { + expect(serverEntry[name]).toBe(plain); + }); + + it.each(cases)('`%s`: the browser entry serves the browser variant', (name, plain, browser) => { + expect(browserEntry[name]).toBe(browser); + expect(browserEntry[name]).not.toBe(plain); + }); +}); diff --git a/packages/browser/test/integrations/spanstreaming.test.ts b/packages/core/test/integrations/browserSpanStreaming.test.ts similarity index 59% rename from packages/browser/test/integrations/spanstreaming.test.ts rename to packages/core/test/integrations/browserSpanStreaming.test.ts index 16c93f6c8f12..d16e15e989e9 100644 --- a/packages/browser/test/integrations/spanstreaming.test.ts +++ b/packages/core/test/integrations/browserSpanStreaming.test.ts @@ -1,15 +1,9 @@ -import * as SentryCore from '@sentry/core/browser'; -import { - debug, - SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT, - SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS, -} from '@sentry/core/browser'; -import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes'; +import * as SentryCore from '../../src'; +import { debug } from '../../src'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { BrowserClient, spanStreamingIntegration, withStaticSpan } from '../../src'; -import { getDefaultBrowserClientOptions } from '../helper/browser-client-options'; +import { spanStreamingIntegration } from '../../src/integrations/browserSpanStreaming'; +import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; -// Mock SpanBuffer as a class that can be instantiated const mockSpanBufferInstance = vi.hoisted(() => ({ flush: vi.fn(), add: vi.fn(), @@ -20,20 +14,20 @@ const MockSpanBuffer = vi.hoisted(() => { return vi.fn(() => mockSpanBufferInstance); }); -vi.mock('@sentry/core/browser', async () => { - const original = await vi.importActual('@sentry/core/browser'); +vi.mock('../../src/tracing/spans/spanBuffer', async () => { + const original = await vi.importActual('../../src/tracing/spans/spanBuffer'); return { ...original, SpanBuffer: MockSpanBuffer, }; }); -describe('spanStreamingIntegration', () => { +describe('spanStreamingIntegration (browser)', () => { beforeEach(() => { vi.clearAllMocks(); }); - it('has the correct hooks', () => { + it('has the correct name and setup hook', () => { const integration = spanStreamingIntegration(); expect(integration.name).toBe('SpanStreaming'); // eslint-disable-next-line @typescript-eslint/unbound-method @@ -42,8 +36,8 @@ describe('spanStreamingIntegration', () => { it('does not set up span streaming if traceLifecycle is "static"', () => { const debugSpy = vi.spyOn(debug, 'log').mockImplementation(() => {}); - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], traceLifecycle: 'static', @@ -67,8 +61,8 @@ describe('spanStreamingIntegration', () => { ['explicitly set to "stream"', 'stream' as const], ['left unset', undefined], ])('sets up span streaming if traceLifecycle is %s', (_, traceLifecycle) => { - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], traceLifecycle, @@ -77,31 +71,32 @@ describe('spanStreamingIntegration', () => { SentryCore.setCurrentClient(client); client.init(); - expect(MockSpanBuffer).toHaveBeenCalledTimes(1); + expect(MockSpanBuffer).toHaveBeenCalledWith(client); expect(client.getOptions().traceLifecycle).toBe('stream'); }); it('still sets up span streaming if beforeSendSpan is wrapped with withStaticSpan', () => { - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], traceLifecycle: 'stream', - beforeSendSpan: withStaticSpan(span => span), + beforeSendSpan: SentryCore.withStaticSpan(span => span), }); SentryCore.setCurrentClient(client); client.init(); - expect(MockSpanBuffer).toHaveBeenCalledTimes(1); + expect(MockSpanBuffer).toHaveBeenCalledWith(client); expect(client.getOptions().traceLifecycle).toBe('stream'); }); it('enqueues a span into the buffer when the span ends', () => { - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], + traceLifecycle: 'stream', tracesSampleRate: 1, }); @@ -111,57 +106,22 @@ describe('spanStreamingIntegration', () => { const span = new SentryCore.SentrySpan({ name: 'test', sampled: true }); client.emit('afterSpanEnd', span); - expect(mockSpanBufferInstance.add).toHaveBeenCalledWith({ - _segmentSpan: span, - trace_id: span.spanContext().traceId, - span_id: span.spanContext().spanId, - end_timestamp: expect.any(Number), - is_segment: true, - name: 'test', - start_timestamp: expect.any(Number), - status: 'ok', - attributes: { - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - 'sentry.origin': { - type: 'string', - value: 'manual', - }, - 'sentry.sdk.name': { - type: 'string', - value: 'sentry.javascript.browser', - }, - 'sentry.sdk.version': { - type: 'string', - value: expect.any(String), - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]: { - type: 'array', - value: ['SpanStreaming'], - }, - 'sentry.segment.id': { - type: 'string', - value: span.spanContext().spanId, - }, - 'sentry.segment.name': { - type: 'string', - value: 'test', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - }, - }); + expect(mockSpanBufferInstance.add).toHaveBeenCalledWith( + expect.objectContaining({ + _segmentSpan: span, + trace_id: span.spanContext().traceId, + span_id: span.spanContext().spanId, + name: 'test', + }), + ); }); it('does not enqueue a span into the buffer when the span is not sampled', () => { - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], + traceLifecycle: 'stream', tracesSampleRate: 1, }); @@ -177,8 +137,8 @@ describe('spanStreamingIntegration', () => { it('flushes the trace when the segment span ends after a delay for close to finished child spans', () => { vi.useFakeTimers(); - const client = new BrowserClient({ - ...getDefaultBrowserClientOptions(), + const client = new TestClient({ + ...getDefaultTestClientOptions(), dsn: 'https://username@domain/123', integrations: [spanStreamingIntegration()], traceLifecycle: 'stream', diff --git a/packages/core/test/lib/fetch.test.ts b/packages/core/test/lib/fetch.test.ts index 1ded8a41a2c4..844b49f851ce 100644 --- a/packages/core/test/lib/fetch.test.ts +++ b/packages/core/test/lib/fetch.test.ts @@ -4,7 +4,7 @@ import type { HandlerDataFetch } from '../../src'; import { _INTERNAL_getTracingHeadersForFetchRequest, instrumentFetchRequest } from '../../src/fetch'; import { SentryNonRecordingSpan } from '../../src/tracing/sentryNonRecordingSpan'; import type { Span } from '../../src/types/span'; -import * as tracing from '../../src/tracing'; +import * as tracing from '../../src/tracing/trace'; import * as spanUtils from '../../src/utils/spanUtils'; import * as traceData from '../../src/utils/traceData'; diff --git a/packages/core/test/lib/integrations/express/patch-layer.test.ts b/packages/core/test/lib/integrations/express/patch-layer.test.ts index 10b5e8b2a3c3..4d41dc785a5a 100644 --- a/packages/core/test/lib/integrations/express/patch-layer.test.ts +++ b/packages/core/test/lib/integrations/express/patch-layer.test.ts @@ -140,6 +140,9 @@ vi.mock('../../../../src/tracing', () => ({ expect(span).toBe(parentSpan); return cb(); }, +})); + +vi.mock('../../../../src/tracing/trace', () => ({ startSpanManual(options: StartSpanOptions, callback: (span: Span) => T): T { const span = new MockSpan(options); mockSpans.push(span); diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts index 8921585ecf88..f67cf54b09a4 100644 --- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts +++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as currentScopes from '../../../../src/currentScopes'; import { wrapMcpServerWithSentry } from '../../../../src/integrations/mcp-server'; -import * as tracingModule from '../../../../src/tracing'; +import * as tracingModule from '../../../../src/tracing/trace'; import { createMockClient, createMockMcpServer, diff --git a/packages/core/test/lib/integrations/mcp-server/piiFiltering.test.ts b/packages/core/test/lib/integrations/mcp-server/piiFiltering.test.ts index 8be3c1919957..611802f9ae79 100644 --- a/packages/core/test/lib/integrations/mcp-server/piiFiltering.test.ts +++ b/packages/core/test/lib/integrations/mcp-server/piiFiltering.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as currentScopes from '../../../../src/currentScopes'; import { wrapMcpServerWithSentry } from '../../../../src/integrations/mcp-server'; import { filterMcpPiiFromSpanData } from '../../../../src/integrations/mcp-server/piiFiltering'; -import * as tracingModule from '../../../../src/tracing'; +import * as tracingModule from '../../../../src/tracing/trace'; import { createMockClient, createMockMcpServer, createMockTransport } from './testUtils'; describe('MCP Server PII Filtering', () => { diff --git a/packages/core/test/lib/integrations/mcp-server/semanticConventions.test.ts b/packages/core/test/lib/integrations/mcp-server/semanticConventions.test.ts index 06b3cef96592..d90fe023f89e 100644 --- a/packages/core/test/lib/integrations/mcp-server/semanticConventions.test.ts +++ b/packages/core/test/lib/integrations/mcp-server/semanticConventions.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as currentScopes from '../../../../src/currentScopes'; import { wrapMcpServerWithSentry } from '../../../../src/integrations/mcp-server'; -import * as tracingModule from '../../../../src/tracing'; +import * as tracingModule from '../../../../src/tracing/trace'; import { createMockClient, createMockMcpServer, createMockTransport } from './testUtils'; describe('MCP Server Semantic Conventions', () => { diff --git a/packages/core/test/lib/integrations/mcp-server/transportInstrumentation.test.ts b/packages/core/test/lib/integrations/mcp-server/transportInstrumentation.test.ts index 29d2c90a35f9..92fec69f90bf 100644 --- a/packages/core/test/lib/integrations/mcp-server/transportInstrumentation.test.ts +++ b/packages/core/test/lib/integrations/mcp-server/transportInstrumentation.test.ts @@ -24,7 +24,7 @@ import { wrapTransportOnMessage, wrapTransportSend, } from '../../../../src/integrations/mcp-server/transport'; -import * as tracingModule from '../../../../src/tracing'; +import * as tracingModule from '../../../../src/tracing/trace'; import { createMockClient, createMockMcpServer, diff --git a/packages/core/test/lib/integrations/postgresjs.test.ts b/packages/core/test/lib/integrations/postgresjs.test.ts index 921e82b89a42..6542a1d22358 100644 --- a/packages/core/test/lib/integrations/postgresjs.test.ts +++ b/packages/core/test/lib/integrations/postgresjs.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { _reconstructQuery, _sanitizeSqlQuery, instrumentPostgresJsSql } from '../../../src/integrations/postgresjs'; -import * as tracing from '../../../src/tracing'; +import * as tracing from '../../../src/tracing/trace'; import * as spanUtils from '../../../src/utils/spanUtils'; describe('PostgresJs portable instrumentation', () => { diff --git a/packages/core/test/lib/integrations/supabase.test.ts b/packages/core/test/lib/integrations/supabase.test.ts index ce00004056c6..d9a47452e282 100644 --- a/packages/core/test/lib/integrations/supabase.test.ts +++ b/packages/core/test/lib/integrations/supabase.test.ts @@ -30,12 +30,15 @@ const currentScopesMocks = vi.hoisted(() => ({ // Mock tracing to avoid needing full SDK setup vi.mock('../../../src/tracing', () => ({ - startSpan: tracingMocks.startSpan, setHttpStatus: vi.fn(), SPAN_STATUS_OK: 1, SPAN_STATUS_ERROR: 2, })); +vi.mock('../../../src/tracing/trace', () => ({ + startSpan: tracingMocks.startSpan, +})); + vi.mock('../../../src/currentScopes', () => ({ getClient: currentScopesMocks.getClient, })); diff --git a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts index 06dc36f12505..34a0f1bcd55c 100644 --- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts +++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts @@ -13,8 +13,8 @@ import { SentryNonRecordingSpan, SentrySpan, setCapturedScopesOnSpan, - startInactiveSpan, } from '../../../src/tracing'; +import { startInactiveSpan } from '../../../src/tracing/trace'; import { freezeDscOnSpan, getDynamicSamplingContextFromClient } from '../../../src/tracing/dynamicSamplingContext'; import type { Span, SpanContextData } from '../../../src/types/span'; import type { TransactionSource } from '../../../src/types/transaction'; diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index b75eec2877f0..2157cd154429 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -21,12 +21,10 @@ import { isTracingSuppressed, registerSpanErrorInstrumentation, SentrySpan, - startInactiveSpan, - startSpan, - startSpanManual, suppressTracing, withActiveSpan, } from '../../../src/tracing'; +import { startInactiveSpan, startSpan, startSpanManual } from '../../../src/tracing/trace'; import { SentryNonRecordingSpan } from '../../../src/tracing/sentryNonRecordingSpan'; import { startNewTrace } from '../../../src/tracing/trace'; import type { Event } from '../../../src/types/event'; diff --git a/packages/core/test/lib/trpc.test.ts b/packages/core/test/lib/trpc.test.ts index 6f9f964962b8..15c445bdd99c 100644 --- a/packages/core/test/lib/trpc.test.ts +++ b/packages/core/test/lib/trpc.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; import { type Client, setCurrentClient, type Span, trpcMiddleware } from '../../src'; import * as currentScopes from '../../src/currentScopes'; import * as exports from '../../src/exports'; -import * as tracing from '../../src/tracing'; +import * as tracing from '../../src/tracing/trace'; import { resolveDataCollectionOptions } from '../../src/utils/data-collection/resolveDataCollectionOptions'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; diff --git a/packages/effect/src/index.types.ts b/packages/effect/src/index.types.ts index bc77a68bd35d..f0c594a2e5dc 100644 --- a/packages/effect/src/index.types.ts +++ b/packages/effect/src/index.types.ts @@ -22,6 +22,12 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/nuxt/src/index.types.ts b/packages/nuxt/src/index.types.ts index 4865aac868d3..12fec1515234 100644 --- a/packages/nuxt/src/index.types.ts +++ b/packages/nuxt/src/index.types.ts @@ -17,6 +17,12 @@ export declare function init(options: Options | SentryNuxtClientOptions | Sentry export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/react-router/src/index.types.ts b/packages/react-router/src/index.types.ts index af8c4da6d94a..d05254378757 100644 --- a/packages/react-router/src/index.types.ts +++ b/packages/react-router/src/index.types.ts @@ -17,6 +17,12 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index 498b640d7e6e..f3fcacf069f3 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -19,6 +19,12 @@ export declare const browserTracingIntegration: typeof clientSdk.browserTracingI export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 71e45f1d2a2d..4f6e9186f5f5 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -19,6 +19,12 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 135e466438d8..65e638c558e0 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -47,6 +47,12 @@ export declare function wrapLoadWithSentry any>(orig export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; diff --git a/packages/tanstackstart-react/src/index.types.ts b/packages/tanstackstart-react/src/index.types.ts index d1464a88175e..b4104071e95d 100644 --- a/packages/tanstackstart-react/src/index.types.ts +++ b/packages/tanstackstart-react/src/index.types.ts @@ -19,6 +19,12 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; +// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming +// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the +// star exports above are ambiguous without these. +export declare const startSpan: typeof clientSdk.startSpan; +export declare const startSpanManual: typeof clientSdk.startSpanManual; +export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; export declare const withStaticSpan: typeof clientSdk.withStaticSpan; // oxlint-disable-next-line typescript/no-deprecated export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan; From 47848cf999bb09d415db817fdd6d91cd405b0d67 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 12 Aug 2026 18:23:21 +0200 Subject: [PATCH 2/2] remove so much fluff comments. i hate clankers --- packages/astro/src/index.types.ts | 3 --- packages/core/src/browser-exports.ts | 10 ++-------- packages/core/src/index.ts | 7 +++---- packages/core/src/server-exports.ts | 4 ---- packages/core/src/tracing/browserSpanApi.ts | 10 +++------- packages/core/src/tracing/index.ts | 3 --- packages/effect/src/index.types.ts | 3 --- packages/nuxt/src/index.types.ts | 3 --- packages/react-router/src/index.types.ts | 3 --- packages/remix/src/index.types.ts | 3 --- packages/solidstart/src/index.types.ts | 3 --- packages/sveltekit/src/index.types.ts | 3 --- packages/tanstackstart-react/src/index.types.ts | 3 --- 13 files changed, 8 insertions(+), 50 deletions(-) diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index 0a387e30a89c..e9bb84c6e266 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -21,9 +21,6 @@ export declare function init(options: Options | clientSdk.BrowserOptions | NodeO export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/core/src/browser-exports.ts b/packages/core/src/browser-exports.ts index 1df55682ac77..0cd910db7d82 100644 --- a/packages/core/src/browser-exports.ts +++ b/packages/core/src/browser-exports.ts @@ -3,14 +3,7 @@ * * @module */ -// These share their names with the plain variants in `server-exports`, but additionally make sure -// the browser span streaming integration is installed on the client before the span starts. That -// indirection is what lets error-only browser bundles tree-shake span streaming away entirely: -// `init()` never references the integration, so it's only retained when span-creating code is. -// -// `@sentry/core` pins these names to the plain variants (see `index.ts`); `@sentry/core/browser` -// serves the guarded ones. Browser-facing packages therefore upgrade by importing from -// `@sentry/core/browser` — no call site changes. + export { startSpan, startInactiveSpan, @@ -18,6 +11,7 @@ export { startIdleSpan, _INTERNAL_ensureBrowserSpanStreaming, } from './tracing/browserSpanApi'; + export { spanStreamingIntegration } from './integrations/browserSpanStreaming'; export { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 631b772aad6b..d96a9afe71a6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,10 +7,9 @@ export { warnOnRemovedBuildOptions } from './build-time-plugins/warnOnRemovedBui export * from './server-exports'; export * from './browser-exports'; -// `server-exports` and `browser-exports` both export these names (plain vs. browser-guarded -// variants). Explicit re-exports shadow the otherwise-ambiguous star exports and pin the root entry -// to the plain/server variants. Without them, importing any of these from `@sentry/core` is a -// link-time `SyntaxError: ... contains conflicting star exports`. +// `server-exports` and `browser-exports` both export these APIs. +// We need to re-export them here to disambiguate the exports for anyone importing +// from `@sentry/core`. Server exports win over browser exports. export { startSpan, startInactiveSpan, startSpanManual } from './tracing/trace'; export { startIdleSpan } from './tracing/idleSpan'; export { spanStreamingIntegration } from './integrations/spanStreaming'; diff --git a/packages/core/src/server-exports.ts b/packages/core/src/server-exports.ts index 54aa689b53e6..e21aea8a970b 100644 --- a/packages/core/src/server-exports.ts +++ b/packages/core/src/server-exports.ts @@ -4,11 +4,7 @@ * @module */ -// The span-start APIs and the span streaming integration exist in two variants under the same name: -// the plain ones here, and span-streaming-aware ones in `browser-exports`. See `browser-exports.ts`. export { startSpan, startInactiveSpan, startSpanManual } from './tracing/trace'; -// `startIdleSpan` has no server consumer, but `@sentry/core` re-exports it — routing it through here -// keeps the root entry's surface unchanged. export { startIdleSpan } from './tracing/idleSpan'; export { spanStreamingIntegration } from './integrations/spanStreaming'; diff --git a/packages/core/src/tracing/browserSpanApi.ts b/packages/core/src/tracing/browserSpanApi.ts index c46651a92e20..bd59c12da591 100644 --- a/packages/core/src/tracing/browserSpanApi.ts +++ b/packages/core/src/tracing/browserSpanApi.ts @@ -18,13 +18,9 @@ import { * actually start a span. `@sentry/browser`'s `init()` deliberately doesn't reference spanStreamingIntegration, * so error-only apps tree-shake the entire span streaming graph away without needing * the `__SENTRY_TRACING__` flag. - * - * The names deliberately match the core originals: `@sentry/core/browser` serves these guarded - * variants, `@sentry/core` serves the plain ones, so browser-facing packages upgrade by changing - * only the import specifier. */ -const installed = new WeakSet(); +const clientsWithIntegration = new WeakSet(); /** * Lazily install the browser span streaming integration. @@ -37,11 +33,11 @@ const installed = new WeakSet(); export function _INTERNAL_ensureBrowserSpanStreaming(client: Client | undefined = getClient()): void { // The `WeakSet` is an allocation optimization, not a semantic gate — `addIntegration()` is already // idempotent by integration name, including against a user-supplied instance. - if (!client || installed.has(client) || !hasSpanStreamingEnabled(client)) { + if (!client || clientsWithIntegration.has(client) || !hasSpanStreamingEnabled(client)) { return; } - installed.add(client); + clientsWithIntegration.add(client); client.addIntegration(spanStreamingIntegration()); } diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index 354fab2da5a4..30353daba631 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -17,9 +17,6 @@ export { SPAN_STATUS_OK, SPAN_STATUS_UNSET, } from './spanstatus'; -// Note: `startSpan`, `startInactiveSpan`, `startSpanManual` and `startIdleSpan` are deliberately -// not re-exported here. They exist in a plain variant (`server-exports`) and a span-streaming-aware -// variant (`browser-exports`) under the same name — see `browser-exports.ts` for why. export { startSpan, startInactiveSpan, diff --git a/packages/effect/src/index.types.ts b/packages/effect/src/index.types.ts index f0c594a2e5dc..5311b8a6f24e 100644 --- a/packages/effect/src/index.types.ts +++ b/packages/effect/src/index.types.ts @@ -22,9 +22,6 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/nuxt/src/index.types.ts b/packages/nuxt/src/index.types.ts index 12fec1515234..953331327cea 100644 --- a/packages/nuxt/src/index.types.ts +++ b/packages/nuxt/src/index.types.ts @@ -17,9 +17,6 @@ export declare function init(options: Options | SentryNuxtClientOptions | Sentry export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/react-router/src/index.types.ts b/packages/react-router/src/index.types.ts index d05254378757..2c0a21a6c5ec 100644 --- a/packages/react-router/src/index.types.ts +++ b/packages/react-router/src/index.types.ts @@ -17,9 +17,6 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index f3fcacf069f3..6b3b86bba2d5 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -19,9 +19,6 @@ export declare const browserTracingIntegration: typeof clientSdk.browserTracingI export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 4f6e9186f5f5..c02a833b40a0 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -19,9 +19,6 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 65e638c558e0..ba597b6d40b4 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -47,9 +47,6 @@ export declare function wrapLoadWithSentry any>(orig export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan; diff --git a/packages/tanstackstart-react/src/index.types.ts b/packages/tanstackstart-react/src/index.types.ts index b4104071e95d..847a6daac441 100644 --- a/packages/tanstackstart-react/src/index.types.ts +++ b/packages/tanstackstart-react/src/index.types.ts @@ -19,9 +19,6 @@ export declare function init(options: Options | clientSdk.BrowserOptions | serve export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration; -// The client entry serves the `@sentry/core/browser` span-start APIs, which install span streaming -// on first use; the server entry serves the plain `@sentry/core` ones. Same signatures, but the -// star exports above are ambiguous without these. export declare const startSpan: typeof clientSdk.startSpan; export declare const startSpanManual: typeof clientSdk.startSpanManual; export declare const startInactiveSpan: typeof clientSdk.startInactiveSpan;