@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.
- package/dist/server/internal.js +16 -0
- package/dist/server/internal.js.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/server/pipeline-phases.ts +19 -0
- package/src/server/route-handler.ts +7 -0
- package/src/server/rsc-entry/api-handler.ts +40 -8
|
@@ -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,
|
|
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;
|
|
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,
|
|
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.
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
/**
|