@timber-js/app 0.2.0-alpha.107 → 0.2.0-alpha.109
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 +22 -4
- package/dist/server/internal.js.map +1 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/server/param-coercion.ts +23 -4
- package/src/server/pipeline-outcome.ts +9 -0
- package/src/server/pipeline-phases.ts +26 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"param-coercion.d.ts","sourceRoot":"","sources":["../../src/server/param-coercion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"param-coercion.d.ts","sourceRoot":"","sources":["../../src/server/param-coercion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAMhD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE1E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-outcome.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline-outcome.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhE,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE1D,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAID;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,QAAQ,CAAC,CAgHnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-phases.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmBH,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;AAID;;;;;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;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"pipeline-phases.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmBH,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;AAID;;;;;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;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,QAAQ,CAAC,CAmPnB"}
|
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.109",
|
|
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",
|
|
@@ -16,6 +16,7 @@ import type { RouteMatch } from './pipeline.js';
|
|
|
16
16
|
import { sanitizeParamValue } from './pipeline-helpers.js';
|
|
17
17
|
import { loadModule } from './safe-load.js';
|
|
18
18
|
import { ParamCoercionError } from './route-element-builder.js';
|
|
19
|
+
import { isDebug } from './debug.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Run segment param coercion on the matched route's segments.
|
|
@@ -47,9 +48,15 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
47
48
|
try {
|
|
48
49
|
mod = await loadModule(segment.params);
|
|
49
50
|
} catch (err) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const message = `Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`;
|
|
52
|
+
if (isDebug()) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`[timber] Param coercion error: ${message}\n` +
|
|
55
|
+
` Segment: ${segment.segmentName}\n` +
|
|
56
|
+
` Params file: ${segment.params}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
throw new ParamCoercionError(message);
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
const segmentParamsDef = mod.segmentParams as
|
|
@@ -70,7 +77,19 @@ export async function coerceSegmentParams(match: RouteMatch): Promise<void> {
|
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
} catch (err) {
|
|
73
|
-
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
if (isDebug()) {
|
|
82
|
+
const rawKeys = Object.keys(match.segmentParams).join(', ');
|
|
83
|
+
console.warn(
|
|
84
|
+
`[timber] Param codec rejected values for segment "${segment.segmentName}"\n` +
|
|
85
|
+
` Error: ${message}\n` +
|
|
86
|
+
` Available raw params: { ${rawKeys} }\n` +
|
|
87
|
+
` Params file: ${segment.params}\n` +
|
|
88
|
+
` Hint: if some params are only present at deeper route depths,\n` +
|
|
89
|
+
` make them optional in defineSegmentParams().`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
throw new ParamCoercionError(message);
|
|
74
93
|
}
|
|
75
94
|
}
|
|
76
95
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from './logger.js';
|
|
26
26
|
import { markResponseFlushed } from './request-context.js';
|
|
27
27
|
import { RedirectSignal, DenySignal } from './primitives.js';
|
|
28
|
+
import { isDebug } from './debug.js';
|
|
28
29
|
import type { PipelineConfig, RouteMatch } from './pipeline.js';
|
|
29
30
|
|
|
30
31
|
// ─── Phase Outcome ─────────────────────────────────────────────────────────
|
|
@@ -116,6 +117,14 @@ export async function outcomeToResponse(
|
|
|
116
117
|
config.onPipelineError(denyRenderError, 'render');
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
if (isDebug()) {
|
|
121
|
+
console.warn(
|
|
122
|
+
`[timber] DenySignal(${outcome.signal.status}) from ${outcome.phase} phase — ` +
|
|
123
|
+
`no renderDenyFallback configured, returning bare ${outcome.signal.status} response\n` +
|
|
124
|
+
` Request: ${ctx.method} ${ctx.path}\n` +
|
|
125
|
+
` Add a not-found.tsx or error.tsx to render a custom deny page.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
119
128
|
return new Response(null, { status: outcome.signal.status, headers });
|
|
120
129
|
}
|
|
121
130
|
|
|
@@ -206,6 +206,13 @@ export async function handleRequest(
|
|
|
206
206
|
const url = new URL(req.url);
|
|
207
207
|
const result = canonicalize(url.pathname, stripTrailingSlash);
|
|
208
208
|
if (!result.ok) {
|
|
209
|
+
if (isDebug()) {
|
|
210
|
+
console.warn(
|
|
211
|
+
`[timber] URL canonicalization rejected ${method} ${url.pathname} — responding with ${result.status}\n` +
|
|
212
|
+
` This usually means the URL contains encoded separators (%2f, %5c),\n` +
|
|
213
|
+
` null bytes (%00), path traversal (..), or malformed percent-encoding.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
209
216
|
return new Response(null, { status: result.status });
|
|
210
217
|
}
|
|
211
218
|
const canonicalPathname = result.pathname;
|
|
@@ -226,6 +233,12 @@ export async function handleRequest(
|
|
|
226
233
|
|
|
227
234
|
const mod = await loadModule<{ default?: MetadataHandler }>(metaMatch.file);
|
|
228
235
|
if (typeof mod.default !== 'function') {
|
|
236
|
+
if (isDebug()) {
|
|
237
|
+
console.warn(
|
|
238
|
+
`[timber] Metadata route ${metaMatch.file} does not export a default function — responding with 500\n` +
|
|
239
|
+
` Metadata routes must export a default function that returns the metadata content.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
229
242
|
return new Response('Metadata route must export a default function', { status: 500 });
|
|
230
243
|
}
|
|
231
244
|
const handlerResult = await mod.default();
|
|
@@ -370,6 +383,19 @@ export async function handleRequest(
|
|
|
370
383
|
await coerceSegmentParams(match);
|
|
371
384
|
} catch (error) {
|
|
372
385
|
if (error instanceof ParamCoercionError) {
|
|
386
|
+
// Dev-time diagnostic: log param coercion failures so developers can
|
|
387
|
+
// see why a matched route 404'd. Silent coercion 404s are brutal to debug.
|
|
388
|
+
if (isDebug()) {
|
|
389
|
+
const segmentChain = match.segments.map((s) => s.segmentName || '/').join(' → ');
|
|
390
|
+
console.warn(
|
|
391
|
+
`[timber] Param coercion failed for ${method} ${canonicalPathname} — responding with 404\n` +
|
|
392
|
+
` Matched segments: ${segmentChain}\n` +
|
|
393
|
+
` Error: ${error.message}\n` +
|
|
394
|
+
` This usually means a params.ts codec rejected the URL params.\n` +
|
|
395
|
+
` Check that all fields in defineSegmentParams() are optional for params\n` +
|
|
396
|
+
` that don't appear at every route depth (e.g. year, month, day).`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
373
399
|
// For API routes (route.ts), return a bare 404 — not an HTML page.
|
|
374
400
|
// API consumers expect JSON/empty responses, not rendered HTML.
|
|
375
401
|
const leafSegment = match.segments[match.segments.length - 1];
|