@belvador/cloudflare 0.4.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,7 +2,7 @@
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";
5
+ import { captureException, consoleIntegration, dedupeIntegration, eventFiltersIntegration, fetchIntegration, linkedErrorsIntegration, requestDataIntegration, withSentry, } from "@sentry/cloudflare";
6
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) {
@@ -96,10 +96,55 @@ export function workerEngineOptions(options, env, runtime = "cloudflare") {
96
96
  }
97
97
  }
98
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
+ }
99
143
  /**
100
144
  * Marks each invocation of an object handler as running inside the wrapper (integration
101
- * health: `verifyIntegration` can tell), and notes a fetch that failed after its client
102
- * 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.
103
148
  */
104
149
  function marked(handler) {
105
150
  if (handler === null || typeof handler !== "object")
@@ -109,8 +154,9 @@ function marked(handler) {
109
154
  const fn = handler[name];
110
155
  if (typeof fn !== "function")
111
156
  continue;
112
- out[name] = function (...args) {
157
+ out[name] = function (...input) {
113
158
  markHooked("wrapper");
159
+ const args = input.length > CTX_ARG ? input.map((a, i) => (i === CTX_ARG ? reportingContext(a) : a)) : input;
114
160
  const signal = name === "fetch" ? signalOf(args[0]) : undefined;
115
161
  if (!signal)
116
162
  return fn.apply(handler, args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@belvador/cloudflare",
3
- "version": "0.4.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.4.0",
35
+ "@belvador/core": "0.4.1",
36
36
  "@sentry/cloudflare": "10.76.2"
37
37
  },
38
38
  "publishConfig": {