From 04a8ee4b145757cbc32022336050035849debe95 Mon Sep 17 00:00:00 2001 From: isaacs Date: Sun, 18 Oct 2026 15:29:34 -0700 Subject: [PATCH] fix(deno): Re-export portable helpers that Cloudflare exposes Cloudflare runs on workerd with no Node APIs, but it still exports more than Deno. The missing exports already work in Deno, because they come from `@sentry/core` and `@sentry/server-utils`, which Deno depends on. The manual AI client helpers matter most. Deno does not inject diagnostics channels by default, so `instrumentOpenAiClient` and the other `instrument*` helpers are the documented way to get AI spans there. Deno now re-exports `addIntegration`, `withActiveSpan`, `getSpanDescendants`, `setConversationId`, `createConsolaReporter`, `growthbookIntegration`, `moduleMetadataIntegration`, `trpcMiddleware`, `prismaIntegration`, the OpenAI, Anthropic, Google GenAI, Mistral, TypeSafe, LangChain and LangGraph helpers, and the `FlueOptions` type. Deno also gets `initWithoutDefaultIntegrations` and `getDefaultIntegrationsWithoutPerformance`, to match Node and Bun. The default integration set does not change. Some Cloudflare exports stay out. `instrumentWorkersAiClient` wraps a binding that exists only on Cloudflare. `spotlightIntegration` and `httpServerIntegration` live in the Cloudflare package, not in a shared entry point, so they need more than a re-export. `parameterize` was in the issue, but Cloudflare no longer exports it. Fixes #23896 Fixes JS-3522 Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/deno/src/index.ts | 31 +++++++++++++++++++--- packages/deno/src/sdk.ts | 23 +++++++++++++--- packages/deno/test/exports.test.ts | 42 ++++++++++++++++++++++++++++++ packages/deno/test/sdk.test.ts | 38 +++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 packages/deno/test/exports.test.ts diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index badb9ed5a8af..f55fe14de14f 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -26,6 +26,7 @@ export type { DenoOptions } from './types'; export { addEventProcessor, addBreadcrumb, + addIntegration, captureException, captureEvent, captureMessage, @@ -52,6 +53,7 @@ export { setAttribute, setAttributes, setUser, + setConversationId, getSpanStatusFromHttpCode, setHttpStatus, withScope, @@ -69,6 +71,8 @@ export { startNewTrace, bindScopeToEmitter, suppressTracing, + withActiveSpan, + getSpanDescendants, eventFiltersIntegration, linkedErrorsIntegration, functionToStringIntegration, @@ -78,6 +82,7 @@ export { dedupeIntegration, extraErrorDataIntegration, rewriteFramesIntegration, + moduleMetadataIntegration, supabaseIntegration, instrumentSupabaseClient, zodErrorsIntegration, @@ -94,7 +99,9 @@ export { spanToTraceHeader, spanToBaggageHeader, updateSpanName, + createConsolaReporter, featureFlagsIntegration, + growthbookIntegration, metrics, withStaticSpan, // oxlint-disable-next-line typescript/no-deprecated @@ -103,12 +110,30 @@ export { consoleLoggingIntegration, spanStreamingIntegration, } from '@sentry/core'; -export { wrapMcpServerWithSentry } from '@sentry/core/server'; -export { instrumentPostgresJsSql } from '@sentry/server-utils'; +export { trpcMiddleware, wrapMcpServerWithSentry } from '@sentry/core/server'; +export { + instrumentPostgresJsSql, + prismaIntegration, + instrumentOpenAiClient, + instrumentAnthropicAiClient, + instrumentGoogleGenAIClient, + instrumentMistralAiClient, + instrumentTypeSafeClient, + createLangChainCallbackHandler, + instrumentLangChainEmbeddings, + instrumentStateGraph, + instrumentCreateReactAgent, +} from '@sentry/server-utils'; +export type { FlueOptions } from '@sentry/server-utils'; export { DenoClient } from './client'; -export { getDefaultIntegrations, init } from './sdk'; +export { + getDefaultIntegrations, + getDefaultIntegrationsWithoutPerformance, + init, + initWithoutDefaultIntegrations, +} from './sdk'; export { denoServeIntegration } from './integrations/deno-serve'; export type { DenoServeIntegrationOptions } from './integrations/deno-serve'; export { fetchIntegration } from './integrations/fetch'; diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index 93869d8face1..8b277fb32453 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -29,8 +29,8 @@ import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; import { makeFetchTransport } from './transports'; import type { DenoOptions } from './types'; -/** Get the default integrations for the Deno SDK. */ -export function getDefaultIntegrations(options: Options): Integration[] { +/** Get the default integrations for the Deno SDK, excluding performance integrations. */ +export function getDefaultIntegrationsWithoutPerformance(): Integration[] { // We return a copy of the defaultIntegrations here to avoid mutating this return [ // Common @@ -52,10 +52,14 @@ export function getDefaultIntegrations(options: Options): Integration[] { globalHandlersIntegration(), // server-utils integrations ...getErrorIntegrations(), - ...(hasSpansEnabled(options) ? getTracingIntegrations() : []), ]; } +/** Get the default integrations for the Deno SDK. */ +export function getDefaultIntegrations(options: Options): Integration[] { + return [...getDefaultIntegrationsWithoutPerformance(), ...(hasSpansEnabled(options) ? getTracingIntegrations() : [])]; +} + const defaultStackParser: StackParser = createStackParser(nodeStackLineParser()); /** @@ -103,10 +107,21 @@ const defaultStackParser: StackParser = createStackParser(nodeStackLineParser()) * @see {@link DenoOptions} for documentation on configuration options. */ export function init(options: DenoOptions = {}): Client { + return _init(options, getDefaultIntegrations); +} + +/** + * Initialize Sentry for Deno, without any integrations added by default. + */ +export function initWithoutDefaultIntegrations(options: DenoOptions = {}): Client { + return _init(options, () => []); +} + +function _init(options: DenoOptions, getDefaultIntegrationsImpl: (options: Options) => Integration[]): Client { // Computed into a local rather than written back onto `options`: the default set // depends on the tracing options, so caching it on the caller's object would pin the // result of the first `init` for any reused options object. - const defaultIntegrations = options.defaultIntegrations ?? getDefaultIntegrations(options); + const defaultIntegrations = options.defaultIntegrations ?? getDefaultIntegrationsImpl(options); const clientOptions: ServerRuntimeClientOptions = { ...options, diff --git a/packages/deno/test/exports.test.ts b/packages/deno/test/exports.test.ts new file mode 100644 index 000000000000..c1468653c566 --- /dev/null +++ b/packages/deno/test/exports.test.ts @@ -0,0 +1,42 @@ +import { assertEquals } from 'https://deno.land/std@0.212.0/assert/assert_equals.ts'; +import * as Sentry from '../build/esm/index.js'; +import type { FlueOptions } from '../build/esm/index.js'; + +// Exports that Cloudflare ships and that need no Node APIs. +const EXPECTED_FUNCTION_EXPORTS = [ + // @sentry/core + 'addIntegration', + 'withActiveSpan', + 'getSpanDescendants', + 'setConversationId', + 'createConsolaReporter', + 'growthbookIntegration', + 'moduleMetadataIntegration', + // @sentry/core/server + 'trpcMiddleware', + // @sentry/server-utils + 'prismaIntegration', + 'instrumentOpenAiClient', + 'instrumentAnthropicAiClient', + 'instrumentGoogleGenAIClient', + 'instrumentMistralAiClient', + 'instrumentTypeSafeClient', + 'createLangChainCallbackHandler', + 'instrumentLangChainEmbeddings', + 'instrumentStateGraph', + 'instrumentCreateReactAgent', + // ./sdk + 'initWithoutDefaultIntegrations', + 'getDefaultIntegrationsWithoutPerformance', +]; + +for (const name of EXPECTED_FUNCTION_EXPORTS) { + Deno.test(`exports ${name}`, () => { + assertEquals(typeof (Sentry as Record)[name], 'function'); + }); +} + +Deno.test('exports the FlueOptions type', () => { + const options: FlueOptions = {}; + assertEquals(typeof options, 'object'); +}); diff --git a/packages/deno/test/sdk.test.ts b/packages/deno/test/sdk.test.ts index 032442ece005..9eebad5e6482 100644 --- a/packages/deno/test/sdk.test.ts +++ b/packages/deno/test/sdk.test.ts @@ -3,7 +3,9 @@ import { assertArrayIncludes } from 'https://deno.land/std@0.212.0/assert/assert import { getCurrentScope, getDefaultIntegrations, + getDefaultIntegrationsWithoutPerformance, init as initSdk, + initWithoutDefaultIntegrations, spanStreamingIntegration, } from '../build/esm/index.js'; import type { DenoOptions } from '../build/esm/index.js'; @@ -82,3 +84,39 @@ Deno.test("init doesn't write the computed default integrations back onto the op const names = second.getOptions().integrations.map(i => i.name); assertArrayIncludes(names, ['Graphql']); }); + +Deno.test('initWithoutDefaultIntegrations sets up only the integrations the user passes', () => { + getCurrentScope().setClient(undefined); + const client = initWithoutDefaultIntegrations({ + tracesSampleRate: 1, + traceLifecycle: 'static', + integrations: [spanStreamingIntegration()], + }); + const names = client.getOptions().integrations.map(i => i.name); + assertEquals(names, ['SpanStreaming']); +}); + +Deno.test('initWithoutDefaultIntegrations keeps defaultIntegrations the user passes', () => { + getCurrentScope().setClient(undefined); + const client = initWithoutDefaultIntegrations({ + traceLifecycle: 'static', + defaultIntegrations: getDefaultIntegrationsWithoutPerformance(), + }); + const names = client.getOptions().integrations.map(i => i.name); + assertArrayIncludes(names, ['Breadcrumbs', 'DenoServe', 'Express']); +}); + +Deno.test('getDefaultIntegrationsWithoutPerformance leaves out the tracing set', () => { + const names = getDefaultIntegrationsWithoutPerformance().map(i => i.name); + assertArrayIncludes(names, ['Breadcrumbs', 'DenoServe', 'Express', 'Fastify']); + assert(!names.includes('Graphql')); + assert(!names.includes('Postgres')); + assert(!names.includes('OpenAI')); +}); + +Deno.test('getDefaultIntegrations is getDefaultIntegrationsWithoutPerformance plus the tracing set', () => { + const base = getDefaultIntegrationsWithoutPerformance().map(i => i.name); + const full = getDefaultIntegrations({ tracesSampleRate: 1 }).map(i => i.name); + assertEquals(full.slice(0, base.length), base); + assertArrayIncludes(full.slice(base.length), ['Graphql', 'Postgres', 'OpenAI']); +});