@belvador/cloudflare 0.3.0 → 0.4.1

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.
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export type BelvadorEnv = {
19
19
  };
20
20
  /** Engine options for one Worker invocation (exported for tests and @belvador/tanstack-start). */
21
21
  export declare function workerEngineOptions(options: WorkerOptions | undefined, env: unknown, runtime?: "cloudflare" | "tanstack-start"): EngineCloudflareOptions;
22
+ /** Mechanism of an error from a rejected `ctx.waitUntil` promise (unhandled: nothing caught it). */
23
+ export declare const WAIT_UNTIL_MECHANISM = "auto.faas.cloudflare.wait_until";
22
24
  /** Shared with @belvador/tanstack-start so its server events carry its own SDK name. */
23
25
  export declare function wrapHandler<Env, H>(optionsCallback: (env: Env) => WorkerOptions | undefined, handler: H, runtime: "cloudflare" | "tanstack-start"): H;
24
26
  /**
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  // Built on the open-source Sentry JavaScript SDK (MIT), with Belvador's
3
3
  // privacy and sending rules. Needs the `nodejs_compat` (or `nodejs_als`)
4
4
  // compatibility flag so every request gets its own scope.
5
- import { consoleIntegration, dedupeIntegration, eventFiltersIntegration, fetchIntegration, linkedErrorsIntegration, requestDataIntegration, withSentry, } from "@sentry/cloudflare";
6
- import { TRACE_OPTION, buildEngineOptions, currentHooks, installTracePropagation, markHooked, noteClientAbort, runVerification, withIncomingTraceparent, } from "@belvador/core";
5
+ import { captureException, consoleIntegration, dedupeIntegration, eventFiltersIntegration, fetchIntegration, linkedErrorsIntegration, requestDataIntegration, withSentry, } from "@sentry/cloudflare";
6
+ import { OTEL_OPTION, TRACE_OPTION, buildEngineOptions, otelBridgeIntegration, currentHooks, installTracePropagation, markHooked, noteClientAbort, runVerification, withIncomingTraceparent, } from "@belvador/core";
7
7
  export { SDK_VERSION, addBreadcrumb, captureCheckIn, captureException, captureMessage, close, flush, heartbeat, withMonitor, getDiagnostics, lastEventId, setContext, setTag, setTags, setUser, withScope, } from "@belvador/core";
8
8
  function binding(env, name) {
9
9
  if (env === null || typeof env !== "object")
@@ -81,6 +81,9 @@ export function workerEngineOptions(options, env, runtime = "cloudflare") {
81
81
  integrations.push(fetchIntegration({ breadcrumbs: true, shouldCreateSpanForRequest: () => false }));
82
82
  if (base[TRACE_OPTION])
83
83
  installTracePropagation();
84
+ const otel = base[OTEL_OPTION];
85
+ if (otel)
86
+ integrations.push(otelBridgeIntegration(otel));
84
87
  return {
85
88
  ...base,
86
89
  // 0, not undefined: an undefined value would let SENTRY_TRACES_SAMPLE_RATE turn tracing on.
@@ -93,10 +96,55 @@ export function workerEngineOptions(options, env, runtime = "cloudflare") {
93
96
  }
94
97
  }
95
98
  const HANDLER_METHODS = ["fetch", "scheduled", "queue", "email", "tail"];
99
+ /** Every module handler receives `(input, env, ctx)`; ctx is the ExecutionContext. */
100
+ const CTX_ARG = 2;
101
+ /** Mechanism of an error from a rejected `ctx.waitUntil` promise (unhandled: nothing caught it). */
102
+ export const WAIT_UNTIL_MECHANISM = "auto.faas.cloudflare.wait_until";
103
+ /**
104
+ * The handler's own view of ctx: `waitUntil` reports a rejected promise as an unhandled
105
+ * error, then hands the runtime the same rejection, so Cloudflare still logs it and the
106
+ * promise keeps its meaning. Everything else is the real ctx. The engine keeps the
107
+ * original object, so its own flush is untouched; it waits (up to its 2 s flush timeout)
108
+ * for promises passed here, which is what lets a late rejection still be sent.
109
+ */
110
+ function reportingContext(ctx) {
111
+ if (ctx === null || typeof ctx !== "object")
112
+ return ctx;
113
+ try {
114
+ if (typeof ctx.waitUntil !== "function")
115
+ return ctx;
116
+ }
117
+ catch {
118
+ return ctx;
119
+ }
120
+ const waitUntil = (promise) => {
121
+ const real = Reflect.get(ctx, "waitUntil", ctx);
122
+ if (!isThenable(promise))
123
+ return real.call(ctx, promise);
124
+ return real.call(ctx, Promise.resolve(promise).then(undefined, (error) => {
125
+ try {
126
+ captureException(error, { mechanism: { type: WAIT_UNTIL_MECHANISM, handled: false } });
127
+ }
128
+ catch {
129
+ /* reporting must never change the app's behaviour */
130
+ }
131
+ throw error;
132
+ }));
133
+ };
134
+ return new Proxy(ctx, {
135
+ get(target, prop) {
136
+ if (prop === "waitUntil")
137
+ return waitUntil;
138
+ const value = Reflect.get(target, prop, target);
139
+ return typeof value === "function" ? value.bind(target) : value;
140
+ },
141
+ });
142
+ }
96
143
  /**
97
144
  * Marks each invocation of an object handler as running inside the wrapper (integration
98
- * health: `verifyIntegration` can tell), and notes a fetch that failed after its client
99
- * disconnected so the error counts as a cancellation. Class handlers pass through unchanged.
145
+ * health: `verifyIntegration` can tell), reports rejected `ctx.waitUntil` promises, and notes
146
+ * a fetch that failed after its client disconnected so the error counts as a cancellation.
147
+ * Class handlers pass through unchanged.
100
148
  */
101
149
  function marked(handler) {
102
150
  if (handler === null || typeof handler !== "object")
@@ -106,8 +154,9 @@ function marked(handler) {
106
154
  const fn = handler[name];
107
155
  if (typeof fn !== "function")
108
156
  continue;
109
- out[name] = function (...args) {
157
+ out[name] = function (...input) {
110
158
  markHooked("wrapper");
159
+ const args = input.length > CTX_ARG ? input.map((a, i) => (i === CTX_ARG ? reportingContext(a) : a)) : input;
111
160
  const signal = name === "fetch" ? signalOf(args[0]) : undefined;
112
161
  if (!signal)
113
162
  return fn.apply(handler, args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@belvador/cloudflare",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Belvador error tracking for Cloudflare Workers: per-request scope, errors only, private by default.",
5
5
  "license": "MIT",
6
6
  "author": "Belvador (https://belvador.dev)",
@@ -32,7 +32,7 @@
32
32
  "node": ">=18"
33
33
  },
34
34
  "dependencies": {
35
- "@belvador/core": "0.3.0",
35
+ "@belvador/core": "0.4.1",
36
36
  "@sentry/cloudflare": "10.76.2"
37
37
  },
38
38
  "publishConfig": {