Skip to content
Draft
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
4 changes: 2 additions & 2 deletions packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@
"@sentry/server-utils": "10.64.0"
},
"peerDependencies": {
"@cloudflare/workers-types": "^4.x"
"@cloudflare/workers-types": "^4.x || ^5.x"
},
"peerDependenciesMeta": {
"@cloudflare/workers-types": {
"optional": true
}
},
"devDependencies": {
"@cloudflare/workers-types": "4.20250922.0",
"@cloudflare/workers-types": "5.20260710.1",
"@types/node": "^18.19.1",
"wrangler": "4.61.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function instrumentQueueProducer<T extends Queue>(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<void> {
return function (this: unknown, message: unknown, options?: QueueSendOptions): ReturnType<Queue['send']> {
return startPublishSpan({ bindingName, bodySize: getBodySize(message) }, () =>
Reflect.apply(original, target, [message, options]),
);
Expand All @@ -82,7 +82,7 @@ export function instrumentQueueProducer<T extends Queue>(queue: T, bindingName:
this: unknown,
messages: Iterable<MessageSendRequest>,
options?: QueueSendBatchOptions,
): Promise<void> {
): ReturnType<Queue['sendBatch']> {
const messageArray = Array.from(messages);
const totalBodySize = messageArray.reduce<number | undefined>((acc, m) => {
const size = getBodySize(m.body);
Expand Down
8 changes: 5 additions & 3 deletions packages/cloudflare/src/pages-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ExecutionContext } from '@cloudflare/workers-types';
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { wrapRequestHandler } from './request';
Expand Down Expand Up @@ -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());
};
}
46 changes: 35 additions & 11 deletions packages/cloudflare/src/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -70,23 +73,34 @@ class WrappedWorkflowStep implements WorkflowStep {

public async do<T extends Rpc.Serializable<T>>(
name: string,
callback: (...args: unknown[]) => Promise<T>,
callback: (ctx: WorkflowStepContext) => Promise<T>,
rollbackOptions?: WorkflowStepRollbackOptions<T>,
): Promise<T>;
public async do<T extends Rpc.Serializable<T>>(
public async do<T extends Rpc.Serializable<T>, const C extends WorkflowStepConfig>(
name: string,
config: WorkflowStepConfig,
callback: (...args: unknown[]) => Promise<T>,
config: C,
callback: (
ctx: WorkflowStepContext<C['retries'] extends { delay: infer D } ? D : WorkflowDelayDuration | number>,
) => Promise<T>,
rollbackOptions?: WorkflowStepRollbackOptions<T>,
): Promise<T>;
public async do<T extends Rpc.Serializable<T>>(
name: string,
configOrCallback: WorkflowStepConfig | (() => Promise<T>),
maybeCallback?: (...args: unknown[]) => Promise<T>,
configOrCallback: WorkflowStepConfig | ((ctx: WorkflowStepContext) => Promise<T>),
callbackOrRollback?: ((ctx: WorkflowStepContext) => Promise<T>) | WorkflowStepRollbackOptions<T>,
maybeRollback?: WorkflowStepRollbackOptions<T>,
): Promise<T> {
// 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<T>;
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<T>;
const rollbackOptions = (hasConfig ? maybeRollback : callbackOrRollback) as
| WorkflowStepRollbackOptions<T>
| undefined;

const instrumentedCallback = async (...args: unknown[]): Promise<T> => {
// Feature detection: Cloudflare Workflows (April 2026+) pass a step context
Expand All @@ -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',
Expand All @@ -118,7 +134,7 @@ class WrappedWorkflowStep implements WorkflowStep {
},
async span => {
try {
const result = await userCallback(...args);
const result = await (userCallback as (...args: unknown[]) => Promise<T>)(...args);
span.setStatus({ code: 1 });
return result;
} catch (error) {
Expand All @@ -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<void> {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading