@sentry/cloudflare 10.69.0 → 10.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/build/cjs/durableobject.js +114 -36
  2. package/build/cjs/durableobject.js.map +1 -1
  3. package/build/cjs/flush.js.map +1 -1
  4. package/build/cjs/instrumentations/agents/index.js +2 -0
  5. package/build/cjs/instrumentations/agents/index.js.map +1 -1
  6. package/build/cjs/instrumentations/agents/instrumentAgentCallableRpc.js +2 -2
  7. package/build/cjs/instrumentations/agents/instrumentAgentCallableRpc.js.map +1 -1
  8. package/build/cjs/instrumentations/agents/instrumentAgentRequestConversation.js +19 -0
  9. package/build/cjs/instrumentations/agents/instrumentAgentRequestConversation.js.map +1 -0
  10. package/build/cjs/instrumentations/agents/instrumentChatAgentConversation.js +3 -3
  11. package/build/cjs/instrumentations/agents/instrumentChatAgentConversation.js.map +1 -1
  12. package/build/cjs/instrumentations/agents/types.js +58 -13
  13. package/build/cjs/instrumentations/agents/types.js.map +1 -1
  14. package/build/cjs/request.js +3 -2
  15. package/build/cjs/request.js.map +1 -1
  16. package/build/cjs/utils/invocationScope.js +12 -0
  17. package/build/cjs/utils/invocationScope.js.map +1 -0
  18. package/build/cjs/utils/rpcMeta.js +4 -0
  19. package/build/cjs/utils/rpcMeta.js.map +1 -1
  20. package/build/cjs/workflows.js +1 -1
  21. package/build/cjs/workflows.js.map +1 -1
  22. package/build/cjs/wrapMethodWithSentry.js +2 -3
  23. package/build/cjs/wrapMethodWithSentry.js.map +1 -1
  24. package/build/esm/durableobject.js +117 -39
  25. package/build/esm/durableobject.js.map +1 -1
  26. package/build/esm/flush.js.map +1 -1
  27. package/build/esm/instrumentations/agents/index.js +2 -0
  28. package/build/esm/instrumentations/agents/index.js.map +1 -1
  29. package/build/esm/instrumentations/agents/instrumentAgentCallableRpc.js +2 -2
  30. package/build/esm/instrumentations/agents/instrumentAgentCallableRpc.js.map +1 -1
  31. package/build/esm/instrumentations/agents/instrumentAgentRequestConversation.js +17 -0
  32. package/build/esm/instrumentations/agents/instrumentAgentRequestConversation.js.map +1 -0
  33. package/build/esm/instrumentations/agents/instrumentChatAgentConversation.js +4 -4
  34. package/build/esm/instrumentations/agents/instrumentChatAgentConversation.js.map +1 -1
  35. package/build/esm/instrumentations/agents/types.js +56 -13
  36. package/build/esm/instrumentations/agents/types.js.map +1 -1
  37. package/build/esm/package.json +1 -1
  38. package/build/esm/request.js +4 -3
  39. package/build/esm/request.js.map +1 -1
  40. package/build/esm/utils/invocationScope.js +10 -0
  41. package/build/esm/utils/invocationScope.js.map +1 -0
  42. package/build/esm/utils/rpcMeta.js +4 -1
  43. package/build/esm/utils/rpcMeta.js.map +1 -1
  44. package/build/esm/workflows.js +2 -2
  45. package/build/esm/workflows.js.map +1 -1
  46. package/build/esm/wrapMethodWithSentry.js +3 -4
  47. package/build/esm/wrapMethodWithSentry.js.map +1 -1
  48. package/build/types/durableobject.d.ts +17 -8
  49. package/build/types/durableobject.d.ts.map +1 -1
  50. package/build/types/flush.d.ts +1 -1
  51. package/build/types/flush.d.ts.map +1 -1
  52. package/build/types/instrumentations/agents/index.d.ts +4 -3
  53. package/build/types/instrumentations/agents/index.d.ts.map +1 -1
  54. package/build/types/instrumentations/agents/instrumentAgentCallableRpc.d.ts +4 -1
  55. package/build/types/instrumentations/agents/instrumentAgentCallableRpc.d.ts.map +1 -1
  56. package/build/types/instrumentations/agents/instrumentAgentRequestConversation.d.ts +16 -0
  57. package/build/types/instrumentations/agents/instrumentAgentRequestConversation.d.ts.map +1 -0
  58. package/build/types/instrumentations/agents/instrumentChatAgentConversation.d.ts +10 -9
  59. package/build/types/instrumentations/agents/instrumentChatAgentConversation.d.ts.map +1 -1
  60. package/build/types/instrumentations/agents/types.d.ts +63 -18
  61. package/build/types/instrumentations/agents/types.d.ts.map +1 -1
  62. package/build/types/request.d.ts.map +1 -1
  63. package/build/types/utils/invocationScope.d.ts +24 -0
  64. package/build/types/utils/invocationScope.d.ts.map +1 -0
  65. package/build/types/utils/rpcMeta.d.ts +8 -0
  66. package/build/types/utils/rpcMeta.d.ts.map +1 -1
  67. package/build/types/wrapMethodWithSentry.d.ts +1 -1
  68. package/build/types/wrapMethodWithSentry.d.ts.map +1 -1
  69. package/build/types-ts3.8/durableobject.d.ts +17 -8
  70. package/build/types-ts3.8/flush.d.ts +1 -1
  71. package/build/types-ts3.8/instrumentations/agents/index.d.ts +4 -3
  72. package/build/types-ts3.8/instrumentations/agents/instrumentAgentCallableRpc.d.ts +4 -1
  73. package/build/types-ts3.8/instrumentations/agents/instrumentAgentRequestConversation.d.ts +16 -0
  74. package/build/types-ts3.8/instrumentations/agents/instrumentChatAgentConversation.d.ts +10 -9
  75. package/build/types-ts3.8/instrumentations/agents/types.d.ts +63 -18
  76. package/build/types-ts3.8/utils/invocationScope.d.ts +24 -0
  77. package/build/types-ts3.8/utils/rpcMeta.d.ts +8 -0
  78. package/build/types-ts3.8/wrapMethodWithSentry.d.ts +1 -1
  79. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../../../src/instrumentations/agents/types.ts"],"sourcesContent":["import { getCurrentScope } from '@sentry/core';\n\nexport const AGENT_SPAN_ORIGIN = 'auto.faas.cloudflare.agents';\nexport const AGENT_CLASS_ATTRIBUTE = 'cloudflare.agent.class';\nexport const AGENT_NAME_ATTRIBUTE = 'cloudflare.agent.name';\n\n/**\n * The subset of the `agents` `Agent` instance internals that we instrument. These are runtime\n * implementation details of the `agents` package (v0.13.x) rather than part of its public type\n * surface, so every access is guarded and wrapping degrades gracefully if a name changes upstream.\n */\nexport interface AgentInternals {\n /** Central choke-point through which all `agents:*` observability events are published. */\n _emit?: (type: string, payload?: Record<string, unknown>) => void;\n /** WebSocket message handler; dispatches `@callable()` RPC requests. */\n onMessage?: (...args: unknown[]) => unknown;\n /**\n * Chat-turn handler. Only `AIChatAgent` (from `@cloudflare/ai-chat`) defines this; a plain `Agent`\n * does not, so its presence discriminates a chat agent.\n */\n onChatMessage?: (...args: unknown[]) => unknown;\n /** The user's Agent class (used by the SDK for the observability event `agent` field). */\n _ParentClass?: { name?: string };\n /** The Agent instance name, which in the Agents model identifies the conversation/thread. */\n name?: string;\n /**\n * Internal: the active conversation id, rotated when the chat is cleared (the `message:clear`\n * observability event) so a reset chat groups as a fresh conversation while the instance (and its\n * MCP/OAuth state) stays put. Set by `instrumentChatAgentConversation`; falls back to `name`\n * before the first clear.\n */\n __sentryConversationId?: string;\n}\n\n/** Reads best-effort agent identity attributes from the instance, tolerating missing internals. */\nexport function getAgentAttributes(instance: AgentInternals): Record<string, string> {\n const attributes: Record<string, string> = {};\n\n const agentClass = instance._ParentClass?.name;\n if (typeof agentClass === 'string' && agentClass) {\n attributes[AGENT_CLASS_ATTRIBUTE] = agentClass;\n }\n\n const agentName = instance.name;\n if (typeof agentName === 'string' && agentName) {\n attributes[AGENT_NAME_ATTRIBUTE] = agentName;\n }\n\n return attributes;\n}\n\n/**\n * Sets the agent instance's conversation id on the current scope for the duration of the\n * surrounding unit of work (chat turn, callable RPC call). In the Agents model one instance is one\n * conversation, so the instance `name` is the natural conversation id — for chat and plain agents\n * alike, since plain agents run LLM calls too (e.g. inside `@callable()` methods). Once the chat\n * has been cleared, the rotated `__sentryConversationId` takes precedence so LLM calls from any\n * unit of work group under the fresh conversation.\n *\n * `conversationIdIntegration` reads the id off the scope at `spanStart` and stamps\n * `gen_ai.conversation.id` onto AI spans created within the unit of work, correlating its model\n * and tool calls. Callers run inside a per-event forked scope (`wrapMethodWithSentry`), so the id\n * does not leak into unrelated events.\n */\nexport function setAgentConversationId(instance: AgentInternals): void {\n const conversationId = instance.__sentryConversationId ?? instance.name;\n\n if (typeof conversationId === 'string' && conversationId) {\n getCurrentScope().setConversationId(conversationId);\n }\n}\n"],"names":["getCurrentScope"],"mappings":";;;;AAEO,MAAM,iBAAA,GAAoB;AAC1B,MAAM,qBAAA,GAAwB;AAC9B,MAAM,oBAAA,GAAuB;AA+B7B,SAAS,mBAAmB,QAAA,EAAkD;AACnF,EAAA,MAAM,aAAqC,EAAC;AAE5C,EAAA,MAAM,UAAA,GAAa,SAAS,YAAA,EAAc,IAAA;AAC1C,EAAA,IAAI,OAAO,UAAA,KAAe,QAAA,IAAY,UAAA,EAAY;AAChD,IAAA,UAAA,CAAW,qBAAqB,CAAA,GAAI,UAAA;AAAA,EACtC;AAEA,EAAA,MAAM,YAAY,QAAA,CAAS,IAAA;AAC3B,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,EAAW;AAC9C,IAAA,UAAA,CAAW,oBAAoB,CAAA,GAAI,SAAA;AAAA,EACrC;AAEA,EAAA,OAAO,UAAA;AACT;AAeO,SAAS,uBAAuB,QAAA,EAAgC;AACrE,EAAA,MAAM,cAAA,GAAiB,QAAA,CAAS,sBAAA,IAA0B,QAAA,CAAS,IAAA;AAEnE,EAAA,IAAI,OAAO,cAAA,KAAmB,QAAA,IAAY,cAAA,EAAgB;AACxD,IAAAA,oBAAA,EAAgB,CAAE,kBAAkB,cAAc,CAAA;AAAA,EACpD;AACF;;;;;;;;"}
