@timber-js/app 0.2.0-alpha.136 → 0.2.0-alpha.137

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":"pipeline-phases.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoBH,OAAO,EAA2C,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEpG,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKrF,UAAU,aAAa;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAwBD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAavB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,YAAY,CAAC,CA6DvB;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,EAAE,iBAAiB,EAAE,YAAY,EAAE,EAAE,aAAa,GACjD,OAAO,CAAC,YAAY,CAAC,CAkBvB;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,CAAC,QAAQ,CAAC,CAwRnB"}
1
+ {"version":3,"file":"pipeline-phases.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoBH,OAAO,EAA2C,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEpG,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKrF,UAAU,aAAa;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAwBD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAmBvB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,YAAY,CAAC,CA6DvB;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,EAAE,iBAAiB,EAAE,YAAY,EAAE,EAAE,aAAa,GACjD,OAAO,CAAC,YAAY,CAAC,CAkBvB;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,CAAC,QAAQ,CAAC,CAqSnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
1
+ {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAO/D;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,eAAe,EAAE,OAAO,GACvB,OAAO,CAAC,QAAQ,CAAC,CAqDnB"}
1
+ {"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAO/D;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,eAAe,EAAE,OAAO,GACvB,OAAO,CAAC,QAAQ,CAAC,CAsDnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.136",
3
+ "version": "0.2.0-alpha.137",
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",
@@ -88,6 +88,12 @@ export async function runProxyPhase(
88
88
  );
89
89
  return { kind: 'response', phase: 'proxy', response };
90
90
  } catch (error) {
91
+ if (error instanceof RedirectSignal) {
92
+ return { kind: 'redirect', phase: 'proxy', signal: error };
93
+ }
94
+ if (error instanceof DenySignal) {
95
+ return { kind: 'deny', phase: 'proxy', signal: error };
96
+ }
91
97
  return { kind: 'error', phase: 'proxy', error };
92
98
  }
93
99
  }
@@ -313,6 +319,19 @@ export async function handleRequest(
313
319
  headers: { 'Content-Type': `${contentType}; charset=utf-8` },
314
320
  });
315
321
  } catch (error) {
322
+ // Control-flow signals from the metadata handler (e.g. a sitemap that
323
+ // calls deny(404) for an unknown tenant, or a dynamic icon that
324
+ // redirects to a CDN-hosted asset) translate to proper HTTP responses
325
+ // instead of being logged as 500s.
326
+ if (error instanceof RedirectSignal) {
327
+ return new Response(null, {
328
+ status: error.status,
329
+ headers: { Location: error.location },
330
+ });
331
+ }
332
+ if (error instanceof DenySignal) {
333
+ return new Response(null, { status: error.status });
334
+ }
316
335
  logRenderError({ method, path, error });
317
336
  if (config.onPipelineError && error instanceof Error)
318
337
  config.onPipelineError(error, 'metadata-route');
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type { RouteContext } from './types.js';
12
12
  import { logRouteError } from './logger.js';
13
+ import { DenySignal, RedirectSignal } from './primitives.js';
13
14
 
14
15
  // ─── Types ───────────────────────────────────────────────────────────────
15
16
 
@@ -123,6 +124,12 @@ async function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Res
123
124
  const res = await handler(ctx);
124
125
  return mergeResponseHeaders(res, ctx.headers);
125
126
  } catch (error) {
127
+ // Control-flow signals — propagate to the API route dispatcher so deny()
128
+ // gets the JSON status-file chain and redirect() gets a proper 3xx with
129
+ // Location. Not errors; don't log them.
130
+ if (error instanceof DenySignal || error instanceof RedirectSignal) {
131
+ throw error;
132
+ }
126
133
  logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });
127
134
  return new Response(null, { status: 500 });
128
135
  }
@@ -61,13 +61,8 @@ export async function handleApiRoute(
61
61
  }
62
62
  );
63
63
  } catch (error) {
64
- if (error instanceof DenySignal) {
65
- return renderApiDeny(error, segments, responseHeaders);
66
- }
67
- if (error instanceof RedirectSignal) {
68
- responseHeaders.set('Location', error.location);
69
- return new Response(null, { status: error.status, headers: responseHeaders });
70
- }
64
+ const signalResponse = await translateApiSignal(error, req, segments, responseHeaders);
65
+ if (signalResponse) return signalResponse;
71
66
  throw error;
72
67
  }
73
68
  }
@@ -80,7 +75,44 @@ export async function handleApiRoute(
80
75
  // the request-context ALS that the pipeline already populated.
81
76
  const routeMod = await loadModule<RouteModule>(leaf.route!);
82
77
  const ctx: RouteContext = { req, headers: responseHeaders };
83
- return handleRouteRequest(routeMod, ctx);
78
+ try {
79
+ return await handleRouteRequest(routeMod, ctx);
80
+ } catch (error) {
81
+ const signalResponse = await translateApiSignal(error, req, segments, responseHeaders);
82
+ if (signalResponse) return signalResponse;
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Translate a control-flow signal thrown from access.ts or a route.ts
89
+ * method handler into the proper API response: DenySignal → JSON status-file
90
+ * chain via `renderApiDeny`, RedirectSignal → bare 3xx with Location header.
91
+ *
92
+ * For HEAD requests, strip the deny body to preserve HEAD semantics — the
93
+ * normal HEAD path in `handleRouteRequest` strips bodies after the handler
94
+ * returns, but signals bypass that codepath.
95
+ *
96
+ * Returns `null` for non-signal errors so the caller knows to rethrow.
97
+ */
98
+ async function translateApiSignal(
99
+ error: unknown,
100
+ req: Request,
101
+ segments: ManifestSegmentNode[],
102
+ responseHeaders: Headers
103
+ ): Promise<Response | null> {
104
+ if (error instanceof DenySignal) {
105
+ const res = await renderApiDeny(error, segments, responseHeaders);
106
+ if (req.method.toUpperCase() === 'HEAD') {
107
+ return new Response(null, { status: res.status, headers: res.headers });
108
+ }
109
+ return res;
110
+ }
111
+ if (error instanceof RedirectSignal) {
112
+ responseHeaders.set('Location', error.location);
113
+ return new Response(null, { status: error.status, headers: responseHeaders });
114
+ }
115
+ return null;
84
116
  }
85
117
 
86
118
  /**