@timber-js/app 0.2.0-alpha.83 → 0.2.0-alpha.85
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/_chunks/{actions-Dg-ANYHb.js → actions-DLnUaR65.js} +2 -2
- package/dist/_chunks/{actions-Dg-ANYHb.js.map → actions-DLnUaR65.js.map} +1 -1
- package/dist/_chunks/{chunk-DYhsFzuS.js → chunk-BYIpzuS7.js} +7 -1
- package/dist/_chunks/{define-CZqDwhSu.js → define-Itxvcd7F.js} +2 -2
- package/dist/_chunks/{define-CZqDwhSu.js.map → define-Itxvcd7F.js.map} +1 -1
- package/dist/_chunks/{define-cookie-C2IkoFGN.js → define-cookie-BowvzoP0.js} +4 -4
- package/dist/_chunks/{define-cookie-C2IkoFGN.js.map → define-cookie-BowvzoP0.js.map} +1 -1
- package/dist/_chunks/{request-context-qMsWgy9C.js → request-context-CK5tZqIP.js} +3 -3
- package/dist/_chunks/{request-context-qMsWgy9C.js.map → request-context-CK5tZqIP.js.map} +1 -1
- package/dist/_chunks/{use-query-states-Lo_s_pw2.js → use-query-states-BiV5GJgm.js} +4 -1
- package/dist/_chunks/{use-query-states-Lo_s_pw2.js.map → use-query-states-BiV5GJgm.js.map} +1 -1
- package/dist/client/form.d.ts +4 -1
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/config-validation.d.ts +51 -0
- package/dist/config-validation.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1169 -51
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-404-page.d.ts +56 -0
- package/dist/plugins/dev-404-page.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +14 -11
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/dev-error-page.d.ts +58 -0
- package/dist/plugins/dev-error-page.d.ts.map +1 -0
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/dev-terminal-error.d.ts +28 -0
- package/dist/plugins/dev-terminal-error.d.ts.map +1 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +4 -0
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts +41 -0
- package/dist/routing/convention-lint.d.ts.map +1 -0
- package/dist/search-params/index.js +2 -2
- package/dist/server/action-client.d.ts +13 -5
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +9 -5
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +2 -2
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/form.tsx +10 -5
- package/src/client/use-query-states.ts +6 -0
- package/src/config-validation.ts +299 -0
- package/src/index.ts +17 -0
- package/src/plugins/dev-404-page.ts +418 -0
- package/src/plugins/dev-error-overlay.ts +165 -54
- package/src/plugins/dev-error-page.ts +536 -0
- package/src/plugins/dev-server.ts +63 -10
- package/src/plugins/dev-terminal-error.ts +217 -0
- package/src/plugins/entries.ts +3 -0
- package/src/plugins/fonts.ts +3 -2
- package/src/plugins/routing.ts +37 -5
- package/src/plugins/shims.ts +1 -0
- package/src/routing/convention-lint.ts +356 -0
- package/src/server/action-client.ts +17 -9
- package/src/server/fallback-error.ts +39 -88
- package/src/server/rsc-entry/index.ts +34 -2
|
@@ -140,13 +140,13 @@ export interface ActionBuilder<TCtx> {
|
|
|
140
140
|
/** Define the action body without input validation. */
|
|
141
141
|
action<TData>(
|
|
142
142
|
fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>
|
|
143
|
-
): ActionFn<
|
|
143
|
+
): ActionFn<TData, undefined>;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/** Builder after .schema() has been called. */
|
|
147
147
|
export interface ActionBuilderWithSchema<TCtx, TInput> {
|
|
148
148
|
/** Define the action body with validated input. */
|
|
149
|
-
action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<
|
|
149
|
+
action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData, TInput>;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
@@ -169,10 +169,18 @@ export interface ActionBuilderWithSchema<TCtx, TInput> {
|
|
|
169
169
|
export type InputHint<T> =
|
|
170
170
|
T extends Record<string, unknown> ? { [K in keyof T]: string | undefined } : T;
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
/**
|
|
173
|
+
* ActionFn — the callable returned by `createActionClient().action()`.
|
|
174
|
+
*
|
|
175
|
+
* Generic order: `<TData, TInput>` — TData first for backward compatibility.
|
|
176
|
+
* Previously ActionFn had a single `<TData>` generic, so existing code like
|
|
177
|
+
* `ActionFn<MyResult>` must still work with TData in the first position.
|
|
178
|
+
* See TIM-797.
|
|
179
|
+
*/
|
|
180
|
+
export type ActionFn<TData = unknown, TInput = unknown> = {
|
|
173
181
|
/** <form action={fn}> compatibility — React discards the return value. */
|
|
174
182
|
(formData: FormData): void;
|
|
175
|
-
/** Direct call: action(input) — optional when TInput is undefined (no-schema actions). */
|
|
183
|
+
/** Direct call: action(input) — optional when TInput is undefined/unknown (no-schema actions). */
|
|
176
184
|
(
|
|
177
185
|
...args: undefined extends TInput ? [input?: TInput] : [input: TInput]
|
|
178
186
|
): Promise<ActionResult<TData>>;
|
|
@@ -310,7 +318,7 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
310
318
|
function buildAction<TInput, TData>(
|
|
311
319
|
schema: ActionSchema<TInput> | undefined,
|
|
312
320
|
fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>
|
|
313
|
-
): ActionFn<
|
|
321
|
+
): ActionFn<TData, TInput> {
|
|
314
322
|
async function actionHandler(...args: unknown[]): Promise<ActionResult<TData>> {
|
|
315
323
|
try {
|
|
316
324
|
// Run middleware
|
|
@@ -401,7 +409,7 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
401
409
|
}
|
|
402
410
|
}
|
|
403
411
|
|
|
404
|
-
return actionHandler as ActionFn<
|
|
412
|
+
return actionHandler as ActionFn<TData, TInput>;
|
|
405
413
|
}
|
|
406
414
|
|
|
407
415
|
return {
|
|
@@ -409,14 +417,14 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
409
417
|
return {
|
|
410
418
|
action<TData>(
|
|
411
419
|
fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>
|
|
412
|
-
): ActionFn<
|
|
420
|
+
): ActionFn<TData, TInput> {
|
|
413
421
|
return buildAction(schema, fn);
|
|
414
422
|
},
|
|
415
423
|
};
|
|
416
424
|
},
|
|
417
425
|
action<TData>(
|
|
418
426
|
fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>
|
|
419
|
-
): ActionFn<
|
|
427
|
+
): ActionFn<TData, undefined> {
|
|
420
428
|
return buildAction(undefined, fn as (ctx: ActionContext<TCtx, unknown>) => Promise<TData>);
|
|
421
429
|
},
|
|
422
430
|
};
|
|
@@ -445,7 +453,7 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
445
453
|
export function validated<TInput, TData>(
|
|
446
454
|
schema: ActionSchema<TInput>,
|
|
447
455
|
handler: (input: TInput) => Promise<TData>
|
|
448
|
-
): ActionFn<
|
|
456
|
+
): ActionFn<TData, TInput> {
|
|
449
457
|
return createActionClient()
|
|
450
458
|
.schema(schema)
|
|
451
459
|
.action(async ({ input }) => handler(input));
|
|
@@ -31,10 +31,11 @@ export async function renderFallbackError(
|
|
|
31
31
|
isDev: boolean,
|
|
32
32
|
rootSegment: ManifestSegmentNode,
|
|
33
33
|
clientBootstrap: ClientBootstrapConfig,
|
|
34
|
-
globalError?: GlobalErrorFile
|
|
34
|
+
globalError?: GlobalErrorFile,
|
|
35
|
+
projectRoot?: string
|
|
35
36
|
): Promise<Response> {
|
|
36
37
|
if (isDev) {
|
|
37
|
-
return renderDevErrorPage(error);
|
|
38
|
+
return renderDevErrorPage(error, projectRoot);
|
|
38
39
|
}
|
|
39
40
|
// Lazy import to avoid loading error-renderer in the pipeline module
|
|
40
41
|
const { renderErrorPage } = await import('./rsc-entry/error-renderer.js');
|
|
@@ -75,89 +76,35 @@ export async function renderFallbackError(
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* Render a dev-mode 500 error page with error
|
|
79
|
+
* Render a dev-mode 500 error page with error details, source context,
|
|
80
|
+
* classified stack trace, and copy button.
|
|
79
81
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
+
* Dynamically imports the shared template from `plugins/dev-error-page.ts`
|
|
83
|
+
* so it is NOT pulled into production server bundles. The Vite client script
|
|
84
|
+
* is injected so the error overlay fires when the HMR WebSocket connects.
|
|
85
|
+
*
|
|
86
|
+
* Dev-only — the dynamic import has zero production cost.
|
|
82
87
|
*/
|
|
83
|
-
export function renderDevErrorPage(error: unknown): Response {
|
|
88
|
+
export async function renderDevErrorPage(error: unknown, projectRoot?: string): Promise<Response> {
|
|
84
89
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
85
|
-
const
|
|
86
|
-
const message = escapeHtml(err.message);
|
|
87
|
-
const stack = err.stack ? escapeHtml(err.stack) : '';
|
|
90
|
+
const root = projectRoot ?? process.cwd();
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.container { max-width: 800px; margin: 0 auto; }
|
|
106
|
-
.badge {
|
|
107
|
-
display: inline-block;
|
|
108
|
-
background: #e74c3c;
|
|
109
|
-
color: white;
|
|
110
|
-
font-size: 0.75rem;
|
|
111
|
-
font-weight: 700;
|
|
112
|
-
padding: 0.2rem 0.6rem;
|
|
113
|
-
border-radius: 4px;
|
|
114
|
-
text-transform: uppercase;
|
|
115
|
-
letter-spacing: 0.05em;
|
|
116
|
-
margin-bottom: 1rem;
|
|
117
|
-
}
|
|
118
|
-
h1 {
|
|
119
|
-
font-size: 1.5rem;
|
|
120
|
-
color: #ff6b6b;
|
|
121
|
-
margin-bottom: 0.5rem;
|
|
122
|
-
word-break: break-word;
|
|
123
|
-
}
|
|
124
|
-
.message {
|
|
125
|
-
font-size: 1.1rem;
|
|
126
|
-
color: #ccc;
|
|
127
|
-
margin-bottom: 1.5rem;
|
|
128
|
-
word-break: break-word;
|
|
129
|
-
}
|
|
130
|
-
.stack-container {
|
|
131
|
-
background: #16213e;
|
|
132
|
-
border: 1px solid #2a2a4a;
|
|
133
|
-
border-radius: 8px;
|
|
134
|
-
padding: 1rem;
|
|
135
|
-
overflow-x: auto;
|
|
136
|
-
}
|
|
137
|
-
.stack {
|
|
138
|
-
font-family: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
|
|
139
|
-
font-size: 0.8rem;
|
|
140
|
-
color: #a0a0c0;
|
|
141
|
-
white-space: pre-wrap;
|
|
142
|
-
word-break: break-all;
|
|
143
|
-
}
|
|
144
|
-
.hint {
|
|
145
|
-
margin-top: 1.5rem;
|
|
146
|
-
font-size: 0.85rem;
|
|
147
|
-
color: #666;
|
|
148
|
-
}
|
|
149
|
-
</style>
|
|
150
|
-
</head>
|
|
151
|
-
<body>
|
|
152
|
-
<div class="container">
|
|
153
|
-
<span class="badge">500 Internal Server Error</span>
|
|
154
|
-
<h1>${escapeHtml(title)}</h1>
|
|
155
|
-
<p class="message">${message}</p>
|
|
156
|
-
${stack ? `<div class="stack-container"><pre class="stack">${stack}</pre></div>` : ''}
|
|
157
|
-
<p class="hint">This error page is only shown in development.</p>
|
|
158
|
-
</div>
|
|
159
|
-
</body>
|
|
160
|
-
</html>`;
|
|
92
|
+
let html: string;
|
|
93
|
+
try {
|
|
94
|
+
// Dynamic import — keeps dev-error-page.ts and its transitive deps
|
|
95
|
+
// (dev-error-overlay.ts, @jridgewell/trace-mapping) out of production bundles.
|
|
96
|
+
const { generateDevErrorPage } = await import('../plugins/dev-error-page.js');
|
|
97
|
+
html = generateDevErrorPage(err, 'render', root);
|
|
98
|
+
// Inject Vite client script so the error overlay fires when HMR connects.
|
|
99
|
+
html = html.replace(
|
|
100
|
+
'</head>',
|
|
101
|
+
' <script type="module" src="/@vite/client"></script>\n</head>'
|
|
102
|
+
);
|
|
103
|
+
} catch {
|
|
104
|
+
// If the shared template fails (e.g., circular dep, missing module),
|
|
105
|
+
// fall back to a minimal error page.
|
|
106
|
+
html = minimalErrorPage(err);
|
|
107
|
+
}
|
|
161
108
|
|
|
162
109
|
return new Response(html, {
|
|
163
110
|
status: 500,
|
|
@@ -165,11 +112,15 @@ export function renderDevErrorPage(error: unknown): Response {
|
|
|
165
112
|
});
|
|
166
113
|
}
|
|
167
114
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Minimal fallback error page — used only if the shared template fails to load.
|
|
117
|
+
*/
|
|
118
|
+
function minimalErrorPage(err: Error): string {
|
|
119
|
+
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
120
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>500</title>
|
|
121
|
+
<script type="module" src="/@vite/client"></script></head>
|
|
122
|
+
<body style="font-family:system-ui;padding:2rem;background:#1a1a2e;color:#e0e0e0">
|
|
123
|
+
<h1 style="color:#ff6b6b">500 Internal Server Error</h1>
|
|
124
|
+
<pre style="color:#a0a0c0;white-space:pre-wrap">${esc(err.stack ?? err.message)}</pre>
|
|
125
|
+
</body></html>`;
|
|
175
126
|
}
|
|
@@ -247,7 +247,34 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
247
247
|
);
|
|
248
248
|
},
|
|
249
249
|
renderNoMatch: async (req: Request, responseHeaders: Headers) => {
|
|
250
|
-
|
|
250
|
+
const response = await renderNoMatchPage(
|
|
251
|
+
req,
|
|
252
|
+
manifest.root,
|
|
253
|
+
responseHeaders,
|
|
254
|
+
clientBootstrap
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// In dev mode, if the pipeline returned a bare 404 (no body — meaning
|
|
258
|
+
// no user-defined 404.tsx was found), replace it with a helpful dev
|
|
259
|
+
// page that lists available routes and suggests similar paths.
|
|
260
|
+
// Only for GET requests that accept HTML — HEAD/API/RSC clients
|
|
261
|
+
// should receive the bare 404 without an HTML body (TIM-793).
|
|
262
|
+
const acceptsHtml = (req.headers.get('accept') ?? '').includes('text/html');
|
|
263
|
+
if (isDev && !response.body && req.method === 'GET' && acceptsHtml) {
|
|
264
|
+
const { generateDev404Page, collectRoutes } = await import('../../plugins/dev-404-page.js');
|
|
265
|
+
const routes = collectRoutes(manifest.root);
|
|
266
|
+
const pathname = new URL(req.url).pathname;
|
|
267
|
+
const html = generateDev404Page(pathname, routes);
|
|
268
|
+
return new Response(html, {
|
|
269
|
+
status: 404,
|
|
270
|
+
headers: {
|
|
271
|
+
...Object.fromEntries(responseHeaders.entries()),
|
|
272
|
+
'content-type': 'text/html; charset=utf-8',
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return response;
|
|
251
278
|
},
|
|
252
279
|
interceptionRewrites: manifest.interceptionRewrites,
|
|
253
280
|
// Slow request threshold from timber.config.ts. Default 3000ms, 0 to disable.
|
|
@@ -276,7 +303,12 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
276
303
|
isDev,
|
|
277
304
|
manifest.root,
|
|
278
305
|
clientBootstrap,
|
|
279
|
-
manifest.globalError
|
|
306
|
+
manifest.globalError,
|
|
307
|
+
// Project root for dev error page frame classification.
|
|
308
|
+
// Not in runtimeConfig (TIM-787: leaked to client bundles).
|
|
309
|
+
// manifest.root is the resolved Vite root — correct even when
|
|
310
|
+
// CWD differs from project root (e.g., monorepo custom root) (TIM-807).
|
|
311
|
+
isDev ? manifest.root : undefined
|
|
280
312
|
),
|
|
281
313
|
// Auto-generated sitemap handler — enabled when sitemap.enabled is true
|
|
282
314
|
// and no user-authored sitemap exists at the app root.
|