1
+ {"version":3,"file":"types.js","sources":["../../../../src/instrumentations/agents/types.ts"],"sourcesContent":["import type { DurableObjectStorage } from '@cloudflare/workers-types';\nimport { debug, getCurrentScope, getIsolationScope, uuid4 } from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport type { InstrumentedDurableObjectState } from '../../wrapMethodWithSentry';\n\n// v10 predates `@sentry/conventions` and doesn't expose this from `@sentry/core`, so keep the\n// standard semantic attribute name locally.\nconst GEN_AI_AGENT_NAME_ATTRIBUTE = 'gen_ai.agent.name';\n\nexport const AGENT_SPAN_ORIGIN = 'auto.faas.cloudflare.agents';\n\n/** DO storage key under which the conversation id is persisted so it survives hibernation. */\nexport const AGENT_CONVERSATION_ID_STORAGE_KEY = '__SENTRY_AGENT_CONVERSATION_ID__';\n\n/**\n * Instance keys for our conversation-id bookkeeping, keyed by symbol so the state stays invisible\n * to anything enumerating the user-owned agent instance (`Object.keys`, `JSON.stringify`, spread).\n * Exported because the exported `AgentInternals` interface references them.\n */\nexport const AGENT_CONVERSATION_ID_SYMBOL: unique symbol = Symbol('sentryAgentConversationId');\nexport const AGENT_APPLIED_CONVERSATION_ID_SYMBOL: unique symbol = Symbol('sentryAgentAppliedConversationId');\n\n/**\n * The subset of the `agents` `Agent` instance internals that we instrument. These are runtime\n * implementation details of the `agents` package (v0.13.x) rather than part of its public type\n * surface, so every access is guarded and wrapping degrades gracefully if a name changes upstream.\n */\nexport interface AgentInternals {\n /** Central choke-point through which all `agents:*` observability events are published. */\n _emit?: (type: string, payload?: Record<string, unknown>) => void;\n /** WebSocket message handler; dispatches `@callable()` RPC requests. */\n onMessage?: (...args: unknown[]) => unknown;\n /**\n * Chat-turn handler. Only `AIChatAgent` (from `@cloudflare/ai-chat`) defines this; a plain `Agent`\n * does not, so its presence discriminates a chat agent.\n */\n onChatMessage?: (...args: unknown[]) => unknown;\n /** HTTP request handler; the router sends every non-WebSocket request here. */\n onRequest?: (...args: unknown[]) => unknown;\n /** The user's Agent class (used by the SDK for the observability event `agent` field). */\n _ParentClass?: { name?: string };\n /** The Agent instance name, reported as the `cloudflare.agent.name` span attribute. */\n name?: string;\n /**\n * The Durable Object state of the instance. Present on every real Agent; guarded like the other\n * internals so partially-mocked instances keep working.\n */\n ctx?: InstrumentedDurableObjectState;\n /**\n * The current conversation id for this agent instance. It is cached in memory after being loaded\n * from Durable Object storage and updated when the conversation rotates. `undefined` means storage\n * has not been read yet.\n */\n [AGENT_CONVERSATION_ID_SYMBOL]?: string;\n /**\n * The conversation id this instrumentation most recently applied to the isolation scope. It lets\n * subsequent units of work distinguish an SDK-applied id, which may be replaced after rotation,\n * from an id explicitly set by the user, which must be preserved.\n */\n [AGENT_APPLIED_CONVERSATION_ID_SYMBOL]?: string;\n}\n\n/** Reads best-effort agent identity attributes from the instance, tolerating missing internals. */\nexport function getAgentAttributes(instance: AgentInternals): Record<string, string> {\n const attributes: Record<string, string> = {};\n\n const agentClass = instance._ParentClass?.name;\n if (typeof agentClass === 'string' && agentClass) {\n attributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentClass;\n }\n\n return attributes;\n}\n\n/**\n * Persists the conversation id to Durable Object storage, so it survives hibernation, and updates\n * the in-memory cache synchronously so the current wake is immediately consistent even if the\n * write fails. Uses the async storage API, which exists on KV- and SQLite-backed DOs alike.\n * The write is fire-and-forget: DO storage writes are tracked by the runtime and land without\n * awaiting (`waitUntil` is a no-op in Durable Objects), and the catch keeps a rejection from\n * surfacing as unhandled — a failed write just means the next wake starts a new conversation,\n * which must never throw into user code. Uses the original uninstrumented storage so internal\n * bookkeeping doesn't create spans.\n */\nexport function storeAgentConversationId(instance: AgentInternals, conversationId: string): void {\n instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;\n\n const storage = resolveStorage(instance);\n if (!storage) {\n return;\n }\n\n try {\n storage\n .put(AGENT_CONVERSATION_ID_STORAGE_KEY, conversationId)\n .catch(error => DEBUG_BUILD && debug.log('[Sentry] Failed to persist agent conversation id', error));\n } catch (error) {\n DEBUG_BUILD && debug.log('[Sentry] Failed to persist agent conversation id', error);\n }\n}\n\n/**\n * Sets the agent instance's conversation id on the active scope for the duration of the\n * surrounding unit of work (chat turn, callable RPC call, HTTP request). In the Agents model one\n * instance is one long-lived conversation, so the id is minted once per instance and persisted to\n * DO storage — for chat and plain agents alike, since plain agents run LLM calls too (e.g. inside\n * `@callable()` methods). The instance `name` is deliberately not used: it is caller-chosen and can\n * be a stable, guessable, or shared value, whereas a conversation id should identify exactly one\n * conversation. Clearing the chat rotates it (see `storeAgentConversationId`) so subsequent LLM\n * calls group under the fresh conversation.\n *\n * Every handler wrapper awaits this before invoking the original, so the id is on the scope before\n * the unit of work — and any `gen_ai` span it creates — starts. Only the first unit of work per wake\n * pays the storage read; the rest resolve from the in-memory cache. Awaiting is safe on all three\n * paths because the `agents` `Agent` constructor already replaces `onMessage` and `onRequest` with\n * `async` wrappers of its own, and `onChatMessage` is async by contract, so every caller upstream\n * already handles a promise.\n *\n * An id the user set explicitly outranks this inferred one, in either order: a `setConversationId()`\n * call that already happened is detected here and left alone, and one made inside the handler lands\n * on the same scope afterwards and therefore wins. That is why the write targets the isolation scope\n * — it is the scope the public `Sentry.setConversationId()` writes to, and `conversationIdIntegration`\n * prefers the current scope over it, so writing there would make a user's call unoverridable.\n *\n * `conversationIdIntegration` reads the id off the scope at `spanStart` and stamps\n * `gen_ai.conversation.id` onto AI spans created within the unit of work, correlating its model and\n * tool calls.\n */\nexport async function setAgentConversationId(instance: AgentInternals): Promise<void> {\n const isolationScope = getIsolationScope();\n const existing = isolationScope.getScopeData().conversationId ?? getCurrentScope().getScopeData().conversationId;\n\n // An id that isn't the one we put there ourselves came from the user — never override it.\n if (existing && existing !== instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL]) {\n return;\n }\n\n const conversationId = instance[AGENT_CONVERSATION_ID_SYMBOL] ?? (await loadAgentConversationId(instance));\n\n instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL] = conversationId;\n isolationScope.setConversationId(conversationId);\n}\n\n/**\n * Reads the persisted id into the instance cache, minting and persisting a fresh one when storage\n * holds none, so subsequent units of work resolve from memory. The async `get` exists on KV- and\n * SQLite-backed DOs alike, so this one path covers both.\n *\n * Anything already cached by the time the read lands wins over the read: that covers a rotation\n * (cleared chat) racing the read, and a second concurrent unit of work that must not mint and\n * persist a competing id for the same conversation.\n */\nasync function loadAgentConversationId(instance: AgentInternals): Promise<string> {\n let stored: unknown;\n let readFailed = false;\n\n try {\n stored = await resolveStorage(instance)?.get<string>(AGENT_CONVERSATION_ID_STORAGE_KEY);\n } catch (error) {\n readFailed = true;\n DEBUG_BUILD && debug.log('[Sentry] Failed to read agent conversation id from storage', error);\n }\n\n const cached = instance[AGENT_CONVERSATION_ID_SYMBOL];\n\n if (cached !== undefined) {\n return cached;\n }\n\n if (typeof stored === 'string' && stored) {\n instance[AGENT_CONVERSATION_ID_SYMBOL] = stored;\n\n return stored;\n }\n\n const conversationId = uuid4();\n\n if (readFailed) {\n // Cache without persisting: a read that failed may still have an id behind it, and overwriting\n // it would split the conversation for good instead of just for this wake.\n instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;\n } else {\n storeAgentConversationId(instance, conversationId);\n }\n\n return conversationId;\n}\n\n/**\n * Resolves the uninstrumented DO storage for internal reads/writes, so they don't create spans of\n * their own. Falls back to the instance's regular storage when the uninstrumented one isn't\n * exposed (e.g. when `instrumentCloudflareAgent` is used directly, without `instrumentContext`).\n */\nfunction resolveStorage(instance: AgentInternals): DurableObjectStorage | undefined {\n return instance.ctx?.originalStorage ?? instance.ctx?.storage;\n}\n"],"names":["DEBUG_BUILD","debug","getIsolationScope","getCurrentScope","uuid4"],"mappings":"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"}
@@ -5,16 +5,17 @@ const httpServer = require('./integrations/httpServer.js');
5
5
  const flush = require('./flush.js');
6
6
  const scopeUtils = require('./scope-utils.js');
7
7
  const sdk = require('./sdk.js');
8
+ const invocationScope = require('./utils/invocationScope.js');
8
9
  const streaming = require('./utils/streaming.js');
9
10
 
10
11
  function getRequestErrorMechanismType(context) {
11
12
  return context && "storage" in context ? "auto.faas.cloudflare.durable_object" : "auto.http.cloudflare";
12
13
  }
