@sproutboat/runtime 0.13.1 → 0.13.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sproutboat/runtime
2
2
 
3
+ ## 0.13.2
4
+
5
+ ### Patch Changes
6
+
7
+ - dc6539c: Log uncaught handler exceptions to stderr before answering 500, as `Uncaught (in fetch) Error: ...` (and `scheduled` / `alarm` for those triggers). A thrown or rejected handler used to produce a bare 500 with nothing logged, which hid baronunread/sproutboat#168 entirely.
8
+
3
9
  ## 0.13.1
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/runtime",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1993,27 +1993,43 @@ async function __sbFetchWithDrain(res, tasks) {
1993
1993
  let r;
1994
1994
  try {
1995
1995
  r = res && __sbIsFn(res.then) ? await res : res;
1996
- } catch {
1996
+ } catch (error) {
1997
1997
  // #179 — a rejected handler promise must not take the process down with it.
1998
- r = __sbErrorResponse();
1998
+ r = __sbErrorResponse(error);
1999
1999
  }
2000
2000
  await __sbDrainWaitUntil(tasks);
2001
2001
  return r;
2002
2002
  }
2003
2003
 
2004
2004
  // #179 — turn an uncaught handler exception into a 500 instead of letting it
2005
- // propagate past __sbEntry and crash the whole server.
2006
- function __sbErrorResponse() {
2005
+ // propagate past __sbEntry and crash the whole server. Logged first, the way a
2006
+ // Worker logs "Uncaught Error: ...": a swallowed exception left a 500 with
2007
+ // nothing on stderr, which hid #168 entirely.
2008
+ function __sbErrorResponse(error) {
2009
+ __sbLogUncaught("fetch", error);
2007
2010
  return new Response("Internal Server Error", { status: 500 });
2008
2011
  }
2009
2012
 
2013
+ function __sbLogUncaught(where, error) {
2014
+ try {
2015
+ let text = "";
2016
+ if (error && error.stack) text = String(error.stack);
2017
+ else if (error && error.message != null) text = (error.name || "Error") + ": " + error.message;
2018
+ else text = String(error);
2019
+ console.error("Uncaught (in " + where + ") " + text);
2020
+ } catch {
2021
+ /* logging must never turn a handled error into a crash */
2022
+ }
2023
+ }
2024
+
2010
2025
  // Shared by alarm and scheduled: both reply with an empty 204 once their
2011
2026
  // handler (and anything it queued via `waitUntil`) has settled.
2012
2027
  async function __sb204WithDrain(res, tasks) {
2013
2028
  try {
2014
2029
  if (res && __sbIsFn(res.then)) await res;
2015
- } catch {
2030
+ } catch (error) {
2016
2031
  // #179 — same rule as fetch: a rejected scheduled/alarm promise must not crash the process.
2032
+ __sbLogUncaught("scheduled/alarm", error);
2017
2033
  }
2018
2034
  await __sbDrainWaitUntil(tasks);
2019
2035
  return new Response("", { status: 204 });
@@ -2082,10 +2098,10 @@ globalThis.__sbEntry = function (handlers, request) {
2082
2098
  __res = globalThis.__sbCompat >= "2026-09-28"
2083
2099
  ? handlers.fetch(request, globalThis.env, ctx)
2084
2100
  : handlers.fetch(request, ctx);
2085
- } catch {
2101
+ } catch (error) {
2086
2102
  // #179 — a synchronous throw anywhere in the handler must not take the
2087
2103
  // whole process (and every other in-flight request) down with it.
2088
- return __sbErrorResponse();
2104
+ return __sbErrorResponse(error);
2089
2105
  }
2090
2106
  // Same promise-identity rule as above applies to `__sbFetchWithDrain`'s
2091
2107
  // own return, which is why it's tail-called rather than chained on here.
@@ -2112,8 +2128,9 @@ globalThis.__sbEntry = function (handlers, request) {
2112
2128
  __sres = globalThis.__sbCompat >= "2026-09-28"
2113
2129
  ? handlers.scheduled(event, globalThis.env, ctx)
2114
2130
  : handlers.scheduled(event, ctx);
2115
- } catch {
2131
+ } catch (error) {
2116
2132
  // #179 — same rule as fetch: don't let a throw here crash the process.
2133
+ __sbLogUncaught("scheduled", error);
2117
2134
  return new Response("", { status: 204 });
2118
2135
  }
2119
2136
  if (__sbTasks.length || (__sres && __sbIsFn(__sres.then))) return __sb204WithDrain(__sres, __sbTasks);
@@ -2146,8 +2163,9 @@ globalThis.__sbEntry = function (handlers, request) {
2146
2163
  let __ares;
2147
2164
  try {
2148
2165
  __ares = inst.alarm();
2149
- } catch {
2166
+ } catch (error) {
2150
2167
  // #179 — same rule as fetch: don't let a throw here crash the process.
2168
+ __sbLogUncaught("alarm", error);
2151
2169
  return new Response("", { status: 204 });
2152
2170
  }
2153
2171
  // Was fire-and-forget before: an async `alarm()` and any `state.waitUntil()`