Skip to content

fix(cloudflare): Import types explicitly from worker-types #16650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.31",
"@cloudflare/workers-types": "^4.20250521.0",
"@cloudflare/workers-types": "^4.20250619.0",
"vitest": "3.1.0",
"wrangler": "^4.16.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Hono } from 'hono';
import { DurableObject } from 'cloudflare:workers';
import * as Sentry from '@sentry/cloudflare';

const app = new Hono();
Expand All @@ -25,6 +26,19 @@ app.notFound(ctx => {
return ctx.json({ message: 'Not Found' }, 404);
});

class MyDurableObjectBase extends DurableObject<Env> {
// impl
}

// Typecheck that the instrumented durable object is valid
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env?.E2E_TEST_DSN,
tracesSampleRate: 1.0,
}),
MyDurableObjectBase,
);

export default Sentry.withSentry(
(env: Env) => ({
dsn: env?.E2E_TEST_DSN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.4.5",
"@cloudflare/workers-types": "^4.20240725.0",
"@cloudflare/workers-types": "^4.20250619.0",
"typescript": "^5.5.2",
"vitest": "1.6.1",
"wrangler": "^3.60.3"
Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
}
},
"devDependencies": {
"@cloudflare/workers-types": "4.20240725.0",
"@cloudflare/workers-types": "^4.20250619.0",
"@types/node": "^18.19.1",
"wrangler": "^3.67.1"
},
Expand Down
19 changes: 10 additions & 9 deletions packages/cloudflare/src/durableobject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/unbound-method */
import type { DurableObject, DurableObjectState, ExecutionContext, Rpc } from '@cloudflare/workers-types';
import {
captureException,
flush,
Expand All @@ -9,7 +10,6 @@ import {
withIsolationScope,
withScope,
} from '@sentry/core';
import type { DurableObject } from 'cloudflare:workers';
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { isInstrumented, markAsInstrumented } from './instrument';
Expand Down Expand Up @@ -125,26 +125,27 @@ function wrapMethodWithSentry<T extends (...args: any[]) => any>(
* }
*
* export const MyDurableObject = instrumentDurableObjectWithSentry(
* env => ({
* (env: Env) => ({
* dsn: env.SENTRY_DSN,
* tracesSampleRate: 1.0,
* }),
* MyDurableObjectBase,
* );
* ```
*/
export function instrumentDurableObjectWithSentry<
E,
T extends DurableObject<E>,
C extends new (state: DurableObjectState, env: E) => T,
>(optionsCallback: (env: E) => CloudflareOptions, DurableObjectClass: C): C {
return new Proxy(DurableObjectClass, {
export function instrumentDurableObjectWithSentry<E, C>(
optionsCallback: (env: E) => CloudflareOptions,
DurableObjectClass: C,
): C {
// We need to use `any` here because of type issues with the Durable Object constructor.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Proxy(DurableObjectClass as any, {
construct(target, [context, env]) {
setAsyncLocalStorageAsyncContextStrategy();

const options = getFinalOptions(optionsCallback(env), env);

const obj = new target(context, env);
const obj = new target(context, env) as DurableObject & Rpc.DurableObjectBranded;

// These are the methods that are available on a Durable Object
// ref: https://developers.cloudflare.com/durable-objects/api/base/
Expand Down
8 changes: 8 additions & 0 deletions packages/cloudflare/src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import type {
EmailExportedHandler,
ExportedHandler,
ExportedHandlerFetchHandler,
ExportedHandlerQueueHandler,
ExportedHandlerScheduledHandler,
ExportedHandlerTailHandler,
} from '@cloudflare/workers-types';
import {
captureException,
flush,
Expand Down
9 changes: 8 additions & 1 deletion packages/cloudflare/src/pages-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EventPluginContext, PagesPluginFunction } from '@cloudflare/workers-types';
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { wrapRequestHandler } from './request';
Expand Down Expand Up @@ -49,6 +50,12 @@ export function sentryPagesPlugin<
setAsyncLocalStorageAsyncContextStrategy();
return context => {
const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions;
return wrapRequestHandler({ options, request: context.request, context }, () => context.next());
return wrapRequestHandler(
{ options, request: context.request, context },
// Need to mark as any because of incompatibilities between Cloudflare and regular Request types
// This should still be fine because we don't expose this type externally in `sentryPagesPlugin`.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
() => context.next() as any,
) as ReturnType<PagesPluginFunction<Env, Params, Data, PluginParams>>;
};
}
32 changes: 23 additions & 9 deletions packages/cloudflare/src/request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
import type {
EventPluginContext,
ExecutionContext,
IncomingRequestCfProperties,
Request as CloudflareRequest,
} from '@cloudflare/workers-types';
import {
captureException,
continueTrace,
Expand All @@ -14,10 +19,18 @@ import type { CloudflareOptions } from './client';
import { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';
import { init } from './sdk';

interface RequestHandlerWrapperOptions {
interface RequestHandlerWrapperOptions<
Env = unknown,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Params extends string = any,
Data extends Record<string, unknown> = Record<string, unknown>,
// Although it is not ideal to use `any` here, it makes usage more flexible for different setups.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
PluginParams = any,
> {
options: CloudflareOptions;
request: Request<unknown, IncomingRequestCfProperties<unknown>>;
context: ExecutionContext;
request: Request | CloudflareRequest;
context: ExecutionContext | EventPluginContext<Env, Params, Data, PluginParams>;
}

/**
Expand All @@ -29,11 +42,12 @@ export function wrapRequestHandler(
): Promise<Response> {
return withIsolationScope(async isolationScope => {
const { options, request } = wrapperOptions;
const cloudflareRequest = request as unknown as CloudflareRequest<unknown, IncomingRequestCfProperties>;

// In certain situations, the passed context can become undefined.
// For example, for Astro while prerendering pages at build time.
// see: https://github.com/getsentry/sentry-javascript/issues/13217
const context = wrapperOptions.context as ExecutionContext | undefined;
const context = wrapperOptions.context as RequestHandlerWrapperOptions['context'] | undefined;

const client = init(options);
isolationScope.setClient(client);
Expand All @@ -50,10 +64,10 @@ export function wrapRequestHandler(

addCloudResourceContext(isolationScope);
if (request) {
addRequest(isolationScope, request);
if (request.cf) {
addCultureContext(isolationScope, request.cf);
attributes['network.protocol.name'] = request.cf.httpProtocol;
addRequest(isolationScope, cloudflareRequest);
if (cloudflareRequest.cf) {
addCultureContext(isolationScope, cloudflareRequest.cf as IncomingRequestCfProperties);
attributes['network.protocol.name'] = cloudflareRequest.cf.httpProtocol;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/cloudflare/src/scope-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IncomingRequestCfProperties } from '@cloudflare/workers-types';
import type { IncomingRequestCfProperties, Request } from '@cloudflare/workers-types';
import type { Scope } from '@sentry/core';
import { winterCGRequestToRequestData } from '@sentry/core';

Expand Down
3 changes: 1 addition & 2 deletions packages/cloudflare/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"include": ["src/**/*"],

"compilerOptions": {
"module": "esnext",
"types": ["node", "@cloudflare/workers-types"]
"module": "esnext"
}
}
8 changes: 6 additions & 2 deletions packages/sveltekit/src/worker/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
wrapRequestHandler,
} from '@sentry/cloudflare';
import { addNonEnumerableProperty } from '@sentry/core';
import type { Handle } from '@sveltejs/kit';
import type { Handle, MaybePromise } from '@sveltejs/kit';
import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration';

/**
Expand Down Expand Up @@ -34,12 +34,16 @@ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle {
return wrapRequestHandler(
{
options: opts,
// @ts-expect-error This expects a cloudflare request, but we cannot type
// it in the sveltekit worker.
request: event.request,
// @ts-expect-error This will exist in Cloudflare
context: event.platform.context,
},
() => resolve(event),
);
// We need to cast this because `wrapRequestHandler` returns a Cloudflare Response,
// which is not compatible with the regular Response type.
) as unknown as MaybePromise<Response>;
}
return resolve(event);
};
Expand Down
Loading
Loading