@timber-js/app 0.2.0-alpha.42 → 0.2.0-alpha.43

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.
@@ -1 +1 @@
1
- {"version":3,"file":"slot-resolver.d.ts","sourceRoot":"","sources":["../../src/server/slot-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,KAAK,eAAe,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC;AAkHlE;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,UAAU,EACjB,CAAC,EAAE,eAAe,EAClB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CA0EpC"}
1
+ {"version":3,"file":"slot-resolver.d.ts","sourceRoot":"","sources":["../../src/server/slot-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,KAAK,eAAe,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC;AAkHlE;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,UAAU,EACjB,CAAC,EAAE,eAAe,EAClB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAyFpC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.42",
3
+ "version": "0.2.0-alpha.43",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -22,6 +22,7 @@ import { SlotAccessGate } from './access-gate.js';
22
22
  import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
23
23
  import type { InterceptionContext, RouteMatch } from './pipeline.js';
24
24
  import { DenySignal } from './primitives.js';
25
+ import { logRenderError } from './logger.js';
25
26
  import type { ManifestSegmentNode } from './route-matcher.js';
26
27
 
27
28
  type CreateElementFn = (...args: unknown[]) => React.ReactElement;
@@ -174,11 +175,19 @@ export async function resolveSlotElement(
174
175
  // §"Slot Access Failure = Graceful Degradation"
175
176
  const denyFallback = await renderDefaultFallback(slotNode, h);
176
177
 
177
- // Wrap the slot page to catch DenySignal (from notFound() or deny())
178
- // at the component level. This prevents the signal from reaching the
179
- // RSC onError callback and being tracked as a page-level denial, which
180
- // would cause the pipeline to replace the entire successful SSR response
181
- // with a deny page. Instead, the slot gracefully degrades.
178
+ // Wrap the slot page to catch ALL errors at the component level.
179
+ // This prevents errors from leaving unresolved Flight rows in the
180
+ // RSC stream when a slot component throws and the error propagates
181
+ // to React's Flight renderer, it may not emit a resolution row for
182
+ // the slot's lazy reference. The client's createFromReadableStream
183
+ // then throws "Connection closed" when the stream ends with pending
184
+ // references. By catching all errors here and returning a fallback,
185
+ // React sees a resolved component and emits a proper Flight row.
186
+ //
187
+ // DenySignal (from notFound() or deny()) returns the deny fallback.
188
+ // All other errors return the deny fallback or null — the slot
189
+ // gracefully degrades rather than breaking the entire page.
190
+ // See TIM-524.
182
191
  const SafeSlotPage = async (props: Record<string, unknown>) => {
183
192
  try {
184
193
  return await (SlotPage as (props: Record<string, unknown>) => unknown)(props);
@@ -186,7 +195,14 @@ export async function resolveSlotElement(
186
195
  if (error instanceof DenySignal) {
187
196
  return denyFallback;
188
197
  }
189
- throw error;
198
+ // Log the error but don't re-throw — returning fallback ensures
199
+ // the Flight row is resolved and the page hydrates correctly.
200
+ logRenderError({
201
+ method: '',
202
+ path: '',
203
+ error,
204
+ });
205
+ return denyFallback;
190
206
  }
191
207
  };
192
208