@timber-js/app 0.2.0-alpha.167 → 0.2.0-alpha.169
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-TSxpXLHJ.js → actions-O_LsyCE4.js} +3 -3
- package/dist/_chunks/{actions-TSxpXLHJ.js.map → actions-O_LsyCE4.js.map} +1 -1
- package/dist/_chunks/{cache-api-DzpQQOEx.js → cache-api-B-lhk9p4.js} +56 -11
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js → cli-schema-sync-B73L6pMq.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js.map → cli-schema-sync-B73L6pMq.js.map} +1 -1
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
- package/dist/_chunks/{logger-t3uxAmbX.js → logger-AWfuX-KJ.js} +2 -19
- package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
- package/dist/_chunks/{walkers-Cfwvl-UC.js → walkers-CoOC8Hga.js} +2 -2
- package/dist/_chunks/{walkers-Cfwvl-UC.js.map → walkers-CoOC8Hga.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +9 -2
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/json-lossy-check.d.ts +11 -0
- package/dist/cache/json-lossy-check.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +42 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -1
- package/dist/cache/stores/memory.d.ts +1 -1
- package/dist/cache/stores/memory.d.ts.map +1 -1
- package/dist/cache/stores/redis.d.ts +1 -1
- package/dist/cache/stores/redis.d.ts.map +1 -1
- package/dist/cache/stores/vercel.d.ts +1 -1
- package/dist/cache/stores/vercel.d.ts.map +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -1
- package/dist/cdn/fastly-purge.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/child-segment-context.d.ts +2 -2
- package/dist/client/child-segment-context.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +4 -3
- package/dist/client/internal.js.map +1 -1
- package/dist/client/router.d.ts +6 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -3
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-outlet.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -1
- package/dist/codec.js.map +1 -1
- package/dist/config-types.d.ts +16 -37
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/fonts/pipeline.d.ts +19 -0
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -84
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +27 -3
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/action-client.d.ts +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/body-limits.d.ts.map +1 -1
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/index.js +20 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +24 -40
- package/dist/server/internal.js.map +1 -1
- package/dist/server/middleware-runner.d.ts +0 -17
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +6 -3
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +18 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +14 -14
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +1 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +44 -74
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/shared/redirect-type.d.ts +2 -2
- package/dist/shared/redirect-type.d.ts.map +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/image.d.ts +120 -120
- package/dist/shims/image.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +112 -0
- package/docs/api/34-api-config.mdx +0 -26
- package/docs/learn/09-caching.mdx +121 -5
- package/docs/learn/12-client-navigation.mdx +9 -1
- package/docs/learn/13-configuration.mdx +6 -8
- package/docs/learn/14-deploying.mdx +18 -18
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +6 -6
- package/src/adapters/cloudflare-kv-cache.ts +27 -6
- package/src/cache/index.ts +1 -1
- package/src/cache/json-lossy-check.ts +75 -0
- package/src/cache/redis-handler.ts +65 -14
- package/src/cache/timber-cache.ts +10 -2
- package/src/client/browser-entry/index.ts +2 -0
- package/src/client/browser-entry/router-init.ts +2 -0
- package/src/client/router.ts +13 -3
- package/src/codec.ts +3 -1
- package/src/config-types.ts +16 -37
- package/src/config-validation.ts +7 -5
- package/src/fonts/pipeline.ts +32 -0
- package/src/fonts/transform.ts +30 -24
- package/src/plugins/adapter-build.ts +131 -13
- package/src/plugins/entries.ts +40 -43
- package/src/plugins/fonts.ts +30 -0
- package/src/plugins/server-bundle.ts +7 -15
- package/src/server/action-client.ts +1 -1
- package/src/server/middleware-runner.ts +0 -44
- package/src/server/param-coercion.ts +1 -0
- package/src/server/pipeline-phases.ts +11 -13
- package/src/server/pipeline.ts +51 -3
- package/src/server/route-element-builder.ts +10 -1
- package/src/server/rsc-entry/action-middleware-runner.ts +14 -14
- package/src/server/rsc-entry/index.ts +30 -40
- package/src/server/rsc-entry/render-route.ts +3 -1
- package/src/server/rsc-entry/wrap-action-dispatch.ts +109 -372
- package/dist/_chunks/cache-api-DzpQQOEx.js.map +0 -1
- package/dist/_chunks/logger-t3uxAmbX.js.map +0 -1
- package/dist/_chunks/tree-match-D2l830j2.js +0 -102
- package/dist/_chunks/tree-match-D2l830j2.js.map +0 -1
package/src/server/pipeline.ts
CHANGED
|
@@ -238,6 +238,29 @@ export interface PipelineConfig {
|
|
|
238
238
|
*/
|
|
239
239
|
match?: RouteMatch
|
|
240
240
|
) => Response | Promise<Response>;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Server action dispatch — called after proxy.ts runs for POST requests
|
|
244
|
+
* that look like server actions. Returns a Response if the request was
|
|
245
|
+
* handled as an action, or null to fall through to normal routing.
|
|
246
|
+
*
|
|
247
|
+
* `canonicalPath` is the already-decoded canonical pathname — the same
|
|
248
|
+
* value that `handleRequest` and `matchRoute` use. Do NOT re-derive it
|
|
249
|
+
* from `req.url` (which re-encodes).
|
|
250
|
+
*
|
|
251
|
+
* The `reenter` parameter is the full pipeline function (including proxy,
|
|
252
|
+
* ALS, tracing) for the no-JS validation rerender path — the synthetic
|
|
253
|
+
* GET flows through the complete pipeline.
|
|
254
|
+
*
|
|
255
|
+
* Moved into the pipeline (TIM-1213) so proxy.ts runs on action POSTs,
|
|
256
|
+
* matching the design doc contract: "proxy.ts runs on every request,
|
|
257
|
+
* no exclusions."
|
|
258
|
+
*/
|
|
259
|
+
dispatchAction?: (
|
|
260
|
+
req: Request,
|
|
261
|
+
canonicalPath: string,
|
|
262
|
+
reenter: (req: Request) => Promise<Response>
|
|
263
|
+
) => Promise<Response | null>;
|
|
241
264
|
}
|
|
242
265
|
|
|
243
266
|
// ─── Pipeline ──────────────────────────────────────────────────────────────
|
|
@@ -265,7 +288,11 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
265
288
|
// Logged with each request for diagnosing resource contention.
|
|
266
289
|
let activeRequests = 0;
|
|
267
290
|
|
|
268
|
-
|
|
291
|
+
// Self-reference for re-entry — the no-JS validation rerender path
|
|
292
|
+
// passes a synthetic GET back through the full pipeline (including
|
|
293
|
+
// proxy, ALS, tracing). The inner closure captures `pipelineFn` by
|
|
294
|
+
// name; by the time it's invoked, the const binding is initialized.
|
|
295
|
+
const pipelineFn = async (req: Request): Promise<Response> => {
|
|
269
296
|
const url = new URL(req.url);
|
|
270
297
|
const method = req.method;
|
|
271
298
|
const path = url.pathname;
|
|
@@ -325,6 +352,24 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
325
352
|
canonicalReq = new Request(canonicalUrl.toString(), req);
|
|
326
353
|
}
|
|
327
354
|
|
|
355
|
+
// Build the inner handler — runs after proxy, before route
|
|
356
|
+
// matching. Checks for server action POSTs and dispatches them
|
|
357
|
+
// without entering the route match → middleware → render chain.
|
|
358
|
+
// Falls through to handleRequest for normal route handling.
|
|
359
|
+
// TIM-1213: action dispatch is now INSIDE the pipeline so
|
|
360
|
+
// proxy.ts runs on every request, including action POSTs.
|
|
361
|
+
const innerHandler = async (): Promise<Response> => {
|
|
362
|
+
if (config.dispatchAction) {
|
|
363
|
+
const actionResult = await config.dispatchAction(
|
|
364
|
+
canonicalReq,
|
|
365
|
+
canonicalPath,
|
|
366
|
+
pipelineFn
|
|
367
|
+
);
|
|
368
|
+
if (actionResult) return actionResult;
|
|
369
|
+
}
|
|
370
|
+
return handleRequest(config, canonicalReq, method, canonicalPath, true);
|
|
371
|
+
};
|
|
372
|
+
|
|
328
373
|
let result: Response;
|
|
329
374
|
if (proxyResolver) {
|
|
330
375
|
const outcome = await runProxyPhase(
|
|
@@ -332,7 +377,8 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
332
377
|
proxyResolver,
|
|
333
378
|
canonicalReq,
|
|
334
379
|
method,
|
|
335
|
-
canonicalPath
|
|
380
|
+
canonicalPath,
|
|
381
|
+
innerHandler
|
|
336
382
|
);
|
|
337
383
|
result = await outcomeToResponse(config, outcome, {
|
|
338
384
|
req: canonicalReq,
|
|
@@ -340,7 +386,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
340
386
|
path: canonicalPath,
|
|
341
387
|
});
|
|
342
388
|
} else {
|
|
343
|
-
result = await
|
|
389
|
+
result = await innerHandler();
|
|
344
390
|
}
|
|
345
391
|
|
|
346
392
|
// Set response status on the root span before it ends —
|
|
@@ -427,4 +473,6 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
427
473
|
});
|
|
428
474
|
});
|
|
429
475
|
};
|
|
476
|
+
|
|
477
|
+
return pipelineFn;
|
|
430
478
|
}
|
|
@@ -408,6 +408,7 @@ export async function buildRouteElement(
|
|
|
408
408
|
// Skip OG auto-linking when the user already declared images in metadata.
|
|
409
409
|
// See design/16-metadata.md §"Auto-Linking"
|
|
410
410
|
const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);
|
|
411
|
+
const hasUserTwitterImage = Boolean(resolvedMetadata.twitter?.images);
|
|
411
412
|
const requestUrl = new URL(req.url);
|
|
412
413
|
const requestPathname = requestUrl.pathname;
|
|
413
414
|
// In dev mode, use the request origin so OG URLs resolve to localhost.
|
|
@@ -427,7 +428,7 @@ export async function buildRouteElement(
|
|
|
427
428
|
if (!convention) continue;
|
|
428
429
|
// Non-nestable routes only auto-link from root
|
|
429
430
|
if (!convention.nestable && segment.urlPath !== '/') continue;
|
|
430
|
-
// Skip auto-linking if user
|
|
431
|
+
// Skip auto-linking entirely if user declared openGraph.images (covers both og + twitter)
|
|
431
432
|
if (convention.type === 'opengraph-image' && hasUserOgImage) continue;
|
|
432
433
|
// Build the href using the actual request path (not the pattern with [param]).
|
|
433
434
|
// For nestable routes, the metadata route sits under the same resolved path
|
|
@@ -450,6 +451,14 @@ export async function buildRouteElement(
|
|
|
450
451
|
href = new URL(href, ogBase).toString();
|
|
451
452
|
}
|
|
452
453
|
for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {
|
|
454
|
+
// Skip auto-linked twitter:image when user declared twitter.images
|
|
455
|
+
if (
|
|
456
|
+
hasUserTwitterImage &&
|
|
457
|
+
autoLink.tag === 'meta' &&
|
|
458
|
+
'name' in autoLink &&
|
|
459
|
+
autoLink.name === 'twitter:image'
|
|
460
|
+
)
|
|
461
|
+
continue;
|
|
453
462
|
if (autoLink.tag === 'link') {
|
|
454
463
|
const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };
|
|
455
464
|
if (autoLink.type) attrs.type = autoLink.type;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Middleware
|
|
3
|
-
* chain
|
|
4
|
-
*
|
|
2
|
+
* Middleware runner for revalidation — runs a route's `middleware.ts`
|
|
3
|
+
* chain inside its own `runWithRequestContext` scope and returns a
|
|
4
|
+
* `MiddlewareOutcome` value the caller can translate.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* so it can be unit-tested in isolation. It does not import virtual
|
|
10
|
-
* modules and does not call into the action handler — it owns exactly
|
|
11
|
-
* the "run middleware once" step and nothing else.
|
|
6
|
+
* Used by the revalidation renderer (TIM-996) to run the TARGET route's
|
|
7
|
+
* middleware before building the revalidated element tree, ensuring that
|
|
8
|
+
* `revalidatePath('/admin')` from a public action honors admin's auth.
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* NOT used for action dispatch itself — per-segment middleware does not
|
|
11
|
+
* run on action POSTs (TIM-1134). Actions are dispatched by action ID,
|
|
12
|
+
* not route segment, so `proxy.ts` and `createActionClient({ middleware })`
|
|
13
|
+
* are the correct auth mechanisms.
|
|
16
14
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* The function is intentionally a free function (not a closure over deps)
|
|
16
|
+
* so it can be unit-tested in isolation. Failure modes are encoded as
|
|
17
|
+
* outcome variants rather than thrown so the caller never has to wrap
|
|
18
|
+
* this in try/catch.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { getCookiesForSsr, getSetCookieHeaders } from '../cookie-context.js';
|
|
@@ -43,7 +43,7 @@ import { loadCdnPurgeHandler } from 'virtual:timber-cache-handler';
|
|
|
43
43
|
// @ts-expect-error — virtual module provided by timber-routing plugin
|
|
44
44
|
import globalSchemaCodecs from 'virtual:timber-schema';
|
|
45
45
|
|
|
46
|
-
import { wrapPipelineWithActionDispatch } from './wrap-action-dispatch.js';
|
|
46
|
+
import { wrapPipelineWithActionDispatch, buildActionDispatcher } from './wrap-action-dispatch.js';
|
|
47
47
|
import type { BodyLimitsConfig } from '../body-limits.js';
|
|
48
48
|
import type { BuildManifest } from '../build-manifest.js';
|
|
49
49
|
import { resolveLogMode } from '../../dev-tools/logger.js';
|
|
@@ -199,6 +199,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
199
199
|
| { disabled: boolean; enableHMRInDev: boolean }
|
|
200
200
|
| undefined;
|
|
201
201
|
const clientJsDisabled = clientJsConfig?.disabled ?? false;
|
|
202
|
+
const clientSegmentCache =
|
|
203
|
+
((runtimeConfig as Record<string, unknown>).clientSegmentCache as boolean) ?? false;
|
|
202
204
|
const clientBootstrap = buildClientScripts({
|
|
203
205
|
...runtimeConfig,
|
|
204
206
|
clientJavascript: clientJsConfig ?? { disabled: false, enableHMRInDev: false },
|
|
@@ -295,6 +297,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
295
297
|
{
|
|
296
298
|
clientBootstrap,
|
|
297
299
|
clientJsDisabled,
|
|
300
|
+
clientSegmentCache,
|
|
298
301
|
rootSegment: manifest.root,
|
|
299
302
|
buildManifest: typedBuildManifest,
|
|
300
303
|
globalError: manifest.globalError,
|
|
@@ -386,19 +389,36 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
386
389
|
// and no user-authored sitemap exists at the app root.
|
|
387
390
|
// See design/16-metadata.md §"Auto-generated Sitemap"
|
|
388
391
|
autoSitemapHandler: createAutoSitemapHandler(manifest.root, runtimeConfig),
|
|
392
|
+
|
|
393
|
+
// Server action dispatch — runs after proxy.ts, before route matching
|
|
394
|
+
// (TIM-1213). Moved into the pipeline so proxy.ts runs on action POSTs,
|
|
395
|
+
// matching the design doc: "proxy.ts runs on every request, no exclusions."
|
|
396
|
+
// Per-segment `middleware.ts` does NOT run on action POSTs (TIM-1134).
|
|
397
|
+
dispatchAction: buildActionDispatcher({
|
|
398
|
+
csrfConfig: {
|
|
399
|
+
csrf: runtimeConfig.csrf,
|
|
400
|
+
allowedOrigins: (runtimeConfig as Record<string, unknown>).allowedOrigins as
|
|
401
|
+
| string[]
|
|
402
|
+
| undefined,
|
|
403
|
+
},
|
|
404
|
+
bodyLimits: (runtimeConfig as Record<string, unknown>).limits as BodyLimitsConfig['limits'],
|
|
405
|
+
sensitiveFields: formsConfig?.stripSensitiveFields,
|
|
406
|
+
matchRoute,
|
|
407
|
+
buildRevalidateRenderer: createRevalidateRendererFactory({
|
|
408
|
+
matchRoute,
|
|
409
|
+
coerceSegmentParams,
|
|
410
|
+
buildManifest: buildManifest as BuildManifest,
|
|
411
|
+
}),
|
|
412
|
+
}),
|
|
389
413
|
};
|
|
390
414
|
|
|
391
415
|
const pipeline = createPipeline(pipelineConfig);
|
|
392
416
|
|
|
393
|
-
// Wrap the pipeline to enforce CSRF at the request boundary
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
// design/08-forms-and-actions.md §"Middleware for Server Actions".
|
|
399
|
-
//
|
|
400
|
-
// CSRF validation lives in the wrapper (not inside the action handler) so
|
|
401
|
-
// it covers route.ts API handlers as well as server actions. See LOCAL-773.
|
|
417
|
+
// Wrap the pipeline to enforce CSRF at the request boundary.
|
|
418
|
+
// CSRF validation lives in the wrapper (not inside the pipeline) so it
|
|
419
|
+
// runs before proxy.ts and covers every unsafe-method request. Server
|
|
420
|
+
// action dispatch has moved into the pipeline (TIM-1213) so proxy.ts
|
|
421
|
+
// runs on action POSTs. See LOCAL-773.
|
|
402
422
|
const csrfConfig = {
|
|
403
423
|
csrf: runtimeConfig.csrf,
|
|
404
424
|
allowedOrigins: (runtimeConfig as Record<string, unknown>).allowedOrigins as
|
|
@@ -406,38 +426,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
406
426
|
| undefined,
|
|
407
427
|
};
|
|
408
428
|
|
|
409
|
-
const actionsConfig = (runtimeConfig as Record<string, unknown>).actions as
|
|
410
|
-
| { runMiddleware?: boolean }
|
|
411
|
-
| undefined;
|
|
412
|
-
|
|
413
429
|
const handler = wrapPipelineWithActionDispatch(pipeline, {
|
|
414
430
|
csrfConfig,
|
|
415
|
-
bodyLimits: (runtimeConfig as Record<string, unknown>).limits as BodyLimitsConfig['limits'],
|
|
416
|
-
sensitiveFields: formsConfig?.stripSensitiveFields,
|
|
417
|
-
matchRoute,
|
|
418
|
-
coerceSegmentParams,
|
|
419
|
-
renderDenyFallback: pipelineConfig.renderDenyFallback,
|
|
420
|
-
runMiddleware: actionsConfig?.runMiddleware,
|
|
421
|
-
// Thread the pipeline's `stripTrailingSlash` setting into the wrapper
|
|
422
|
-
// so its canonicalization step (applied before `matchRoute` for
|
|
423
|
-
// action POSTs) matches the page pipeline exactly. Defaults to `true`
|
|
424
|
-
// both here and inside the wrapper.
|
|
425
|
-
stripTrailingSlash: pipelineConfig.stripTrailingSlash,
|
|
426
|
-
// Revalidation renderer — runs the target route's `middleware.ts`
|
|
427
|
-
// chain BEFORE building the element tree so a server action that
|
|
428
|
-
// revalidates a protected path honors that path's auth, tenant
|
|
429
|
-
// isolation, and request-header injection. Closes the silent-bypass
|
|
430
|
-
// bug where `revalidatePath('/admin')` from a public action would
|
|
431
|
-
// serialize the admin RSC tree without ever consulting admin's
|
|
432
|
-
// middleware. Also validates the user-supplied `path` as a local
|
|
433
|
-
// root-relative URL to reject `revalidatePath('//evil.com/admin')`
|
|
434
|
-
// and similar scheme/authority injections. See TIM-996 and
|
|
435
|
-
// design/08-forms-and-actions.md §"revalidatePath()".
|
|
436
|
-
buildRevalidateRenderer: createRevalidateRendererFactory({
|
|
437
|
-
matchRoute,
|
|
438
|
-
coerceSegmentParams,
|
|
439
|
-
buildManifest: buildManifest as BuildManifest,
|
|
440
|
-
}),
|
|
441
431
|
});
|
|
442
432
|
|
|
443
433
|
return { handler, pipelineConfig };
|
|
@@ -57,6 +57,7 @@ import { callSsr } from './ssr-bridge.js';
|
|
|
57
57
|
export interface RenderRouteDeps {
|
|
58
58
|
clientBootstrap: ClientBootstrapConfig;
|
|
59
59
|
clientJsDisabled: boolean;
|
|
60
|
+
clientSegmentCache: boolean;
|
|
60
61
|
rootSegment: ManifestSegmentNode;
|
|
61
62
|
buildManifest: BuildManifest;
|
|
62
63
|
globalError?: { load: () => Promise<unknown>; filePath: string };
|
|
@@ -95,7 +96,8 @@ export async function renderRoute(
|
|
|
95
96
|
// skips re-rendering those layouts for a smaller, faster RSC payload.
|
|
96
97
|
// Only used for RSC requests — HTML requests always get a full render.
|
|
97
98
|
// See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
98
|
-
const clientStateTree =
|
|
99
|
+
const clientStateTree =
|
|
100
|
+
deps.clientSegmentCache && isRscPayloadRequest(req) ? parseClientStateTree(req) : null;
|
|
99
101
|
|
|
100
102
|
// Build the React element tree — runs access checks eagerly to gate
|
|
101
103
|
// metadata resolution (TIM-1027), then loads modules and resolves
|