13
14
  function wrapRequestHandler(wrapperOptions, handler) {
14
- return core.withIsolationScope(async (isolationScope) => {
15
+ return invocationScope.withInvocationIsolationScope(async (isolationScope) => {
15
16
  const { options, request, captureErrors = true } = wrapperOptions;
16
17
  const context = wrapperOptions.context;
17
- const waitUntil = context ? flush.getOriginalWaitUntil(context)?.bind(context) : void 0;
18
+ const waitUntil = context ? flush.getOriginalWaitUntil(context).bind(context) : void 0;
18
19
  const errorMechanismType = getRequestErrorMechanismType(context);
19
20
  const client = sdk.init({ ...options, ctx: context });
20
21
  isolationScope.setClient(client);
@@ -1 +1 @@
1
- {"version":3,"file":"request.js","sources":["../../src/request.ts"],"sourcesContent":["import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types';\nimport {\n captureException,\n continueTrace,\n getClient,\n getHttpSpanDetailsFromUrlObject,\n httpHeadersToSpanAttributes,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n setHttpStatus,\n startSpanManual,\n winterCGHeadersToDict,\n withIsolationScope,\n} from '@sentry/core';\nimport { captureIncomingRequestBody } from './integrations/httpServer';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';\nimport { init } from './sdk';\nimport { classifyResponseStreaming } from './utils/streaming';\n\nfunction getRequestErrorMechanismType(context: ExecutionContextCompat | undefined): string {\n // Durable Object fetch handlers use DO state as context (see instrumentDurableObjectWithSentry)\n return context && 'storage' in context ? 'auto.faas.cloudflare.durable_object' : 'auto.http.cloudflare';\n}\n\ninterface RequestHandlerWrapperOptions {\n options: CloudflareOptions;\n request: Request<unknown, IncomingRequestCfProperties<unknown> | CfProperties<unknown>>;\n context: ExecutionContextCompat | undefined;\n /**\n * If true, errors will be captured, rethrown and sent to Sentry.\n * Otherwise, errors are rethrown but not captured.\n *\n * You most likely don't want to set this to `false`, if you use `wrapRequestHandler` directly.\n * This is primarily meant as an escape hatch for higher-level SDKs relying on additional error\n * capturing mechanisms where this wrapper captures errors too early or too generally.\n *\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Wraps a cloudflare request handler in Sentry instrumentation\n */\nexport function wrapRequestHandler(\n wrapperOptions: RequestHandlerWrapperOptions,\n handler: (...args: unknown[]) => Response | Promise<Response>,\n): Promise<Response> {\n return withIsolationScope(async isolationScope => {\n const { options, request, captureErrors = true } = wrapperOptions;\n const context = wrapperOptions.context;\n\n // Use getOriginalWaitUntil to get the un-instrumented waitUntil function.\n // This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil\n // to track pending tasks. If we use the instrumented version for flushAndDispose,\n // it acquires the lock, then flushAndDispose tries to wait for the same lock,\n // creating a deadlock.\n const waitUntil = context ? getOriginalWaitUntil(context)?.bind(context) : undefined;\n const errorMechanismType = getRequestErrorMechanismType(context);\n\n const client = init({ ...options, ctx: context });\n isolationScope.setClient(client);\n\n const urlObject = parseStringToURLObject(request.url);\n const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);\n\n const contentLength = request.headers.get('content-length');\n if (contentLength) {\n attributes['http.request.body.size'] = parseInt(contentLength, 10);\n }\n\n const userAgentHeader = request.headers.get('user-agent');\n if (userAgentHeader) {\n attributes['user_agent.original'] = userAgentHeader;\n }\n\n Object.assign(\n attributes,\n httpHeadersToSpanAttributes(\n winterCGHeadersToDict(request.headers),\n getClient()?.getDataCollectionOptions() ?? false,\n ),\n );\n\n attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';\n\n addCloudResourceContext(isolationScope);\n addRequest(isolationScope, request);\n if (request.cf) {\n addCultureContext(isolationScope, request.cf);\n\n if (typeof request.cf.httpProtocol === 'string') {\n attributes['network.protocol.name'] = request.cf.httpProtocol;\n }\n }\n\n // Do not capture spans for OPTIONS and HEAD requests\n if (request.method === 'OPTIONS' || request.method === 'HEAD') {\n try {\n return await handler();\n } catch (e) {\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n throw e;\n } finally {\n waitUntil?.(flushAndDispose(client));\n }\n }\n\n if (client) {\n await captureIncomingRequestBody(client, request);\n }\n\n return continueTrace(\n { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') },\n () => {\n // Note: This span will not have a duration unless I/O happens in the handler. This is\n // because of how the cloudflare workers runtime works.\n // See: https://developers.cloudflare.com/workers/runtime-apis/performance/\n\n // Use startSpanManual to control when span ends (needed for streaming responses)\n return startSpanManual({ name, attributes }, async span => {\n let res: Response;\n\n try {\n res = await handler();\n setHttpStatus(span, res.status);\n\n // After the handler runs, the span name might have been updated by nested instrumentation\n // (e.g., Remix parameterizing routes). The span should already have the correct name\n // from that instrumentation, so we don't need to do anything here.\n } catch (e) {\n span.end();\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n waitUntil?.(flushAndDispose(client));\n throw e;\n }\n\n // Classify response to detect actual streaming\n const classification = classifyResponseStreaming(res);\n\n if (classification.isStreaming && res.body) {\n try {\n let ended = false;\n\n const endSpanOnce = (): void => {\n if (ended) return;\n\n ended = true;\n span.end();\n waitUntil?.(flushAndDispose(client));\n };\n\n const transform = new TransformStream({\n flush() {\n // Source stream completed normally.\n endSpanOnce();\n },\n cancel() {\n // Client disconnected (or downstream cancelled). The `cancel`\n // is being called while the response is still considered\n // active, so this is a safe place to end the span.\n endSpanOnce();\n },\n });\n\n return new Response(res.body.pipeThrough(transform), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n } catch {\n span.end();\n waitUntil?.(flushAndDispose(client));\n return res;\n }\n }\n\n // Non-streaming response - end span immediately and return original\n span.end();\n\n // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive.\n // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers\n // will still be called and may need the client to capture errors.\n if (res.status === 101) {\n waitUntil?.(client?.flush(2000));\n } else {\n waitUntil?.(flushAndDispose(client));\n }\n return res;\n });\n },\n );\n });\n}\n"],"names":["withIsolationScope","getOriginalWaitUntil","init","parseStringToURLObject","getHttpSpanDetailsFromUrlObject","httpHeadersToSpanAttributes","winterCGHeadersToDict","getClient","SEMANTIC_ATTRIBUTE_SENTRY_OP","addCloudResourceContext","addRequest","addCultureContext","captureException","flushAndDispose","captureIncomingRequestBody","continueTrace","startSpanManual","setHttpStatus","classifyResponseStreaming"],"mappings":"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"}
1
+ {"version":3,"file":"request.js","sources":["../../src/request.ts"],"sourcesContent":["import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types';\nimport {\n captureException,\n continueTrace,\n getClient,\n getHttpSpanDetailsFromUrlObject,\n httpHeadersToSpanAttributes,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n setHttpStatus,\n startSpanManual,\n winterCGHeadersToDict,\n} from '@sentry/core';\nimport { captureIncomingRequestBody } from './integrations/httpServer';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';\nimport { init } from './sdk';\nimport { withInvocationIsolationScope } from './utils/invocationScope';\nimport { classifyResponseStreaming } from './utils/streaming';\n\nfunction getRequestErrorMechanismType(context: ExecutionContextCompat | undefined): string {\n // Durable Object fetch handlers use DO state as context (see instrumentDurableObjectWithSentry)\n return context && 'storage' in context ? 'auto.faas.cloudflare.durable_object' : 'auto.http.cloudflare';\n}\n\ninterface RequestHandlerWrapperOptions {\n options: CloudflareOptions;\n request: Request<unknown, IncomingRequestCfProperties<unknown> | CfProperties<unknown>>;\n context: ExecutionContextCompat | undefined;\n /**\n * If true, errors will be captured, rethrown and sent to Sentry.\n * Otherwise, errors are rethrown but not captured.\n *\n * You most likely don't want to set this to `false`, if you use `wrapRequestHandler` directly.\n * This is primarily meant as an escape hatch for higher-level SDKs relying on additional error\n * capturing mechanisms where this wrapper captures errors too early or too generally.\n *\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Wraps a cloudflare request handler in Sentry instrumentation\n */\nexport function wrapRequestHandler(\n wrapperOptions: RequestHandlerWrapperOptions,\n handler: (...args: unknown[]) => Response | Promise<Response>,\n): Promise<Response> {\n return withInvocationIsolationScope(async isolationScope => {\n const { options, request, captureErrors = true } = wrapperOptions;\n const context = wrapperOptions.context;\n\n // Use getOriginalWaitUntil to get the un-instrumented waitUntil function.\n // This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil\n // to track pending tasks. If we use the instrumented version for flushAndDispose,\n // it acquires the lock, then flushAndDispose tries to wait for the same lock,\n // creating a deadlock.\n const waitUntil = context ? getOriginalWaitUntil(context).bind(context) : undefined;\n const errorMechanismType = getRequestErrorMechanismType(context);\n\n const client = init({ ...options, ctx: context });\n isolationScope.setClient(client);\n\n const urlObject = parseStringToURLObject(request.url);\n const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);\n\n const contentLength = request.headers.get('content-length');\n if (contentLength) {\n attributes['http.request.body.size'] = parseInt(contentLength, 10);\n }\n\n const userAgentHeader = request.headers.get('user-agent');\n if (userAgentHeader) {\n attributes['user_agent.original'] = userAgentHeader;\n }\n\n Object.assign(\n attributes,\n httpHeadersToSpanAttributes(\n winterCGHeadersToDict(request.headers),\n getClient()?.getDataCollectionOptions() ?? false,\n ),\n );\n\n attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';\n\n addCloudResourceContext(isolationScope);\n addRequest(isolationScope, request);\n if (request.cf) {\n addCultureContext(isolationScope, request.cf);\n\n if (typeof request.cf.httpProtocol === 'string') {\n attributes['network.protocol.name'] = request.cf.httpProtocol;\n }\n }\n\n // Do not capture spans for OPTIONS and HEAD requests\n if (request.method === 'OPTIONS' || request.method === 'HEAD') {\n try {\n return await handler();\n } catch (e) {\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n throw e;\n } finally {\n waitUntil?.(flushAndDispose(client));\n }\n }\n\n if (client) {\n await captureIncomingRequestBody(client, request);\n }\n\n return continueTrace(\n { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') },\n () => {\n // Note: This span will not have a duration unless I/O happens in the handler. This is\n // because of how the cloudflare workers runtime works.\n // See: https://developers.cloudflare.com/workers/runtime-apis/performance/\n\n // Use startSpanManual to control when span ends (needed for streaming responses)\n return startSpanManual({ name, attributes }, async span => {\n let res: Response;\n\n try {\n res = await handler();\n setHttpStatus(span, res.status);\n\n // After the handler runs, the span name might have been updated by nested instrumentation\n // (e.g., Remix parameterizing routes). The span should already have the correct name\n // from that instrumentation, so we don't need to do anything here.\n } catch (e) {\n span.end();\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n waitUntil?.(flushAndDispose(client));\n throw e;\n }\n\n // Classify response to detect actual streaming\n const classification = classifyResponseStreaming(res);\n\n if (classification.isStreaming && res.body) {\n try {\n let ended = false;\n\n const endSpanOnce = (): void => {\n if (ended) return;\n\n ended = true;\n span.end();\n waitUntil?.(flushAndDispose(client));\n };\n\n const transform = new TransformStream({\n flush() {\n // Source stream completed normally.\n endSpanOnce();\n },\n cancel() {\n // Client disconnected (or downstream cancelled). The `cancel`\n // is being called while the response is still considered\n // active, so this is a safe place to end the span.\n endSpanOnce();\n },\n });\n\n return new Response(res.body.pipeThrough(transform), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n } catch {\n span.end();\n waitUntil?.(flushAndDispose(client));\n return res;\n }\n }\n\n // Non-streaming response - end span immediately and return original\n span.end();\n\n // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive.\n // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers\n // will still be called and may need the client to capture errors.\n if (res.status === 101) {\n waitUntil?.(client?.flush(2000));\n } else {\n waitUntil?.(flushAndDispose(client));\n }\n return res;\n });\n },\n );\n });\n}\n"],"names":["withInvocationIsolationScope","getOriginalWaitUntil","init","parseStringToURLObject","getHttpSpanDetailsFromUrlObject","httpHeadersToSpanAttributes","winterCGHeadersToDict","getClient","SEMANTIC_ATTRIBUTE_SENTRY_OP","addCloudResourceContext","addRequest","addCultureContext","captureException","flushAndDispose","captureIncomingRequestBody","continueTrace","startSpanManual","setHttpStatus","classifyResponseStreaming"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const core = require('@sentry/core');
4
+
5
+ function withInvocationIsolationScope(callback) {
6
+ const isolationScope = core.getIsolationScope();
7
+ const newIsolationScope = isolationScope === core.getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;
8
+ return core.withIsolationScope(newIsolationScope, () => callback(newIsolationScope));
9
+ }
10
+
11
+ exports.withInvocationIsolationScope = withInvocationIsolationScope;
12
+ //# sourceMappingURL=invocationScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocationScope.js","sources":["../../../src/utils/invocationScope.ts"],"sourcesContent":["import { getDefaultIsolationScope, getIsolationScope, type Scope, withIsolationScope } from '@sentry/core';\n\n/**\n * Runs `callback` on the isolation scope for the current invocation.\n *\n * An instrumented handler is either the entry point of an invocation or reentrant — reached from\n * another instrumented handler already serving the same invocation (a Durable Object method calling\n * its own `fetch`, an RPC method reaching a sibling method). Only the entry point may fork:\n *\n * - Forking at the entry point is mandatory. `setUser`/`setTag` write to the isolation scope, and a\n * Durable Object's isolation scope outlives the invocation that touched it, so without a fork one\n * invocation's user and tags reappear on the next invocation's events in the same isolate.\n * Forking clones, so request data set by an enclosing wrapper is still inherited.\n * - Forking again when reentrant would be wrong. Everything below the entry point is one logical\n * unit of work: a nested call must see what the caller set and be able to add to it, the way it\n * would if the SDK were not wrapping it at all.\n *\n * The AsyncLocalStorage strategy hands the default isolation scope back whenever no invocation is in\n * flight, and a forked one while inside `withIsolationScope`. Reference-comparing against the default\n * is therefore enough to tell the two cases apart. The stack fallback does not fork, so it reports the\n * default scope even inside an invocation; there the fork degrades to a no-op, which the stack strategy\n * tolerates. This matches the approach used by `patchEventHandler` in Nuxt.\n */\nexport function withInvocationIsolationScope<T>(callback: (scope: Scope) => T): T {\n const isolationScope = getIsolationScope();\n\n const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;\n\n return withIsolationScope(newIsolationScope, () => callback(newIsolationScope));\n}\n"],"names":["getIsolationScope","getDefaultIsolationScope","withIsolationScope"],"mappings":";;;;AAuBO,SAAS,6BAAgC,QAAA,EAAkC;AAChF,EAAA,MAAM,iBAAiBA,sBAAA,EAAkB;AAEzC,EAAA,MAAM,oBAAoB,cAAA,KAAmBC,6BAAA,EAAyB,GAAI,cAAA,CAAe,OAAM,GAAI,cAAA;AAEnG,EAAA,OAAOC,uBAAA,CAAmB,iBAAA,EAAmB,MAAM,QAAA,CAAS,iBAAiB,CAAC,CAAA;AAChF;;;;"}
@@ -16,6 +16,9 @@ function appendRpcMeta(args) {
16
16
  }
17
17
  return [...args, { [SENTRY_RPC_META_KEY]: traceData }];
18
18
  }
19
+ function hasRpcMeta(args) {
20
+ return args.length > 0 && isSentryRpcMeta(args[args.length - 1]);
21
+ }
19
22
  function extractRpcMeta(args) {
20
23
  if (args.length === 0) {
21
24
  return { args };
@@ -32,4 +35,5 @@ function extractRpcMeta(args) {
32
35
 
33
36
  exports.appendRpcMeta = appendRpcMeta;
34
37
  exports.extractRpcMeta = extractRpcMeta;
38
+ exports.hasRpcMeta = hasRpcMeta;
35
39
  //# sourceMappingURL=rpcMeta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpcMeta.js","sources":["../../../src/utils/rpcMeta.ts"],"sourcesContent":["import { getTraceData, isObjectLike, type SerializedTraceData } from '@sentry/core';\n\n/**\n * Key used to identify Sentry RPC metadata in a trailing argument.\n * This enables transparent trace propagation across Cloudflare Workers RPC\n * calls (Cap'n Proto), which have no native header/metadata support.\n */\nconst SENTRY_RPC_META_KEY = '__sentry_rpc_meta__';\n\ninterface SentryRpcMeta {\n __sentry_rpc_meta__: SerializedTraceData;\n}\n\nfunction isSentryRpcMeta(value: unknown): value is SentryRpcMeta {\n if (!isObjectLike(value) || !(SENTRY_RPC_META_KEY in value)) {\n return false;\n }\n return isObjectLike(value[SENTRY_RPC_META_KEY]);\n}\n\n/**\n * Appends Sentry RPC metadata to an args array for trace propagation.\n * If no active trace exists, returns the original args unchanged.\n */\nexport function appendRpcMeta(args: unknown[]): unknown[] {\n const traceData = getTraceData();\n\n if (!traceData['sentry-trace']) {\n return args;\n }\n\n return [...args, { [SENTRY_RPC_META_KEY]: traceData }];\n}\n\n/**\n * Extracts Sentry RPC metadata from the trailing argument of an args array.\n * Returns cleaned args (without meta) and the extracted trace data if found.\n */\nexport function extractRpcMeta<T extends unknown[]>(\n args: T,\n): {\n args: T;\n rpcMeta?: SerializedTraceData;\n} {\n if (args.length === 0) {\n return { args };\n }\n\n const last = args[args.length - 1];\n if (isSentryRpcMeta(last)) {\n return {\n args: args.slice(0, -1) as T,\n rpcMeta: last.__sentry_rpc_meta__,\n };\n }\n\n return { args };\n}\n"],"names":["isObjectLike","getTraceData"],"mappings":";;;;AAOA,MAAM,mBAAA,GAAsB,qBAAA;AAM5B,SAAS,gBAAgB,KAAA,EAAwC;AAC/D,EAAA,IAAI,CAACA,iBAAA,CAAa,KAAK,CAAA,IAAK,EAAE,uBAAuB,KAAA,CAAA,EAAQ;AAC3D,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAOA,iBAAA,CAAa,KAAA,CAAM,mBAAmB,CAAC,CAAA;AAChD;AAMO,SAAS,cAAc,IAAA,EAA4B;AACxD,EAAA,MAAM,YAAYC,iBAAA,EAAa;AAE/B,EAAA,IAAI,CAAC,SAAA,CAAU,cAAc,CAAA,EAAG;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,CAAC,GAAG,IAAA,EAAM,EAAE,CAAC,mBAAmB,GAAG,WAAW,CAAA;AACvD;AAMO,SAAS,eACd,IAAA,EAIA;AACA,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,EAAE,IAAA,EAAK;AAAA,EAChB;AAEA,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AACjC,EAAA,IAAI,eAAA,CAAgB,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,MACtB,SAAS,IAAA,CAAK;AAAA,KAChB;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAA,EAAK;AAChB;;;;;"}
1
+ {"version":3,"file":"rpcMeta.js","sources":["../../../src/utils/rpcMeta.ts"],"sourcesContent":["import { getTraceData, isObjectLike, type SerializedTraceData } from '@sentry/core';\n\n/**\n * Key used to identify Sentry RPC metadata in a trailing argument.\n * This enables transparent trace propagation across Cloudflare Workers RPC\n * calls (Cap'n Proto), which have no native header/metadata support.\n */\nconst SENTRY_RPC_META_KEY = '__sentry_rpc_meta__';\n\ninterface SentryRpcMeta {\n __sentry_rpc_meta__: SerializedTraceData;\n}\n\nfunction isSentryRpcMeta(value: unknown): value is SentryRpcMeta {\n if (!isObjectLike(value) || !(SENTRY_RPC_META_KEY in value)) {\n return false;\n }\n return isObjectLike(value[SENTRY_RPC_META_KEY]);\n}\n\n/**\n * Appends Sentry RPC metadata to an args array for trace propagation.\n * If no active trace exists, returns the original args unchanged.\n */\nexport function appendRpcMeta(args: unknown[]): unknown[] {\n const traceData = getTraceData();\n\n if (!traceData['sentry-trace']) {\n return args;\n }\n\n return [...args, { [SENTRY_RPC_META_KEY]: traceData }];\n}\n\n/**\n * Whether the trailing argument carries Sentry RPC metadata.\n *\n * Separate from {@link extractRpcMeta} because the RPC method wrappers run this check on every\n * call — including the instance's own internal method calls, which never carry metadata — and\n * must not allocate on that path.\n */\nexport function hasRpcMeta(args: unknown[]): boolean {\n return args.length > 0 && isSentryRpcMeta(args[args.length - 1]);\n}\n\n/**\n * Extracts Sentry RPC metadata from the trailing argument of an args array.\n * Returns cleaned args (without meta) and the extracted trace data if found.\n */\nexport function extractRpcMeta<T extends unknown[]>(\n args: T,\n): {\n args: T;\n rpcMeta?: SerializedTraceData;\n} {\n if (args.length === 0) {\n return { args };\n }\n\n const last = args[args.length - 1];\n if (isSentryRpcMeta(last)) {\n return {\n args: args.slice(0, -1) as T,\n rpcMeta: last.__sentry_rpc_meta__,\n };\n }\n\n return { args };\n}\n"],"names":["isObjectLike","getTraceData"],"mappings":";;;;AAOA,MAAM,mBAAA,GAAsB,qBAAA;AAM5B,SAAS,gBAAgB,KAAA,EAAwC;AAC/D,EAAA,IAAI,CAACA,iBAAA,CAAa,KAAK,CAAA,IAAK,EAAE,uBAAuB,KAAA,CAAA,EAAQ;AAC3D,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAOA,iBAAA,CAAa,KAAA,CAAM,mBAAmB,CAAC,CAAA;AAChD;AAMO,SAAS,cAAc,IAAA,EAA4B;AACxD,EAAA,MAAM,YAAYC,iBAAA,EAAa;AAE/B,EAAA,IAAI,CAAC,SAAA,CAAU,cAAc,CAAA,EAAG;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,CAAC,GAAG,IAAA,EAAM,EAAE,CAAC,mBAAmB,GAAG,WAAW,CAAA;AACvD;AASO,SAAS,WAAW,IAAA,EAA0B;AACnD,EAAA,OAAO,IAAA,CAAK,SAAS,CAAA,IAAK,eAAA,CAAgB,KAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAC,CAAA;AACjE;AAMO,SAAS,eACd,IAAA,EAIA;AACA,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,EAAE,IAAA,EAAK;AAAA,EAChB;AAEA,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AACjC,EAAA,IAAI,eAAA,CAAgB,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,MACtB,SAAS,IAAA,CAAK;AAAA,KAChB;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAA,EAAK;AAChB;;;;;;"}
@@ -102,7 +102,7 @@ function instrumentWorkflowWithSentry(optionsCallback, WorkFlowClass) {
102
102
  return async function(event, step) {
103
103
  async.setAsyncLocalStorageAsyncContextStrategy();
104
104
  return core.withIsolationScope(async (isolationScope) => {
105
- const waitUntil = context.waitUntil.bind(context);
105
+ const waitUntil = flush.getOriginalWaitUntil(context).bind(context);
106
106
  const client = sdk.init({ ...options, ctx: context, enableDedupe: false });
107
107
  isolationScope.setClient(client);
108
108
  scopeUtils.addCloudResourceContext(isolationScope);
@@ -1 +1 @@
1
- {"version":3,"file":"workflows.js","sources":["../../src/workflows.ts"],"sourcesContent":["import type { PropagationContext } from '@sentry/core';\nimport {\n captureException,\n flush,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n startSpan,\n withIsolationScope,\n withScope,\n} from '@sentry/core';\nimport type {\n WorkflowDelayDuration,\n WorkflowEntrypoint,\n WorkflowEvent,\n WorkflowSleepDuration,\n WorkflowStep,\n WorkflowStepConfig,\n WorkflowStepContext,\n WorkflowStepEvent,\n WorkflowStepRollbackOptions,\n WorkflowTimeoutDuration,\n} from 'cloudflare:workers';\nimport { setAsyncLocalStorageAsyncContextStrategy } from './async';\nimport type { CloudflareOptions } from './client';\nimport { flushAndDispose } from './flush';\nimport { instrumentEnv } from './instrumentations/worker/instrumentEnv';\nimport { addCloudResourceContext } from './scope-utils';\nimport { init } from './sdk';\nimport { instrumentContext } from './utils/instrumentContext';\n\nconst UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\n/**\n * Hashes a string to a UUID using SHA-1.\n */\nexport async function deterministicTraceIdFromInstanceId(instanceId: string): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(instanceId));\n return (\n Array.from(new Uint8Array(buf))\n // We only need the first 16 bytes for the 32 characters\n .slice(0, 16)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n );\n}\n\nasync function propagationContextFromInstanceId(instanceId: string): Promise<PropagationContext> {\n const traceId = UUID_REGEX.test(instanceId)\n ? instanceId.replace(/-/g, '')\n : await deterministicTraceIdFromInstanceId(instanceId);\n\n // Derive sampleRand from last 4 characters of the random UUID\n //\n // We cannot store any state between workflow steps, so we derive the\n // sampleRand from the traceId itself. This ensures that the sampling is\n // consistent across all steps in the same workflow instance.\n const sampleRand = parseInt(traceId.slice(-4), 16) / 0xffff;\n\n return {\n traceId,\n sampleRand,\n };\n}\n\nclass WrappedWorkflowStep implements WorkflowStep {\n public constructor(\n private _instanceId: string,\n private _options: CloudflareOptions,\n private _step: WorkflowStep,\n private _waitUntil: ExecutionContext['waitUntil'],\n ) {}\n\n public async do<T extends Rpc.Serializable<T>>(\n name: string,\n callback: (ctx: WorkflowStepContext) => Promise<T>,\n rollbackOptions?: WorkflowStepRollbackOptions<T>,\n ): Promise<T>;\n public async do<T extends Rpc.Serializable<T>, const C extends WorkflowStepConfig>(\n name: string,\n config: C,\n callback: (\n ctx: WorkflowStepContext<C['retries'] extends { delay: infer D } ? D : WorkflowDelayDuration | number>,\n ) => Promise<T>,\n rollbackOptions?: WorkflowStepRollbackOptions<T>,\n ): Promise<T>;\n public async do<T extends Rpc.Serializable<T>>(\n name: string,\n configOrCallback: WorkflowStepConfig | ((ctx: WorkflowStepContext) => Promise<T>),\n callbackOrRollback?: ((ctx: WorkflowStepContext) => Promise<T>) | WorkflowStepRollbackOptions<T>,\n maybeRollback?: WorkflowStepRollbackOptions<T>,\n ): Promise<T> {\n // Capture the current scope, so parent span (e.g., a startSpan surrounding step.do) is preserved\n const scopeForStep = getCurrentScope();\n\n const hasConfig = typeof configOrCallback !== 'function';\n const config = hasConfig ? configOrCallback : undefined;\n const userCallback = (hasConfig ? callbackOrRollback : configOrCallback) as (\n ctx: WorkflowStepContext,\n ) => Promise<T>;\n const rollbackOptions = (hasConfig ? maybeRollback : callbackOrRollback) as\n | WorkflowStepRollbackOptions<T>\n | undefined;\n\n const instrumentedCallback = async (...args: unknown[]): Promise<T> => {\n // Feature detection: Cloudflare Workflows (April 2026+) pass a step context\n // with `attempt` and `config.retries.limit`. When available, we only capture\n // errors on the final attempt to avoid duplicates during retries.\n const stepContext = args[0] as { attempt?: number; config?: { retries?: { limit?: number } } } | undefined;\n const attempt = stepContext?.attempt;\n const retryLimit = stepContext?.config?.retries?.limit;\n const hasStepContext = typeof attempt === 'number' && typeof retryLimit === 'number';\n\n // Only capture error on final attempt (attempt > retryLimit means no more retries left)\n // or when step context is unavailable (legacy behavior - capture all errors)\n const isFinalAttempt = !hasStepContext || attempt > retryLimit;\n\n return startSpan(\n {\n op: 'function.step.do',\n name,\n scope: scopeForStep,\n attributes: {\n 'cloudflare.workflow.timeout': config?.timeout,\n 'cloudflare.workflow.retries.backoff': config?.retries?.backoff,\n // In workers-types v5, `delay` may be a `WorkflowDelayFunction`, which isn't a valid span attribute value.\n 'cloudflare.workflow.retries.delay':\n typeof config?.retries?.delay === 'function' ? undefined : config?.retries?.delay,\n 'cloudflare.workflow.retries.limit': config?.retries?.limit,\n 'cloudflare.workflow.attempt': attempt,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.workflow',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',\n },\n },\n async span => {\n try {\n const result = await (userCallback as (...args: unknown[]) => Promise<T>)(...args);\n span.setStatus({ code: 1 });\n return result;\n } catch (error) {\n if (isFinalAttempt) {\n captureException(error, { mechanism: { handled: true, type: 'auto.faas.cloudflare.workflow' } });\n }\n throw error;\n } finally {\n this._waitUntil(flush(2000));\n }\n },\n );\n };\n\n if (config) {\n return rollbackOptions\n ? this._step.do(name, config, instrumentedCallback, rollbackOptions)\n : this._step.do(name, config, instrumentedCallback);\n }\n\n return rollbackOptions\n ? this._step.do(name, instrumentedCallback, rollbackOptions)\n : this._step.do(name, instrumentedCallback);\n }\n\n public async sleep(name: string, duration: WorkflowSleepDuration): Promise<void> {\n return this._step.sleep(name, duration);\n }\n\n public async sleepUntil(name: string, timestamp: Date | number): Promise<void> {\n return this._step.sleepUntil(name, timestamp);\n }\n\n public async waitForEvent<T extends Rpc.Serializable<T>>(\n name: string,\n options: { type: string; timeout?: WorkflowTimeoutDuration | number },\n ): Promise<WorkflowStepEvent<T>> {\n return this._step.waitForEvent<T>(name, options);\n }\n}\n\n/**\n * Instruments a Cloudflare Workflow class with Sentry.\n *\n * @example\n * ```typescript\n * const InstrumentedWorkflow = instrumentWorkflowWithSentry(\n * (env) => ({ dsn: env.SENTRY_DSN }),\n * MyWorkflowClass\n * );\n *\n * export default InstrumentedWorkflow;\n * ```\n *\n * @param optionsCallback - Function that returns Sentry options to initialize Sentry\n * @param WorkflowClass - The workflow class to instrument\n * @returns Instrumented workflow class with the same interface\n */\nexport function instrumentWorkflowWithSentry<\n E, // Environment type\n P, // Payload type\n T extends WorkflowEntrypoint<E, P>, // WorkflowEntrypoint type\n C extends new (ctx: ExecutionContext, env: E) => T, // Constructor type of the WorkflowEntrypoint class\n>(optionsCallback: (env: E) => CloudflareOptions, WorkFlowClass: C): C {\n return new Proxy(WorkFlowClass, {\n construct(target: C, args: [ctx: ExecutionContext, env: E], newTarget) {\n const [ctx, env] = args;\n const context = instrumentContext(ctx);\n const options = optionsCallback(env);\n args[0] = context;\n args[1] = instrumentEnv(env as Record<string, unknown>, options) as E;\n const instance = Reflect.construct(target, args, newTarget) as T;\n return new Proxy(instance, {\n get(obj, prop, receiver) {\n if (prop === 'run') {\n return async function (event: WorkflowEvent<P>, step: WorkflowStep): Promise<unknown> {\n setAsyncLocalStorageAsyncContextStrategy();\n\n return withIsolationScope(async isolationScope => {\n const waitUntil = context.waitUntil.bind(context);\n const client = init({ ...options, ctx: context, enableDedupe: false });\n isolationScope.setClient(client);\n\n addCloudResourceContext(isolationScope);\n\n return withScope(async scope => {\n const propagationContext = await propagationContextFromInstanceId(event.instanceId);\n scope.setPropagationContext(propagationContext);\n\n try {\n return await obj.run.call(\n obj,\n event,\n new WrappedWorkflowStep(event.instanceId, options, step, waitUntil),\n );\n } finally {\n waitUntil(flushAndDispose(client));\n }\n });\n });\n };\n }\n return Reflect.get(obj, prop, receiver);\n },\n });\n },\n });\n}\n"],"names":["getCurrentScope","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","captureException","flush","instrumentContext","instrumentEnv","setAsyncLocalStorageAsyncContextStrategy","withIsolationScope","init","addCloudResourceContext","withScope","flushAndDispose"],"mappings":"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"}
1
+ {"version":3,"file":"workflows.js","sources":["../../src/workflows.ts"],"sourcesContent":["import type { PropagationContext } from '@sentry/core';\nimport {\n captureException,\n flush,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n startSpan,\n withIsolationScope,\n withScope,\n} from '@sentry/core';\nimport type {\n WorkflowDelayDuration,\n WorkflowEntrypoint,\n WorkflowEvent,\n WorkflowSleepDuration,\n WorkflowStep,\n WorkflowStepConfig,\n WorkflowStepContext,\n WorkflowStepEvent,\n WorkflowStepRollbackOptions,\n WorkflowTimeoutDuration,\n} from 'cloudflare:workers';\nimport { setAsyncLocalStorageAsyncContextStrategy } from './async';\nimport type { CloudflareOptions } from './client';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { instrumentEnv } from './instrumentations/worker/instrumentEnv';\nimport { addCloudResourceContext } from './scope-utils';\nimport { init } from './sdk';\nimport { instrumentContext } from './utils/instrumentContext';\n\nconst UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\n/**\n * Hashes a string to a UUID using SHA-1.\n */\nexport async function deterministicTraceIdFromInstanceId(instanceId: string): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(instanceId));\n return (\n Array.from(new Uint8Array(buf))\n // We only need the first 16 bytes for the 32 characters\n .slice(0, 16)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n );\n}\n\nasync function propagationContextFromInstanceId(instanceId: string): Promise<PropagationContext> {\n const traceId = UUID_REGEX.test(instanceId)\n ? instanceId.replace(/-/g, '')\n : await deterministicTraceIdFromInstanceId(instanceId);\n\n // Derive sampleRand from last 4 characters of the random UUID\n //\n // We cannot store any state between workflow steps, so we derive the\n // sampleRand from the traceId itself. This ensures that the sampling is\n // consistent across all steps in the same workflow instance.\n const sampleRand = parseInt(traceId.slice(-4), 16) / 0xffff;\n\n return {\n traceId,\n sampleRand,\n };\n}\n\nclass WrappedWorkflowStep implements WorkflowStep {\n public constructor(\n private _instanceId: string,\n private _options: CloudflareOptions,\n private _step: WorkflowStep,\n private _waitUntil: ExecutionContext['waitUntil'],\n ) {}\n\n public async do<T extends Rpc.Serializable<T>>(\n name: string,\n callback: (ctx: WorkflowStepContext) => Promise<T>,\n rollbackOptions?: WorkflowStepRollbackOptions<T>,\n ): Promise<T>;\n public async do<T extends Rpc.Serializable<T>, const C extends WorkflowStepConfig>(\n name: string,\n config: C,\n callback: (\n ctx: WorkflowStepContext<C['retries'] extends { delay: infer D } ? D : WorkflowDelayDuration | number>,\n ) => Promise<T>,\n rollbackOptions?: WorkflowStepRollbackOptions<T>,\n ): Promise<T>;\n public async do<T extends Rpc.Serializable<T>>(\n name: string,\n configOrCallback: WorkflowStepConfig | ((ctx: WorkflowStepContext) => Promise<T>),\n callbackOrRollback?: ((ctx: WorkflowStepContext) => Promise<T>) | WorkflowStepRollbackOptions<T>,\n maybeRollback?: WorkflowStepRollbackOptions<T>,\n ): Promise<T> {\n // Capture the current scope, so parent span (e.g., a startSpan surrounding step.do) is preserved\n const scopeForStep = getCurrentScope();\n\n const hasConfig = typeof configOrCallback !== 'function';\n const config = hasConfig ? configOrCallback : undefined;\n const userCallback = (hasConfig ? callbackOrRollback : configOrCallback) as (\n ctx: WorkflowStepContext,\n ) => Promise<T>;\n const rollbackOptions = (hasConfig ? maybeRollback : callbackOrRollback) as\n | WorkflowStepRollbackOptions<T>\n | undefined;\n\n const instrumentedCallback = async (...args: unknown[]): Promise<T> => {\n // Feature detection: Cloudflare Workflows (April 2026+) pass a step context\n // with `attempt` and `config.retries.limit`. When available, we only capture\n // errors on the final attempt to avoid duplicates during retries.\n const stepContext = args[0] as { attempt?: number; config?: { retries?: { limit?: number } } } | undefined;\n const attempt = stepContext?.attempt;\n const retryLimit = stepContext?.config?.retries?.limit;\n const hasStepContext = typeof attempt === 'number' && typeof retryLimit === 'number';\n\n // Only capture error on final attempt (attempt > retryLimit means no more retries left)\n // or when step context is unavailable (legacy behavior - capture all errors)\n const isFinalAttempt = !hasStepContext || attempt > retryLimit;\n\n return startSpan(\n {\n op: 'function.step.do',\n name,\n scope: scopeForStep,\n attributes: {\n 'cloudflare.workflow.timeout': config?.timeout,\n 'cloudflare.workflow.retries.backoff': config?.retries?.backoff,\n // In workers-types v5, `delay` may be a `WorkflowDelayFunction`, which isn't a valid span attribute value.\n 'cloudflare.workflow.retries.delay':\n typeof config?.retries?.delay === 'function' ? undefined : config?.retries?.delay,\n 'cloudflare.workflow.retries.limit': config?.retries?.limit,\n 'cloudflare.workflow.attempt': attempt,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.workflow',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',\n },\n },\n async span => {\n try {\n const result = await (userCallback as (...args: unknown[]) => Promise<T>)(...args);\n span.setStatus({ code: 1 });\n return result;\n } catch (error) {\n if (isFinalAttempt) {\n captureException(error, { mechanism: { handled: true, type: 'auto.faas.cloudflare.workflow' } });\n }\n throw error;\n } finally {\n this._waitUntil(flush(2000));\n }\n },\n );\n };\n\n if (config) {\n return rollbackOptions\n ? this._step.do(name, config, instrumentedCallback, rollbackOptions)\n : this._step.do(name, config, instrumentedCallback);\n }\n\n return rollbackOptions\n ? this._step.do(name, instrumentedCallback, rollbackOptions)\n : this._step.do(name, instrumentedCallback);\n }\n\n public async sleep(name: string, duration: WorkflowSleepDuration): Promise<void> {\n return this._step.sleep(name, duration);\n }\n\n public async sleepUntil(name: string, timestamp: Date | number): Promise<void> {\n return this._step.sleepUntil(name, timestamp);\n }\n\n public async waitForEvent<T extends Rpc.Serializable<T>>(\n name: string,\n options: { type: string; timeout?: WorkflowTimeoutDuration | number },\n ): Promise<WorkflowStepEvent<T>> {\n return this._step.waitForEvent<T>(name, options);\n }\n}\n\n/**\n * Instruments a Cloudflare Workflow class with Sentry.\n *\n * @example\n * ```typescript\n * const InstrumentedWorkflow = instrumentWorkflowWithSentry(\n * (env) => ({ dsn: env.SENTRY_DSN }),\n * MyWorkflowClass\n * );\n *\n * export default InstrumentedWorkflow;\n * ```\n *\n * @param optionsCallback - Function that returns Sentry options to initialize Sentry\n * @param WorkflowClass - The workflow class to instrument\n * @returns Instrumented workflow class with the same interface\n */\nexport function instrumentWorkflowWithSentry<\n E, // Environment type\n P, // Payload type\n T extends WorkflowEntrypoint<E, P>, // WorkflowEntrypoint type\n C extends new (ctx: ExecutionContext, env: E) => T, // Constructor type of the WorkflowEntrypoint class\n>(optionsCallback: (env: E) => CloudflareOptions, WorkFlowClass: C): C {\n return new Proxy(WorkFlowClass, {\n construct(target: C, args: [ctx: ExecutionContext, env: E], newTarget) {\n const [ctx, env] = args;\n const context = instrumentContext(ctx);\n const options = optionsCallback(env);\n args[0] = context;\n args[1] = instrumentEnv(env as Record<string, unknown>, options) as E;\n const instance = Reflect.construct(target, args, newTarget) as T;\n return new Proxy(instance, {\n get(obj, prop, receiver) {\n if (prop === 'run') {\n return async function (event: WorkflowEvent<P>, step: WorkflowStep): Promise<unknown> {\n setAsyncLocalStorageAsyncContextStrategy();\n\n return withIsolationScope(async isolationScope => {\n const waitUntil = getOriginalWaitUntil(context).bind(context);\n const client = init({ ...options, ctx: context, enableDedupe: false });\n isolationScope.setClient(client);\n\n addCloudResourceContext(isolationScope);\n\n return withScope(async scope => {\n const propagationContext = await propagationContextFromInstanceId(event.instanceId);\n scope.setPropagationContext(propagationContext);\n\n try {\n return await obj.run.call(\n obj,\n event,\n new WrappedWorkflowStep(event.instanceId, options, step, waitUntil),\n );\n } finally {\n waitUntil(flushAndDispose(client));\n }\n });\n });\n };\n }\n return Reflect.get(obj, prop, receiver);\n },\n });\n },\n });\n}\n"],"names":["getCurrentScope","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","captureException","flush","instrumentContext","instrumentEnv","setAsyncLocalStorageAsyncContextStrategy","withIsolationScope","getOriginalWaitUntil","init","addCloudResourceContext","withScope","flushAndDispose"],"mappings":"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"}
@@ -4,6 +4,7 @@ const core = require('@sentry/core');
4
4
  const flush = require('./flush.js');
5
5
  const instrument = require('./instrument.js');
6
6
  const sdk = require('./sdk.js');
7
+ const invocationScope = require('./utils/invocationScope.js');
7
8
  const rpcMeta = require('./utils/rpcMeta.js');
8
9
  const traceLinks = require('./utils/traceLinks.js');
9
10
 
@@ -32,8 +33,6 @@ function wrapMethodWithSentry(wrapperOptions, handler, callback, noMark) {
32
33
  args = extracted.args;
33
34
  rpcMeta$1 = extracted.rpcMeta;
34
35
  }
35
- const currentClient = core.getClient();
36
- const sentryWithScope = startNewTrace ? core.withIsolationScope : currentClient ? core.withScope : core.withIsolationScope;
37
36
  const wrappedFunction = (scope) => {
38
37
  const context = wrapperOptions.context;
39
38
  const waitUntil = context ? flush.getOriginalWaitUntil(context)?.bind(context) : void 0;
@@ -124,7 +123,7 @@ function wrapMethodWithSentry(wrapperOptions, handler, callback, noMark) {
124
123
  }
125
124
  return executeSpan();
126
125
  };
127
- return sentryWithScope(wrappedFunction);
126
+ return invocationScope.withInvocationIsolationScope(wrappedFunction);
128
127
  }
129
128
  }),
130
129
  noMark
@@ -1 +1 @@
1
- {"version":3,"file":"wrapMethodWithSentry.js","sources":["../../src/wrapMethodWithSentry.ts"],"sourcesContent":["import type { DurableObjectStorage } from '@cloudflare/workers-types';\nimport type { SerializedTraceData } from '@sentry/core';\nimport {\n isObjectLike,\n captureException,\n continueTrace,\n getClient,\n isThenable,\n type Scope,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n startNewTrace as startNewTraceCore,\n startSpan,\n withIsolationScope,\n withScope,\n} from '@sentry/core';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { ensureInstrumented } from './instrument';\nimport { init } from './sdk';\nimport { extractRpcMeta } from './utils/rpcMeta';\nimport { buildSpanLinks, getStoredSpanContext, storeSpanContext } from './utils/traceLinks';\n\n/** Extended DurableObjectState with originalStorage exposed by instrumentContext */\ninterface InstrumentedDurableObjectState extends DurableObjectState {\n originalStorage?: DurableObjectStorage;\n}\n\n/**\n * Resolves uninstrumented DO storage for the current invocation.\n * Prefer `thisArg.ctx` (the live Durable Object instance) over the context captured at\n * construction time to avoid cross-DO I/O errors in the same isolate.\n */\nfunction resolveOriginalStorage(\n context: ExecutionContext | InstrumentedDurableObjectState | undefined,\n thisArg: unknown,\n): DurableObjectStorage | undefined {\n if (isObjectLike(thisArg) && 'ctx' in thisArg) {\n const doCtx = (thisArg as { ctx: InstrumentedDurableObjectState }).ctx;\n if (doCtx?.originalStorage) {\n return doCtx.originalStorage;\n }\n }\n\n if (context && 'originalStorage' in context && context.originalStorage) {\n return context.originalStorage;\n }\n\n return undefined;\n}\n\ntype MethodWrapperOptions = {\n spanName?: string;\n spanOp?: string;\n options: CloudflareOptions;\n context: ExecutionContext | InstrumentedDurableObjectState;\n /**\n * If true, starts a fresh trace instead of inheriting from a parent trace.\n * Useful for scheduled/independent invocations like alarms.\n *\n * If true, it also stores the current span context and links to the previous invocation's span.\n * Uses Durable Object storage to persist the link. The link is set asynchronously via `span.addLinks()`\n * in a `waitUntil` to avoid blocking.\n *\n * @default false\n */\n startNewTrace?: boolean;\n /**\n * The trace origin identifying which instrumentation created the span, e.g. `auto.faas.cloudflare.durable_object`.\n * Used both as the span's `sentry.origin` attribute and as the `mechanism.type` for captured exceptions.\n */\n origin: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UncheckedMethod = (...args: any[]) => any;\ntype OriginalMethod = UncheckedMethod;\n\n/**\n * Wraps a method with Sentry error tracking and optional tracing.\n * Supports starting new traces and linking to previous invocations via Durable Object storage.\n *\n * @param wrapperOptions - The options for the wrapper.\n * @param handler - The method to wrap.\n * @param callback - The callback to call.\n * @param noMark - Whether to mark the method as instrumented.\n * @returns The wrapped method.\n */\nexport function wrapMethodWithSentry<T extends OriginalMethod>(\n wrapperOptions: MethodWrapperOptions,\n handler: T,\n callback?: (...args: Parameters<T>) => void,\n noMark?: true,\n): T {\n return ensureInstrumented(\n handler,\n original =>\n new Proxy(original, {\n apply(target, thisArg, rawArgs: Parameters<T>) {\n const { startNewTrace, origin } = wrapperOptions;\n\n // For RPC methods, extract Sentry trace context from the trailing argument.\n // The caller side (instrumentDurableObjectStub / JSRPC proxy) appends it;\n // we strip it here so the user's method never sees it.\n let args = rawArgs;\n let rpcMeta: SerializedTraceData | undefined;\n\n if (wrapperOptions.spanOp === 'rpc') {\n const extracted = extractRpcMeta(rawArgs);\n args = extracted.args;\n rpcMeta = extracted.rpcMeta;\n }\n\n // For startNewTrace, always use withIsolationScope to ensure a fresh scope\n // Otherwise, use existing client's scope or isolation scope\n const currentClient = getClient();\n const sentryWithScope = startNewTrace ? withIsolationScope : currentClient ? withScope : withIsolationScope;\n\n const wrappedFunction = (scope: Scope): unknown | Promise<unknown> => {\n // In certain situations, the passed context can become undefined.\n // For example, for Astro while prerendering pages at build time.\n // see: https://github.com/getsentry/sentry-javascript/issues/13217\n const context: typeof wrapperOptions.context | undefined = wrapperOptions.context;\n\n // see: https://github.com/getsentry/sentry-javascript/issues/22328\n const waitUntil = context\n ? getOriginalWaitUntil(context as ExecutionContextCompat)?.bind(context)\n : undefined;\n const storage = resolveOriginalStorage(context, thisArg);\n\n let scopeClient = scope.getClient();\n // Check if client exists AND is still usable (transport not disposed)\n // This handles the case where a previous handler disposed the client\n // but the scope still holds a reference to it (e.g., alarm handlers in Durable Objects)\n // For startNewTrace, always create a fresh client\n if (startNewTrace || !scopeClient?.getTransport()) {\n const client = init({\n ...wrapperOptions.options,\n ctx: context as unknown as ExecutionContext | undefined,\n });\n scope.setClient(client);\n scopeClient = client;\n }\n\n const clientToDispose = scopeClient;\n const methodName = wrapperOptions.spanName || 'unknown';\n\n const teardown = async (): Promise<void> => {\n if (startNewTrace && storage) {\n storeSpanContext(storage, methodName);\n }\n await flushAndDispose(clientToDispose);\n };\n\n const onFulfilled = (res: unknown) => {\n waitUntil?.(teardown());\n return res;\n };\n\n const onRejected = (e: unknown) => {\n captureException(e, {\n mechanism: {\n type: origin,\n handled: false,\n },\n });\n waitUntil?.(teardown());\n throw e;\n };\n\n if (!wrapperOptions.spanName) {\n try {\n if (callback) {\n callback(...args);\n }\n\n const result = Reflect.apply(target, thisArg, args);\n\n if (isThenable(result)) {\n return result.then(onFulfilled, onRejected);\n } else {\n return onFulfilled(result);\n }\n } catch (e) {\n return onRejected(e);\n }\n }\n\n const attributes = wrapperOptions.spanOp\n ? {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: wrapperOptions.spanOp,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,\n }\n : {};\n\n const executeSpan = (): unknown => {\n return startSpan({ name: methodName, attributes }, span => {\n if (startNewTrace && storage) {\n const storedContext = getStoredSpanContext(storage, methodName);\n\n if (storedContext) {\n span.addLinks(buildSpanLinks(storedContext));\n // TODO: Remove this once EAP can store span links. We currently only set this attribute so that we\n // can obtain the previous trace information from the EAP store. Long-term, EAP will handle\n // span links and then we should remove this again. Also throwing in a TODO(v11), to remind us\n // to check this at v11 time :)\n const sampledFlag = storedContext.sampled ? '1' : '0';\n span.setAttribute(\n 'sentry.previous_trace',\n `${storedContext.traceId}-${storedContext.spanId}-${sampledFlag}`,\n );\n }\n }\n\n try {\n const result = Reflect.apply(target, thisArg, args);\n\n if (isThenable(result)) {\n return result.then(onFulfilled, onRejected);\n } else {\n return onFulfilled(result);\n }\n } catch (e) {\n return onRejected(e);\n }\n });\n };\n\n if (rpcMeta) {\n return continueTrace(\n { sentryTrace: rpcMeta['sentry-trace'] || '', baggage: rpcMeta.baggage || '' },\n executeSpan,\n );\n }\n\n if (startNewTrace) {\n return startNewTraceCore(() => executeSpan());\n }\n\n return executeSpan();\n };\n\n return sentryWithScope(wrappedFunction);\n },\n }),\n noMark,\n );\n}\n"],"names":["isObjectLike","ensureInstrumented","rpcMeta","extractRpcMeta","getClient","withIsolationScope","withScope","getOriginalWaitUntil","init","storeSpanContext","flushAndDispose","captureException","isThenable","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","startSpan","getStoredSpanContext","buildSpanLinks","continueTrace","startNewTraceCore"],"mappings":"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"}
1
+ {"version":3,"file":"wrapMethodWithSentry.js","sources":["../../src/wrapMethodWithSentry.ts"],"sourcesContent":["import type { DurableObjectStorage } from '@cloudflare/workers-types';\nimport type { SerializedTraceData } from '@sentry/core';\nimport {\n isObjectLike,\n captureException,\n continueTrace,\n isThenable,\n type Scope,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n startNewTrace as startNewTraceCore,\n startSpan,\n} from '@sentry/core';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { ensureInstrumented } from './instrument';\nimport { init } from './sdk';\nimport { withInvocationIsolationScope } from './utils/invocationScope';\nimport { extractRpcMeta } from './utils/rpcMeta';\nimport { buildSpanLinks, getStoredSpanContext, storeSpanContext } from './utils/traceLinks';\n\n/** Extended DurableObjectState with originalStorage exposed by instrumentContext */\nexport interface InstrumentedDurableObjectState extends DurableObjectState {\n originalStorage?: DurableObjectStorage;\n}\n\n/**\n * Resolves uninstrumented DO storage for the current invocation.\n * Prefer `thisArg.ctx` (the live Durable Object instance) over the context captured at\n * construction time to avoid cross-DO I/O errors in the same isolate.\n */\nfunction resolveOriginalStorage(\n context: ExecutionContext | InstrumentedDurableObjectState | undefined,\n thisArg: unknown,\n): DurableObjectStorage | undefined {\n if (isObjectLike(thisArg) && 'ctx' in thisArg) {\n const doCtx = (thisArg as { ctx: InstrumentedDurableObjectState }).ctx;\n if (doCtx?.originalStorage) {\n return doCtx.originalStorage;\n }\n }\n\n if (context && 'originalStorage' in context && context.originalStorage) {\n return context.originalStorage;\n }\n\n return undefined;\n}\n\ntype MethodWrapperOptions = {\n spanName?: string;\n spanOp?: string;\n options: CloudflareOptions;\n context: ExecutionContext | InstrumentedDurableObjectState;\n /**\n * If true, starts a fresh trace instead of inheriting from a parent trace.\n * Useful for scheduled/independent invocations like alarms.\n *\n * If true, it also stores the current span context and links to the previous invocation's span.\n * Uses Durable Object storage to persist the link. The link is set asynchronously via `span.addLinks()`\n * in a `waitUntil` to avoid blocking.\n *\n * @default false\n */\n startNewTrace?: boolean;\n /**\n * The trace origin identifying which instrumentation created the span, e.g. `auto.faas.cloudflare.durable_object`.\n * Used both as the span's `sentry.origin` attribute and as the `mechanism.type` for captured exceptions.\n */\n origin: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UncheckedMethod = (...args: any[]) => any;\ntype OriginalMethod = UncheckedMethod;\n\n/**\n * Wraps a method with Sentry error tracking and optional tracing.\n * Supports starting new traces and linking to previous invocations via Durable Object storage.\n *\n * @param wrapperOptions - The options for the wrapper.\n * @param handler - The method to wrap.\n * @param callback - The callback to call.\n * @param noMark - Whether to mark the method as instrumented.\n * @returns The wrapped method.\n */\nexport function wrapMethodWithSentry<T extends OriginalMethod>(\n wrapperOptions: MethodWrapperOptions,\n handler: T,\n callback?: (...args: Parameters<T>) => void,\n noMark?: true,\n): T {\n return ensureInstrumented(\n handler,\n original =>\n new Proxy(original, {\n apply(target, thisArg, rawArgs: Parameters<T>) {\n const { startNewTrace, origin } = wrapperOptions;\n\n // For RPC methods, extract Sentry trace context from the trailing argument.\n // The caller side (instrumentDurableObjectStub / JSRPC proxy) appends it;\n // we strip it here so the user's method never sees it.\n let args = rawArgs;\n let rpcMeta: SerializedTraceData | undefined;\n\n if (wrapperOptions.spanOp === 'rpc') {\n const extracted = extractRpcMeta(rawArgs);\n args = extracted.args;\n rpcMeta = extracted.rpcMeta;\n }\n\n const wrappedFunction = (scope: Scope): unknown | Promise<unknown> => {\n // In certain situations, the passed context can become undefined.\n // For example, for Astro while prerendering pages at build time.\n // see: https://github.com/getsentry/sentry-javascript/issues/13217\n const context: typeof wrapperOptions.context | undefined = wrapperOptions.context;\n\n // see: https://github.com/getsentry/sentry-javascript/issues/22328\n const waitUntil = context\n ? getOriginalWaitUntil(context as ExecutionContextCompat)?.bind(context)\n : undefined;\n const storage = resolveOriginalStorage(context, thisArg);\n\n let scopeClient = scope.getClient();\n // Check if client exists AND is still usable (transport not disposed)\n // This handles the case where a previous handler disposed the client\n // but the scope still holds a reference to it (e.g., alarm handlers in Durable Objects)\n // For startNewTrace, always create a fresh client\n if (startNewTrace || !scopeClient?.getTransport()) {\n const client = init({\n ...wrapperOptions.options,\n ctx: context as unknown as ExecutionContext | undefined,\n });\n scope.setClient(client);\n scopeClient = client;\n }\n\n const clientToDispose = scopeClient;\n const methodName = wrapperOptions.spanName || 'unknown';\n\n const teardown = async (): Promise<void> => {\n if (startNewTrace && storage) {\n storeSpanContext(storage, methodName);\n }\n await flushAndDispose(clientToDispose);\n };\n\n const onFulfilled = (res: unknown) => {\n waitUntil?.(teardown());\n return res;\n };\n\n const onRejected = (e: unknown) => {\n captureException(e, {\n mechanism: {\n type: origin,\n handled: false,\n },\n });\n waitUntil?.(teardown());\n throw e;\n };\n\n if (!wrapperOptions.spanName) {\n try {\n if (callback) {\n callback(...args);\n }\n\n const result = Reflect.apply(target, thisArg, args);\n\n if (isThenable(result)) {\n return result.then(onFulfilled, onRejected);\n } else {\n return onFulfilled(result);\n }\n } catch (e) {\n return onRejected(e);\n }\n }\n\n const attributes = wrapperOptions.spanOp\n ? {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: wrapperOptions.spanOp,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,\n }\n : {};\n\n const executeSpan = (): unknown => {\n return startSpan({ name: methodName, attributes }, span => {\n if (startNewTrace && storage) {\n const storedContext = getStoredSpanContext(storage, methodName);\n\n if (storedContext) {\n span.addLinks(buildSpanLinks(storedContext));\n // TODO: Remove this once EAP can store span links. We currently only set this attribute so that we\n // can obtain the previous trace information from the EAP store. Long-term, EAP will handle\n // span links and then we should remove this again. Also throwing in a TODO(v11), to remind us\n // to check this at v11 time :)\n const sampledFlag = storedContext.sampled ? '1' : '0';\n span.setAttribute(\n 'sentry.previous_trace',\n `${storedContext.traceId}-${storedContext.spanId}-${sampledFlag}`,\n );\n }\n }\n\n try {\n const result = Reflect.apply(target, thisArg, args);\n\n if (isThenable(result)) {\n return result.then(onFulfilled, onRejected);\n } else {\n return onFulfilled(result);\n }\n } catch (e) {\n return onRejected(e);\n }\n });\n };\n\n if (rpcMeta) {\n return continueTrace(\n { sentryTrace: rpcMeta['sentry-trace'] || '', baggage: rpcMeta.baggage || '' },\n executeSpan,\n );\n }\n\n if (startNewTrace) {\n return startNewTraceCore(() => executeSpan());\n }\n\n return executeSpan();\n };\n\n return withInvocationIsolationScope(wrappedFunction);\n },\n }),\n noMark,\n );\n}\n"],"names":["isObjectLike","ensureInstrumented","rpcMeta","extractRpcMeta","getOriginalWaitUntil","init","storeSpanContext","flushAndDispose","captureException","isThenable","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","startSpan","getStoredSpanContext","buildSpanLinks","continueTrace","startNewTraceCore","withInvocationIsolationScope"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { captureException } from '@sentry/core';
1
+ import { isObjectLike, captureException } from '@sentry/core';
2
2
  import { setAsyncLocalStorageAsyncContextStrategy } from './async.js';
3
- import { ensureInstrumented } from './instrument.js';
3
+ import { ensureInstrumented, markAsInstrumented, getInstrumented } from './instrument.js';
4
4
  import { instrumentEnv } from './instrumentations/worker/instrumentEnv.js';
5
5
  import { getFinalOptions } from './options.js';
6
6
  import { wrapRequestHandler } from './request.js';
7
7
  import { instrumentContext } from './utils/instrumentContext.js';
8
- import { extractRpcMeta } from './utils/rpcMeta.js';
8
+ import { hasRpcMeta } from './utils/rpcMeta.js';
9
9
  import { getEffectiveRpcPropagation } from './utils/rpcOptions.js';
10
10
  import { instrumentCloudflareAgent } from './instrumentations/agents/index.js';
11
11
  import { wrapMethodWithSentry } from './wrapMethodWithSentry.js';
@@ -15,9 +15,49 @@ function constructInstrumentedDurableObject(target, ctx, env, newTarget, options
15
15
  const options = getFinalOptions(optionsCallback(env), env);
16
16
  const context = instrumentContext(ctx);
17
17
  const instrumentedEnv = instrumentEnv(env, options);
18
+ const prototype = newTarget.prototype ?? target.prototype;
19
+ const cachedFrameworkManagedMethods = frameworkManagedMethodsCache.get(prototype);
20
+ const methodsBeforeConstruction = cachedFrameworkManagedMethods ? void 0 : resolvePrototypeMethods(prototype);
18
21
  const obj = Reflect.construct(target, [context, instrumentedEnv], newTarget);
22
+ const frameworkManagedMethods = resolveFrameworkManagedMethods(
23
+ prototype,
24
+ obj,
25
+ methodsBeforeConstruction,
26
+ cachedFrameworkManagedMethods
27
+ );
19
28
  instrumentDurableObjectHandlers(obj, options, context);
20
- return { obj, options, context };
29
+ return { obj, options, context, frameworkManagedMethods };
30
+ }
31
+ const frameworkManagedMethodsCache = /* @__PURE__ */ new WeakMap();
32
+ function resolvePrototypeMethods(prototype) {
33
+ const methods = /* @__PURE__ */ new Map();
34
+ for (let current = prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
35
+ for (const name of Object.getOwnPropertyNames(current)) {
36
+ if (name === "constructor" || methods.has(name)) {
37
+ continue;
38
+ }
39
+ const descriptor = Object.getOwnPropertyDescriptor(current, name);
40
+ if (descriptor && typeof descriptor.value === "function") {
41
+ methods.set(name, descriptor.value);
42
+ }
43
+ }
44
+ }
45
+ return methods;
46
+ }
47
+ function resolveFrameworkManagedMethods(prototype, obj, methodsBeforeConstruction, cached) {
48
+ if (cached) {
49
+ return cached;
50
+ }
51
+ const methodsAfterConstruction = resolvePrototypeMethods(Object.getPrototypeOf(obj));
52
+ const managed = /* @__PURE__ */ new Set();
53
+ for (const [name, method] of methodsAfterConstruction) {
54
+ const before = methodsBeforeConstruction?.get(name);
55
+ if (before && before !== method) {
56
+ managed.add(name);
57
+ }
58
+ }
59
+ frameworkManagedMethodsCache.set(prototype, managed);
60
+ return managed;
21
61
  }
22
62
  function instrumentDurableObjectHandlers(obj, options, context) {
23
63
  if (obj.fetch && typeof obj.fetch === "function") {
@@ -70,70 +110,108 @@ function instrumentDurableObjectHandlers(obj, options, context) {
70
110
  );
71
111
  }
72
112
  }
73
- function finalizeWithRpcInstrumentation(obj, options, context) {
113
+ const RESERVED_RPC_METHOD_NAMES = /* @__PURE__ */ new Set([
114
+ "constructor",
115
+ "fetch",
116
+ "connect",
117
+ "alarm",
118
+ "webSocketMessage",
119
+ "webSocketClose",
120
+ "webSocketError",
121
+ "dup"
122
+ ]);
123
+ const rpcInstanceStates = /* @__PURE__ */ new WeakMap();
124
+ function finalizeWithRpcInstrumentation(obj, options, context, excludedMethods) {
74
125
  const rpcPropagation = getEffectiveRpcPropagation(options);
75
126
  if (!rpcPropagation) {
76
127
  return obj;
77
128
  }
78
- const instrumentPrototypeMethods = Array.isArray(options.instrumentPrototypeMethods) ? (
129
+ const allowedMethods = Array.isArray(options.instrumentPrototypeMethods) ? (
79
130
  // eslint-disable-next-line typescript/no-deprecated
80
- options.instrumentPrototypeMethods
131
+ new Set(options.instrumentPrototypeMethods)
81
132
  ) : void 0;
82
- const allowSet = instrumentPrototypeMethods ? new Set(instrumentPrototypeMethods) : null;
83
133
  const alwaysTrace = options.enableRpcTracePropagation === void 0;
84
- const methodCache = /* @__PURE__ */ new Map();
85
- return new Proxy(obj, {
86
- get(proxyTarget, prop, receiver) {
87
- const value = Reflect.get(proxyTarget, prop, receiver);
88
- if (typeof prop !== "string" || typeof value !== "function" || prop === "constructor") {
89
- return value;
90
- }
91
- const cached = methodCache.get(prop);
92
- if (cached) {
93
- return cached;
134
+ rpcInstanceStates.set(obj, { options, context, alwaysTrace });
135
+ instrumentPrototypeRpcMethods(obj, excludedMethods, allowedMethods);
136
+ return obj;
137
+ }
138
+ function getRpcMethodDescriptor(obj, prototype, methodName, excludedMethods, allowedMethods) {
139
+ if (RESERVED_RPC_METHOD_NAMES.has(methodName) || Object.prototype.hasOwnProperty.call(obj, methodName) || excludedMethods?.has(methodName) || allowedMethods && !allowedMethods.has(methodName)) {
140
+ return void 0;
141
+ }
142
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
143
+ if (!descriptor || typeof descriptor.value !== "function" || getInstrumented(descriptor.value)) {
144
+ return void 0;
145
+ }
146
+ return descriptor;
147
+ }
148
+ function instrumentPrototypeRpcMethods(obj, excludedMethods, allowedMethods) {
149
+ let prototype = Object.getPrototypeOf(obj);
150
+ while (prototype && prototype !== Object.prototype) {
151
+ for (const methodName of Object.getOwnPropertyNames(prototype)) {
152
+ const descriptor = getRpcMethodDescriptor(obj, prototype, methodName, excludedMethods, allowedMethods);
153
+ if (!descriptor) {
154
+ continue;
94
155
  }
95
- const boundMethod = value.bind(proxyTarget);
96
- if (prop in Object.prototype || Object.prototype.hasOwnProperty.call(proxyTarget, prop) || allowSet && !allowSet.has(prop)) {
97
- methodCache.set(prop, boundMethod);
98
- return boundMethod;
156
+ const wrapped = createRpcPrototypeWrapper(methodName, descriptor.value);
157
+ try {
158
+ Object.defineProperty(prototype, methodName, { ...descriptor, value: wrapped });
159
+ } catch {
99
160
  }
100
- const tracedMethod = wrapMethodWithSentry(
101
- { options, context, spanName: prop, spanOp: "rpc", origin: "auto.faas.cloudflare.durable_object" },
102
- boundMethod,
161
+ markAsInstrumented(wrapped);
162
+ }
163
+ prototype = Object.getPrototypeOf(prototype);
164
+ }
165
+ }
166
+ function createRpcPrototypeWrapper(methodName, originalMethod) {
167
+ const wrapper = function(...args) {
168
+ const state = isObjectLike(this) ? rpcInstanceStates.get(this) : void 0;
169
+ if (!state || !state.alwaysTrace && !hasRpcMeta(args)) {
170
+ return Reflect.apply(originalMethod, this, args);
171
+ }
172
+ const tracedMethods = state.tracedMethods ?? (state.tracedMethods = /* @__PURE__ */ new Map());
173
+ let traced = tracedMethods.get(methodName);
174
+ if (!traced) {
175
+ traced = wrapMethodWithSentry(
176
+ {
177
+ options: state.options,
178
+ context: state.context,
179
+ spanName: methodName,
180
+ spanOp: "rpc",
181
+ origin: "auto.faas.cloudflare.durable_object"
182
+ },
183
+ originalMethod,
103
184
  void 0,
104
185
  true
105
186
  );
106
- if (alwaysTrace) {
107
- methodCache.set(prop, tracedMethod);
108
- return tracedMethod;
109
- }
110
- const wrappedMethod = ((...args) => {
111
- const { rpcMeta } = extractRpcMeta(args);
112
- return rpcMeta ? tracedMethod(...args) : boundMethod(...args);
113
- });
114
- methodCache.set(prop, wrappedMethod);
115
- return wrappedMethod;
187
+ tracedMethods.set(methodName, traced);
116
188
  }
189
+ return Reflect.apply(traced, this, args);
190
+ };
191
+ Object.defineProperties(wrapper, {
192
+ name: { value: originalMethod.name, configurable: true },
193
+ length: { value: originalMethod.length, configurable: true }
117
194
  });
195
+ return wrapper;
118
196
  }
119
197
  function instrumentDurableObjectWithSentry(optionsCallback, DurableObjectClass) {
120
198
  return new Proxy(DurableObjectClass, {
121
199
  construct(target, [ctx, env], newTarget) {
122
- const { obj, options, context } = constructInstrumentedDurableObject(
200
+ const { obj, options, context, frameworkManagedMethods } = constructInstrumentedDurableObject(
123
201
  target,
124
202
  ctx,
125
203
  env,
126
204
  newTarget,
127
205
  optionsCallback
128
206
  );
129
- return finalizeWithRpcInstrumentation(obj, options, context);
207
+ return finalizeWithRpcInstrumentation(obj, options, context, frameworkManagedMethods);
130
208
  }
131
209
  });
132
210
  }
133
211
  function instrumentAgentWithSentry(optionsCallback, AgentClass) {
134
212
  return new Proxy(AgentClass, {
135
213
  construct(target, [ctx, env], newTarget) {
136
- const { obj, options, context } = constructInstrumentedDurableObject(
214
+ const { obj, options, context, frameworkManagedMethods } = constructInstrumentedDurableObject(
137
215
  target,
138
216
  ctx,
139
217
  env,
@@ -141,7 +219,7 @@ function instrumentAgentWithSentry(optionsCallback, AgentClass) {
141
219
  optionsCallback
142
220
  );
143
221
  instrumentCloudflareAgent(obj);
144
- return finalizeWithRpcInstrumentation(obj, options, context);
222
+ return finalizeWithRpcInstrumentation(obj, options, context, frameworkManagedMethods);
145
223
  }
146
224
  });
147
225
  }