diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 662498edb46e..ce0a535ce97e 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -65,7 +65,7 @@ "@sentry/server-utils": "10.64.0" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.x" + "@cloudflare/workers-types": "^4.x || ^5.x" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -73,7 +73,7 @@ } }, "devDependencies": { - "@cloudflare/workers-types": "4.20250922.0", + "@cloudflare/workers-types": "5.20260710.1", "@types/node": "^18.19.1", "wrangler": "4.61.0" }, diff --git a/packages/cloudflare/src/instrumentations/worker/instrumentQueueProducer.ts b/packages/cloudflare/src/instrumentations/worker/instrumentQueueProducer.ts index e52135704bb4..40b9f404e3bc 100644 --- a/packages/cloudflare/src/instrumentations/worker/instrumentQueueProducer.ts +++ b/packages/cloudflare/src/instrumentations/worker/instrumentQueueProducer.ts @@ -69,7 +69,7 @@ export function instrumentQueueProducer(queue: T, bindingName: if (prop === 'send') { const original = Reflect.get(target, prop, receiver) as Queue['send']; - return function (this: unknown, message: unknown, options?: QueueSendOptions): Promise { + return function (this: unknown, message: unknown, options?: QueueSendOptions): ReturnType { return startPublishSpan({ bindingName, bodySize: getBodySize(message) }, () => Reflect.apply(original, target, [message, options]), ); @@ -82,7 +82,7 @@ export function instrumentQueueProducer(queue: T, bindingName: this: unknown, messages: Iterable, options?: QueueSendBatchOptions, - ): Promise { + ): ReturnType { const messageArray = Array.from(messages); const totalBodySize = messageArray.reduce((acc, m) => { const size = getBodySize(m.body); diff --git a/packages/cloudflare/src/pages-plugin.ts b/packages/cloudflare/src/pages-plugin.ts index 971190d32aa0..29c3c48a5fc6 100644 --- a/packages/cloudflare/src/pages-plugin.ts +++ b/packages/cloudflare/src/pages-plugin.ts @@ -1,3 +1,4 @@ +import type { ExecutionContext } from '@cloudflare/workers-types'; import { setAsyncLocalStorageAsyncContextStrategy } from './async'; import type { CloudflareOptions } from './client'; import { wrapRequestHandler } from './request'; @@ -53,8 +54,9 @@ export function sentryPagesPlugin< } const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions; - return wrapRequestHandler({ options, request: context.request, context: { ...context, props: {} } }, () => - context.next(), - ); + // A Pages `EventPluginContext` is not a Workers `ExecutionContext` (it lacks `exports`/`tracing`), + // but `wrapRequestHandler` only reads `waitUntil` off it, so a structural cast is safe here. + const executionContext = { ...context, props: {} } as unknown as ExecutionContext; + return wrapRequestHandler({ options, request: context.request, context: executionContext }, () => context.next()); }; } diff --git a/packages/cloudflare/src/workflows.ts b/packages/cloudflare/src/workflows.ts index 2a5987a67ff8..beccbcfa882c 100644 --- a/packages/cloudflare/src/workflows.ts +++ b/packages/cloudflare/src/workflows.ts @@ -10,12 +10,15 @@ import { withScope, } from '@sentry/core'; import type { + WorkflowDelayDuration, WorkflowEntrypoint, WorkflowEvent, WorkflowSleepDuration, WorkflowStep, WorkflowStepConfig, + WorkflowStepContext, WorkflowStepEvent, + WorkflowStepRollbackOptions, WorkflowTimeoutDuration, } from 'cloudflare:workers'; import { setAsyncLocalStorageAsyncContextStrategy } from './async'; @@ -70,23 +73,34 @@ class WrappedWorkflowStep implements WorkflowStep { public async do>( name: string, - callback: (...args: unknown[]) => Promise, + callback: (ctx: WorkflowStepContext) => Promise, + rollbackOptions?: WorkflowStepRollbackOptions, ): Promise; - public async do>( + public async do, const C extends WorkflowStepConfig>( name: string, - config: WorkflowStepConfig, - callback: (...args: unknown[]) => Promise, + config: C, + callback: ( + ctx: WorkflowStepContext, + ) => Promise, + rollbackOptions?: WorkflowStepRollbackOptions, ): Promise; public async do>( name: string, - configOrCallback: WorkflowStepConfig | (() => Promise), - maybeCallback?: (...args: unknown[]) => Promise, + configOrCallback: WorkflowStepConfig | ((ctx: WorkflowStepContext) => Promise), + callbackOrRollback?: ((ctx: WorkflowStepContext) => Promise) | WorkflowStepRollbackOptions, + maybeRollback?: WorkflowStepRollbackOptions, ): Promise { // Capture the current scope, so parent span (e.g., a startSpan surrounding step.do) is preserved const scopeForStep = getCurrentScope(); - const userCallback = (maybeCallback || configOrCallback) as (...args: unknown[]) => Promise; - const config = typeof configOrCallback === 'function' ? undefined : configOrCallback; + const hasConfig = typeof configOrCallback !== 'function'; + const config = hasConfig ? configOrCallback : undefined; + const userCallback = (hasConfig ? callbackOrRollback : configOrCallback) as ( + ctx: WorkflowStepContext, + ) => Promise; + const rollbackOptions = (hasConfig ? maybeRollback : callbackOrRollback) as + | WorkflowStepRollbackOptions + | undefined; const instrumentedCallback = async (...args: unknown[]): Promise => { // Feature detection: Cloudflare Workflows (April 2026+) pass a step context @@ -109,7 +123,9 @@ class WrappedWorkflowStep implements WorkflowStep { attributes: { 'cloudflare.workflow.timeout': config?.timeout, 'cloudflare.workflow.retries.backoff': config?.retries?.backoff, - 'cloudflare.workflow.retries.delay': config?.retries?.delay, + // In workers-types v5, `delay` may be a `WorkflowDelayFunction`, which isn't a valid span attribute value. + 'cloudflare.workflow.retries.delay': + typeof config?.retries?.delay === 'function' ? undefined : config?.retries?.delay, 'cloudflare.workflow.retries.limit': config?.retries?.limit, 'cloudflare.workflow.attempt': attempt, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.workflow', @@ -118,7 +134,7 @@ class WrappedWorkflowStep implements WorkflowStep { }, async span => { try { - const result = await userCallback(...args); + const result = await (userCallback as (...args: unknown[]) => Promise)(...args); span.setStatus({ code: 1 }); return result; } catch (error) { @@ -133,7 +149,15 @@ class WrappedWorkflowStep implements WorkflowStep { ); }; - return config ? this._step.do(name, config, instrumentedCallback) : this._step.do(name, instrumentedCallback); + if (config) { + return rollbackOptions + ? this._step.do(name, config, instrumentedCallback, rollbackOptions) + : this._step.do(name, config, instrumentedCallback); + } + + return rollbackOptions + ? this._step.do(name, instrumentedCallback, rollbackOptions) + : this._step.do(name, instrumentedCallback); } public async sleep(name: string, duration: WorkflowSleepDuration): Promise { diff --git a/yarn.lock b/yarn.lock index 3a6f2d09ef91..1913c9a83b35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3113,6 +3113,11 @@ resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250922.0.tgz#a159fbf3bb785fa85b473ecfaa8c501525827885" integrity sha512-BaqlKnVc0Xzqm9xt3TC4v0yB9EHy5vVqpiWz+DAsbEmdcpUbqdBschvI9502p6FgFbZElD7XcxTEeViXLsoO0A== +"@cloudflare/workers-types@5.20260710.1": + version "5.20260710.1" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-5.20260710.1.tgz#215c0cf84c3917552b53a1f5129150abf0b6009f" + integrity sha512-4ooaY2Pb5XGwDn8Fzm6jnTAJkIX0R5LBvL9euQpp2T58sQItlAQd9yivAlkwGhpY5cM1u81/9HaXwKAjXwtyzA== + "@cloudflare/workers-types@^4.20260426.0": version "4.20260519.1" resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20260519.1.tgz#061b4594e874a0e506ddc6599221939e6718d2a7"