diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 48515847c792..62ac17cfc5d1 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -19,6 +19,14 @@ "./package.json": "./package.json", ".": { "types": "./build/types/index.types.d.ts", + "workerd": { + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js" + }, + "worker": { + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js" + }, "browser": { "import": "./build/esm/index.client.js", "require": "./build/cjs/index.client.js" @@ -28,6 +36,12 @@ "require": "./build/cjs/index.server.js" } }, + "./cloudflare": { + "types": "./build/types/cloudflare/index.d.ts", + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js", + "default": "./build/esm/cloudflare/index.js" + }, "./import": { "import": { "default": "./build/import-hook.mjs" @@ -72,7 +86,7 @@ "build:dev:watch": "yarn build:watch", "build:transpile:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts", "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts", + "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts && madge --circular src/cloudflare/index.ts", "clean": "rimraf build coverage sentry-tanstackstart-react-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", diff --git a/packages/tanstackstart-react/rollup.npm.config.mjs b/packages/tanstackstart-react/rollup.npm.config.mjs index 3c1bae9abd07..236262ed85f7 100644 --- a/packages/tanstackstart-react/rollup.npm.config.mjs +++ b/packages/tanstackstart-react/rollup.npm.config.mjs @@ -3,7 +3,13 @@ import { makeBaseNPMConfig, makeNPMConfigVariants, makeOtelLoaders } from '@sent export default [ ...makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/client/index.ts', 'src/server/index.ts'], + entrypoints: [ + 'src/index.server.ts', + 'src/index.client.ts', + 'src/client/index.ts', + 'src/server/index.ts', + 'src/cloudflare/index.ts', + ], }), ), ...makeOtelLoaders('./build', 'sentry-node'), diff --git a/packages/tanstackstart-react/src/cloudflare/index.ts b/packages/tanstackstart-react/src/cloudflare/index.ts new file mode 100644 index 000000000000..0a0df6863dbf --- /dev/null +++ b/packages/tanstackstart-react/src/cloudflare/index.ts @@ -0,0 +1,36 @@ +// import/export got a false positive, and affects most of our index barrel files +// can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703 +/* eslint-disable import/export */ +export * from '../client'; +export * from '../common'; + +export { wrapFetchWithSentry } from '../server/wrapFetchWithSentry'; +export { wrapMiddlewaresWithSentry } from '../server/middleware'; +export { sentryGlobalRequestMiddleware, sentryGlobalFunctionMiddleware } from '../server/globalMiddleware'; + +/** + * A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors + * so they should simply be a passthrough. + */ +export const ErrorBoundary = (props: React.PropsWithChildren): React.ReactNode => { + if (!props.children) { + return null; + } + + if (typeof props.children === 'function') { + return (props.children as () => React.ReactNode)(); + } + + return props.children; +}; + +/** + * A passthrough error boundary wrapper for the server that doesn't depend on any react. Error boundaries don't catch + * SSR errors so they should simply be a passthrough. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function withErrorBoundary

>( + WrappedComponent: React.ComponentType

, +): React.FC

{ + return WrappedComponent as React.FC

; +} diff --git a/packages/tanstackstart-react/src/server/middleware.ts b/packages/tanstackstart-react/src/server/middleware.ts index 9347e6760742..348fb74384c7 100644 --- a/packages/tanstackstart-react/src/server/middleware.ts +++ b/packages/tanstackstart-react/src/server/middleware.ts @@ -1,6 +1,5 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import type { Span } from '@sentry/node'; -import { getActiveSpan, startSpanManual, withActiveSpan } from '@sentry/node'; +import type { Span } from '@sentry/core'; +import { addNonEnumerableProperty, getActiveSpan, startSpanManual, withActiveSpan } from '@sentry/core'; import type { MiddlewareWrapperOptions, TanStackMiddlewareBase } from '../common/types'; import { getMiddlewareSpanOptions } from './utils'; diff --git a/packages/tanstackstart-react/src/server/utils.ts b/packages/tanstackstart-react/src/server/utils.ts index 2a94cb79deac..fcbae62c2142 100644 --- a/packages/tanstackstart-react/src/server/utils.ts +++ b/packages/tanstackstart-react/src/server/utils.ts @@ -1,5 +1,5 @@ import type { StartSpanOptions } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; /** * Extracts the SHA-256 hash from a server function pathname. diff --git a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts index 22d218ef0b48..635bc75c8779 100644 --- a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts +++ b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/node'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import { extractServerFunctionSha256 } from './utils'; export type ServerEntry = { diff --git a/packages/tanstackstart-react/test/cloudflare/index.test.ts b/packages/tanstackstart-react/test/cloudflare/index.test.ts new file mode 100644 index 000000000000..db17d528e308 --- /dev/null +++ b/packages/tanstackstart-react/test/cloudflare/index.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; +import * as CloudflareExports from '../../src/cloudflare/index'; + +describe('Cloudflare entrypoint', () => { + describe('exports', () => { + it('exports wrapMiddlewaresWithSentry as a function (server implementation)', () => { + expect(typeof CloudflareExports.wrapMiddlewaresWithSentry).toBe('function'); + + // The server implementation wraps middlewares with Sentry instrumentation. + // Verify it handles an empty object correctly (server impl returns an empty array). + const result = CloudflareExports.wrapMiddlewaresWithSentry({}); + expect(result).toEqual([]); + }); + + it('exports sentryGlobalRequestMiddleware as a middleware object with server handler', () => { + const middleware = CloudflareExports.sentryGlobalRequestMiddleware; + expect(middleware).toBeDefined(); + // The server implementation has a server handler function, unlike the client no-op stub. + expect(typeof middleware.options.server).toBe('function'); + }); + + it('exports sentryGlobalFunctionMiddleware as a middleware object with server handler', () => { + const middleware = CloudflareExports.sentryGlobalFunctionMiddleware; + expect(middleware).toBeDefined(); + // The server implementation has a server handler function, unlike the client no-op stub. + expect(typeof middleware.options.server).toBe('function'); + }); + + it('exports wrapFetchWithSentry', () => { + expect(typeof CloudflareExports.wrapFetchWithSentry).toBe('function'); + }); + }); + + describe('ErrorBoundary', () => { + it('is a passthrough that returns children directly', () => { + const children = 'test child'; + const result = CloudflareExports.ErrorBoundary({ children }); + expect(result).toBe('test child'); + }); + + it('returns null when no children are provided', () => { + const result = CloudflareExports.ErrorBoundary({}); + expect(result).toBeNull(); + }); + + it('calls children when children is a function', () => { + const children = () => 'function result'; + const result = CloudflareExports.ErrorBoundary({ children }); + expect(result).toBe('function result'); + }); + }); + + describe('withErrorBoundary', () => { + it('is a passthrough that returns the original component', () => { + const MockComponent = () => null; + const result = CloudflareExports.withErrorBoundary(MockComponent); + expect(result).toBe(MockComponent); + }); + }); +});