@timber-js/app 0.2.0-alpha.158 → 0.2.0-alpha.159
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-BA5tIpVu.js → actions-CBbP1hHO.js} +3 -3
- package/dist/_chunks/{actions-BA5tIpVu.js.map → actions-CBbP1hHO.js.map} +1 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js → build-output-helper-DF5uScqS.js} +4 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js.map → build-output-helper-DF5uScqS.js.map} +1 -1
- package/dist/_chunks/cache-api-DsWEYwVZ.js +1120 -0
- package/dist/_chunks/cache-api-DsWEYwVZ.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js → cli-schema-sync-Bivx4Rn7.js} +4 -3
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js.map → cli-schema-sync-Bivx4Rn7.js.map} +1 -1
- package/dist/_chunks/dist-BA3u1z3W.js +423 -0
- package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
- package/dist/_chunks/{logger-C5v1IMuh.js → logger-CrRmZMlo.js} +81 -56
- package/dist/_chunks/logger-CrRmZMlo.js.map +1 -0
- package/dist/_chunks/{plugin-context-DUl3dnCF.js → plugin-context-DeAxFRMq.js} +3 -36
- package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
- package/dist/_chunks/{walkers-D5jCGPum.js → walkers-DXbJkeCY.js} +217 -19
- package/dist/_chunks/walkers-DXbJkeCY.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare.d.ts +28 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +143 -9
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.js +1 -1
- package/dist/cache/cache-api.d.ts +2 -0
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/handler-store.d.ts +2 -0
- package/dist/cache/handler-store.d.ts.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +3 -186
- package/dist/cache/redis-handler.d.ts +17 -5
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/store.d.ts +53 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.js +29 -0
- package/dist/cache/stores/cloudflare-kv.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +42 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +90 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/redis.d.ts +69 -0
- package/dist/cache/stores/redis.d.ts.map +1 -0
- package/dist/cache/stores/redis.js +35 -0
- package/dist/cache/stores/redis.js.map +1 -0
- package/dist/cache/stores/vercel.d.ts +36 -0
- package/dist/cache/stores/vercel.d.ts.map +1 -0
- package/dist/cache/stores/vercel.js +33 -0
- package/dist/cache/stores/vercel.js.map +1 -0
- package/dist/cache/tag-aware-handler.d.ts +51 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -0
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts +23 -0
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
- package/dist/cdn/fastly-purge.d.ts +25 -0
- package/dist/cdn/fastly-purge.d.ts.map +1 -0
- package/dist/cdn/headers.d.ts +82 -0
- package/dist/cdn/headers.d.ts.map +1 -0
- package/dist/cdn/index.d.ts +5 -0
- package/dist/cdn/index.d.ts.map +1 -0
- package/dist/cdn/purge-store.d.ts +23 -0
- package/dist/cdn/purge-store.d.ts.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/history.d.ts +0 -3
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +11 -86
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +1 -3
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/router.d.ts +1 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +1 -11
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -3
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/config-types.d.ts +3 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +15 -11
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/browser-logs.d.ts +28 -72
- package/dist/dev-tools/browser-logs.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -1
- package/dist/dev-tools/terminal.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts +4 -0
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1269 -359
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +36 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +21 -0
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +5 -14
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/callsite-ast.d.ts +95 -0
- package/dist/plugins/callsite-ast.d.ts.map +1 -0
- package/dist/plugins/callsite-jsx.d.ts +35 -0
- package/dist/plugins/callsite-jsx.d.ts.map +1 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/prebuilt-capture.d.ts +60 -0
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts +82 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -0
- package/dist/plugins/prerender-sugar.d.ts +42 -0
- package/dist/plugins/prerender-sugar.d.ts.map +1 -0
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +56 -0
- package/dist/routing/export-detect.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/cookie-parsing.d.ts +15 -1
- package/dist/server/cookie-parsing.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +6 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +563 -74
- package/dist/server/internal.js.map +1 -1
- package/dist/server/prebuilt/cache-key.d.ts +58 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +57 -0
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
- package/dist/server/prebuilt/overlay.d.ts +100 -0
- package/dist/server/prebuilt/overlay.d.ts.map +1 -0
- package/dist/server/prebuilt/payload-source.d.ts +75 -0
- package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
- package/dist/server/prebuilt/synthetic-store.d.ts +64 -0
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts +110 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -0
- package/dist/server/prebuilt-runtime.d.ts +101 -0
- package/dist/server/prebuilt-runtime.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +10 -8
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +10 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- 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/rsc-payload.d.ts +3 -4
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts +6 -6
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +37 -3
- package/dist/server/tracing.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +10 -4
- package/docs/learn/00-introduction.mdx +8 -13
- package/docs/learn/02-pages-and-layouts.mdx +9 -0
- package/docs/learn/06-forms-and-actions.mdx +0 -4
- package/docs/learn/07-typed-routes.mdx +127 -0
- package/docs/learn/09-caching.mdx +12 -1
- package/docs/learn/10-middleware.mdx +1 -0
- package/docs/learn/11-error-handling.mdx +9 -0
- package/docs/learn/13-configuration.mdx +28 -0
- package/docs/learn/14-deploying.mdx +52 -0
- package/docs/more/06-instrumentation.mdx +27 -1
- package/docs/more/07-security.mdx +8 -2
- package/docs/more/08-developer-experience.mdx +134 -0
- package/package.json +31 -2
- package/src/adapters/build-output-helper.ts +10 -0
- package/src/adapters/cloudflare.ts +178 -4
- package/src/cache/cache-api.ts +60 -0
- package/src/cache/handler-store.ts +8 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/redis-handler.ts +25 -13
- package/src/cache/singleflight.ts +11 -11
- package/src/cache/store.ts +71 -0
- package/src/cache/stores/cloudflare-kv.ts +52 -0
- package/src/cache/stores/memory.ts +125 -0
- package/src/cache/stores/redis.ts +97 -0
- package/src/cache/stores/vercel.ts +59 -0
- package/src/cache/tag-aware-handler.ts +233 -0
- package/src/cache/timber-cache.ts +17 -7
- package/src/cdn/cloudflare-purge.ts +50 -0
- package/src/cdn/fastly-purge.ts +63 -0
- package/src/cdn/headers.ts +127 -0
- package/src/cdn/index.ts +16 -0
- package/src/cdn/purge-store.ts +34 -0
- package/src/client/browser-entry/action-dispatch.ts +1 -10
- package/src/client/browser-entry/post-hydration.ts +0 -1
- package/src/client/browser-entry/router-init.ts +0 -5
- package/src/client/history.ts +0 -4
- package/src/client/navigation-api.ts +5 -11
- package/src/client/router.ts +9 -43
- package/src/client/rsc-fetch.ts +5 -27
- package/src/client/segment-cache.ts +1 -4
- package/src/client/use-cookie.ts +18 -12
- package/src/config-types.ts +3 -1
- package/src/config-validation.ts +1 -0
- package/src/dev-tools/browser-logs.ts +37 -273
- package/src/dev-tools/index.ts +2 -2
- package/src/dev-tools/instrumentation.ts +1 -1
- package/src/dev-tools/logger.ts +33 -51
- package/src/dev-tools/terminal.ts +21 -22
- package/src/fonts/transform.ts +4 -43
- package/src/index.ts +16 -3
- package/src/plugin-context.ts +36 -0
- package/src/plugins/build-report.ts +133 -1
- package/src/plugins/cache.ts +32 -131
- package/src/plugins/callsite-ast.ts +167 -0
- package/src/plugins/callsite-jsx.ts +243 -0
- package/src/plugins/entries.ts +23 -4
- package/src/plugins/fonts.ts +16 -2
- package/src/plugins/prebuilt-capture.ts +208 -0
- package/src/plugins/prebuilt.ts +673 -0
- package/src/plugins/prerender-sugar.ts +329 -0
- package/src/plugins/server-bundle.ts +12 -0
- package/src/plugins/shims.ts +9 -0
- package/src/plugins/static-build.ts +0 -1
- package/src/routing/codegen.ts +1 -21
- package/src/routing/convention-lint.ts +85 -49
- package/src/routing/export-detect.ts +304 -0
- package/src/routing/manifest-codegen.ts +2 -11
- package/src/rsc-runtime/rsc.ts +7 -0
- package/src/server/action-handler.ts +11 -27
- package/src/server/actions.ts +3 -4
- package/src/server/als-registry.ts +7 -0
- package/src/server/cookie-parsing.ts +75 -59
- package/src/server/deny-renderer.ts +7 -15
- package/src/server/html-injectors.ts +11 -3
- package/src/server/internal.ts +17 -1
- package/src/server/prebuilt/cache-key.ts +224 -0
- package/src/server/prebuilt/capture-state.ts +89 -0
- package/src/server/prebuilt/manifest-tag-index.ts +46 -0
- package/src/server/prebuilt/overlay.ts +256 -0
- package/src/server/prebuilt/payload-source.ts +192 -0
- package/src/server/prebuilt/synthetic-store.ts +108 -0
- package/src/server/prebuilt-builder.ts +640 -0
- package/src/server/prebuilt-runtime.ts +706 -0
- package/src/server/route-element-builder.ts +40 -12
- package/src/server/rsc-entry/helpers.ts +0 -17
- package/src/server/rsc-entry/index.ts +83 -0
- package/src/server/rsc-entry/render-route.ts +6 -33
- package/src/server/rsc-entry/revalidate-renderer.ts +16 -11
- package/src/server/rsc-entry/rsc-payload.ts +5 -11
- package/src/server/sitemap-generator.ts +16 -10
- package/src/server/tracing.ts +88 -3
- package/dist/_chunks/cache-api-Cbyh3RW4.js +0 -401
- package/dist/_chunks/cache-api-Cbyh3RW4.js.map +0 -1
- package/dist/_chunks/logger-C5v1IMuh.js.map +0 -1
- package/dist/_chunks/plugin-context-DUl3dnCF.js.map +0 -1
- package/dist/_chunks/validation-CnKhGJsp.js +0 -113
- package/dist/_chunks/validation-CnKhGJsp.js.map +0 -1
- package/dist/_chunks/walkers-D5jCGPum.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/client/head.d.ts +0 -16
- package/dist/client/head.d.ts.map +0 -1
- package/dist/client/navigation-api-types.d.ts +0 -90
- package/dist/client/navigation-api-types.d.ts.map +0 -1
- package/dist/server/client-module-map.d.ts +0 -47
- package/dist/server/client-module-map.d.ts.map +0 -1
- package/dist/shared/head-header.d.ts +0 -54
- package/dist/shared/head-header.d.ts.map +0 -1
- package/src/client/head.ts +0 -61
- package/src/client/navigation-api-types.ts +0 -112
- package/src/server/client-module-map.ts +0 -58
- package/src/shared/head-header.ts +0 -69
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* See design/19-client-navigation.md
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { NavigationApi, NavigateEvent } from './navigation-api-types.js';
|
|
21
20
|
import { isHardNavigating } from './navigation-root.js';
|
|
22
21
|
|
|
23
22
|
// ─── Feature Detection ───────────────────────────────────────────
|
|
@@ -27,20 +26,15 @@ import { isHardNavigating } from './navigation-root.js';
|
|
|
27
26
|
* Feature-detected at runtime — no polyfill.
|
|
28
27
|
*/
|
|
29
28
|
export function hasNavigationApi(): boolean {
|
|
30
|
-
return
|
|
31
|
-
typeof window !== 'undefined' &&
|
|
32
|
-
'navigation' in window &&
|
|
33
|
-
(window as unknown as { navigation: unknown }).navigation != null
|
|
34
|
-
);
|
|
29
|
+
return typeof window !== 'undefined' && 'navigation' in window && window.navigation != null;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
/**
|
|
38
33
|
* Get the Navigation API instance. Returns null if unavailable.
|
|
39
|
-
* Uses type assertion — we never import Navigation API types unconditionally.
|
|
40
34
|
*/
|
|
41
|
-
export function getNavigationApi():
|
|
35
|
+
export function getNavigationApi(): Navigation | null {
|
|
42
36
|
if (!hasNavigationApi()) return null;
|
|
43
|
-
return
|
|
37
|
+
return window.navigation;
|
|
44
38
|
}
|
|
45
39
|
|
|
46
40
|
// ─── Navigation API Controller ───────────────────────────────────
|
|
@@ -285,7 +279,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
|
|
|
285
279
|
}
|
|
286
280
|
}
|
|
287
281
|
|
|
288
|
-
nav.addEventListener('navigate', handleNavigate
|
|
282
|
+
nav.addEventListener('navigate', handleNavigate);
|
|
289
283
|
|
|
290
284
|
return {
|
|
291
285
|
setRouterNavigating(value: boolean): void {
|
|
@@ -341,7 +335,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
|
|
|
341
335
|
},
|
|
342
336
|
|
|
343
337
|
cleanup(): void {
|
|
344
|
-
nav.removeEventListener('navigate', handleNavigate
|
|
338
|
+
nav.removeEventListener('navigate', handleNavigate);
|
|
345
339
|
},
|
|
346
340
|
};
|
|
347
341
|
}
|
package/src/client/router.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import { SegmentCache, PrefetchCache, buildSegmentTree } from './segment-cache';
|
|
5
5
|
import type { SegmentInfo } from './segment-cache';
|
|
6
6
|
import { HistoryStack } from './history';
|
|
7
|
-
import type { HeadElement } from './head';
|
|
8
7
|
import { setCurrentParams } from './use-segment-params.js';
|
|
9
8
|
import {
|
|
10
9
|
setNavigationState,
|
|
@@ -82,8 +81,6 @@ export interface RouterDeps {
|
|
|
82
81
|
* In tests, this runs the callback synchronously.
|
|
83
82
|
*/
|
|
84
83
|
afterPaint?: (callback: () => void) => void;
|
|
85
|
-
/** Apply resolved head elements (title, meta tags) to the DOM after navigation. */
|
|
86
|
-
applyHead?: (elements: HeadElement[]) => void;
|
|
87
84
|
/**
|
|
88
85
|
* Run a navigation inside a React transition with optimistic pending URL.
|
|
89
86
|
* The pending URL shows immediately (useOptimistic urgent update) and
|
|
@@ -180,7 +177,7 @@ export interface RouterInstance {
|
|
|
180
177
|
* Renders the element tree and updates head elements without a server fetch.
|
|
181
178
|
* See design/08-forms-and-actions.md §"Single-Roundtrip Revalidation".
|
|
182
179
|
*/
|
|
183
|
-
applyRevalidation(element: unknown
|
|
180
|
+
applyRevalidation(element: unknown): void;
|
|
184
181
|
/**
|
|
185
182
|
* Populate the segment cache from server-provided segment metadata.
|
|
186
183
|
* Called on initial hydration with segment info embedded in the HTML.
|
|
@@ -364,27 +361,19 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
364
361
|
* FetchResult plus the NavigationState (so it can be passed explicitly
|
|
365
362
|
* to wrapPayload/renderRoot without temporal coupling).
|
|
366
363
|
*/
|
|
367
|
-
interface TransitionResult {
|
|
368
|
-
headElements: HeadElement[] | null;
|
|
369
|
-
decodePromise: Promise<void> | null;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
364
|
async function renderViaTransition(
|
|
373
365
|
url: string,
|
|
374
366
|
perform: () => Promise<FetchResult & { navState: NavigationState }>
|
|
375
|
-
): Promise<
|
|
367
|
+
): Promise<void> {
|
|
376
368
|
if (deps.navigateTransition) {
|
|
377
|
-
let headElements: HeadElement[] | null = null;
|
|
378
369
|
await deps.navigateTransition(url, async (wrapPayload) => {
|
|
379
370
|
const result = await perform();
|
|
380
|
-
headElements = result.headElements;
|
|
381
371
|
|
|
382
372
|
if (isPartialNavigation(result.skippedSegments)) {
|
|
383
373
|
const segmentUpdates = buildSegmentUpdates(result);
|
|
384
374
|
|
|
385
375
|
historyStack.push(url, {
|
|
386
376
|
payload: null,
|
|
387
|
-
headElements: result.headElements,
|
|
388
377
|
params: result.params,
|
|
389
378
|
});
|
|
390
379
|
|
|
@@ -402,22 +391,18 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
402
391
|
// Full navigation — empty updates, render the new tree.
|
|
403
392
|
historyStack.push(url, {
|
|
404
393
|
payload: result.payload,
|
|
405
|
-
headElements: result.headElements,
|
|
406
394
|
params: result.params,
|
|
407
395
|
});
|
|
408
396
|
const element = wrapPayload(result.payload, result.navState);
|
|
409
397
|
return { element, decodePromise: result.decodePromise };
|
|
410
398
|
});
|
|
411
|
-
|
|
412
|
-
// callback (NavigationRoot), so we don't need it here.
|
|
413
|
-
return { headElements, decodePromise: null };
|
|
399
|
+
return;
|
|
414
400
|
}
|
|
415
401
|
// Fallback: no transition (tests, no React tree)
|
|
416
402
|
const result = await perform();
|
|
417
403
|
if (isPartialNavigation(result.skippedSegments)) {
|
|
418
404
|
historyStack.push(url, {
|
|
419
405
|
payload: null,
|
|
420
|
-
headElements: result.headElements,
|
|
421
406
|
params: result.params,
|
|
422
407
|
});
|
|
423
408
|
} else {
|
|
@@ -430,19 +415,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
430
415
|
: result.payload;
|
|
431
416
|
historyStack.push(url, {
|
|
432
417
|
payload,
|
|
433
|
-
headElements: result.headElements,
|
|
434
418
|
params: result.params,
|
|
435
419
|
});
|
|
436
420
|
renderPayload(payload, result.navState);
|
|
437
421
|
}
|
|
438
|
-
return { headElements: result.headElements, decodePromise: result.decodePromise };
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** Apply head elements (title, meta tags) to the DOM if available. */
|
|
442
|
-
function applyHead(elements: HeadElement[] | null | undefined): void {
|
|
443
|
-
if (elements && deps.applyHead) {
|
|
444
|
-
deps.applyHead(elements);
|
|
445
|
-
}
|
|
446
422
|
}
|
|
447
423
|
|
|
448
424
|
/** Run a callback after the next paint (after React commit). */
|
|
@@ -495,7 +471,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
495
471
|
? {
|
|
496
472
|
payload: prefetched.payload,
|
|
497
473
|
decodePromise: null,
|
|
498
|
-
headElements: prefetched.headElements,
|
|
499
474
|
segmentInfo: prefetched.segmentInfo ?? null,
|
|
500
475
|
params: prefetched.params ?? null,
|
|
501
476
|
skippedSegments: prefetched.skippedSegments ?? null,
|
|
@@ -592,7 +567,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
592
567
|
setPending(true, url);
|
|
593
568
|
|
|
594
569
|
try {
|
|
595
|
-
|
|
570
|
+
await renderViaTransition(fetchUrl, () =>
|
|
596
571
|
performNavigationFetch(fetchUrl, {
|
|
597
572
|
replace,
|
|
598
573
|
commitUrl: url,
|
|
@@ -601,9 +576,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
601
576
|
})
|
|
602
577
|
);
|
|
603
578
|
|
|
604
|
-
// Update document.title and <meta> tags with the new page's metadata
|
|
605
|
-
applyHead(headElements);
|
|
606
|
-
|
|
607
579
|
// Notify nuqs adapter (and any other listeners) that navigation completed.
|
|
608
580
|
window.dispatchEvent(new Event('timber:navigation-end'));
|
|
609
581
|
|
|
@@ -685,7 +657,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
685
657
|
setPending(true, currentUrl);
|
|
686
658
|
|
|
687
659
|
try {
|
|
688
|
-
|
|
660
|
+
await renderViaTransition(currentUrl, async () => {
|
|
689
661
|
// No state tree sent — server renders the complete RSC payload
|
|
690
662
|
const result = await fetchRscPayload(
|
|
691
663
|
currentUrl,
|
|
@@ -699,8 +671,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
699
671
|
const navState = updateNavigationState(result.params, currentUrl);
|
|
700
672
|
return { ...result, navState };
|
|
701
673
|
});
|
|
702
|
-
|
|
703
|
-
applyHead(headElements);
|
|
704
674
|
} catch (error) {
|
|
705
675
|
if (isAbortError(error)) return;
|
|
706
676
|
throw error;
|
|
@@ -739,7 +709,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
739
709
|
setPending(false);
|
|
740
710
|
const navState = updateNavigationState(entry.params, url);
|
|
741
711
|
renderPayload(entry.payload, navState);
|
|
742
|
-
applyHead(entry.headElements);
|
|
743
712
|
restoreScrollAfterPaint(scrollY);
|
|
744
713
|
} else {
|
|
745
714
|
// No cached payload — fetch from server.
|
|
@@ -749,7 +718,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
749
718
|
const navAbort = createNavAbort(externalSignal);
|
|
750
719
|
setPending(true, url);
|
|
751
720
|
try {
|
|
752
|
-
|
|
721
|
+
await renderViaTransition(url, async () => {
|
|
753
722
|
const stateTree = segmentCache.serializeStateTree();
|
|
754
723
|
const result = await fetchRscPayload(url, deps, stateTree, undefined, navAbort.signal);
|
|
755
724
|
updateSegmentCache(result.segmentInfo);
|
|
@@ -758,7 +727,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
758
727
|
return { ...result, navState };
|
|
759
728
|
});
|
|
760
729
|
|
|
761
|
-
applyHead(headElements);
|
|
762
730
|
restoreScrollAfterPaint(scrollY);
|
|
763
731
|
} catch (error) {
|
|
764
732
|
if (isAbortError(error)) return;
|
|
@@ -811,10 +779,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
811
779
|
return () => pendingListeners.delete(listener);
|
|
812
780
|
},
|
|
813
781
|
prefetch,
|
|
814
|
-
applyRevalidation(element: unknown
|
|
782
|
+
applyRevalidation(element: unknown): void {
|
|
815
783
|
// Render the piggybacked element tree from a server action response.
|
|
816
|
-
// Updates the current history entry with the fresh payload
|
|
817
|
-
//
|
|
784
|
+
// Updates the current history entry with the fresh payload —
|
|
785
|
+
// same as refresh() but without a server fetch.
|
|
818
786
|
// Revalidation payloads are already resolved (no Flight thenable),
|
|
819
787
|
// so no segment merging is needed — just cache and render.
|
|
820
788
|
const currentUrl = deps.getCurrentUrl();
|
|
@@ -829,11 +797,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
|
|
|
829
797
|
// to {} (TIM-1037).
|
|
830
798
|
historyStack.push(currentUrl, {
|
|
831
799
|
payload: element,
|
|
832
|
-
headElements,
|
|
833
800
|
params: navState.params,
|
|
834
801
|
});
|
|
835
802
|
renderPayload(element, navState);
|
|
836
|
-
applyHead(headElements);
|
|
837
803
|
},
|
|
838
804
|
initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),
|
|
839
805
|
segmentCache,
|
package/src/client/rsc-fetch.ts
CHANGED
|
@@ -12,13 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { SegmentInfo } from './segment-cache';
|
|
15
|
-
import type { HeadElement } from './head';
|
|
16
15
|
import type { RouterDeps } from './router';
|
|
17
|
-
import { decodeHeadHeader, HEAD_HEADER } from '../shared/head-header.js';
|
|
18
16
|
|
|
19
17
|
// ─── Types ───────────────────────────────────────────────────────
|
|
20
18
|
|
|
21
|
-
/** Result of fetching an RSC payload — includes
|
|
19
|
+
/** Result of fetching an RSC payload — includes segment metadata. */
|
|
22
20
|
export interface FetchResult {
|
|
23
21
|
payload: unknown;
|
|
24
22
|
/**
|
|
@@ -29,7 +27,6 @@ export interface FetchResult {
|
|
|
29
27
|
* become unhandled rejections.
|
|
30
28
|
*/
|
|
31
29
|
decodePromise: Promise<void> | null;
|
|
32
|
-
headElements: HeadElement[] | null;
|
|
33
30
|
/** Segment metadata from X-Timber-Segments header for populating the segment cache. */
|
|
34
31
|
segmentInfo: SegmentInfo[] | null;
|
|
35
32
|
/** Route params from X-Timber-Params header for populating useSegmentParams(). */
|
|
@@ -149,20 +146,6 @@ function warnMalformedHeader(headerName: string, raw: string): void {
|
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
/**
|
|
153
|
-
* Extract head elements from the X-Timber-Head response header.
|
|
154
|
-
* Returns null if the header is missing or malformed.
|
|
155
|
-
*/
|
|
156
|
-
export function extractHeadElements(response: Response): HeadElement[] | null {
|
|
157
|
-
const header = response.headers.get(HEAD_HEADER);
|
|
158
|
-
if (!header) return null;
|
|
159
|
-
const decoded = decodeHeadHeader(header);
|
|
160
|
-
if (decoded === null) {
|
|
161
|
-
warnMalformedHeader(HEAD_HEADER, header);
|
|
162
|
-
}
|
|
163
|
-
return decoded as HeadElement[] | null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
149
|
/**
|
|
167
150
|
* Extract segment metadata from the X-Timber-Segments response header.
|
|
168
151
|
* Returns null if the header is missing or malformed.
|
|
@@ -317,9 +300,6 @@ function trackStreamCompletion(body: ReadableStream<Uint8Array>): {
|
|
|
317
300
|
* Fetch an RSC payload from the server. If a decodeRsc function is provided,
|
|
318
301
|
* the response is decoded into a React element tree via createFromFetch.
|
|
319
302
|
* Otherwise, the raw response text is returned (test mode).
|
|
320
|
-
*
|
|
321
|
-
* Also extracts head elements from the X-Timber-Head response header
|
|
322
|
-
* so the client can update document.title and <meta> tags after navigation.
|
|
323
303
|
*/
|
|
324
304
|
export async function fetchRscPayload(
|
|
325
305
|
url: string,
|
|
@@ -335,10 +315,9 @@ export async function fetchRscPayload(
|
|
|
335
315
|
// createFromFetch takes a Promise<Response> and progressively parses
|
|
336
316
|
// the RSC Flight stream as chunks arrive.
|
|
337
317
|
//
|
|
338
|
-
// Intercept the response to read
|
|
318
|
+
// Intercept the response to read segment metadata before createFromFetch
|
|
339
319
|
// consumes the body. Reading headers does NOT consume the body stream.
|
|
340
320
|
const fetchPromise = deps.fetch(rscUrl, { headers, redirect: 'manual', signal });
|
|
341
|
-
let headElements: HeadElement[] | null = null;
|
|
342
321
|
let segmentInfo: SegmentInfo[] | null = null;
|
|
343
322
|
let params: Record<string, string | string[]> | null = null;
|
|
344
323
|
let skippedSegments: string[] | null = null;
|
|
@@ -371,7 +350,8 @@ export async function fetchRscPayload(
|
|
|
371
350
|
if (response.headers.get('X-Timber-Error') === '1') {
|
|
372
351
|
throw new ServerErrorResponse(response.status, url);
|
|
373
352
|
}
|
|
374
|
-
|
|
353
|
+
// Metadata (<title>/<meta>/<link>) now rides the RSC Flight payload
|
|
354
|
+
// as React elements — React 19 Float handles them. See TIM-1151.
|
|
375
355
|
segmentInfo = extractSegmentInfo(response);
|
|
376
356
|
params = extractParams(response);
|
|
377
357
|
skippedSegments = extractSkippedSegments(response);
|
|
@@ -391,7 +371,7 @@ export async function fetchRscPayload(
|
|
|
391
371
|
}
|
|
392
372
|
return response;
|
|
393
373
|
});
|
|
394
|
-
// Await headers so
|
|
374
|
+
// Await headers so segmentInfo/params are populated.
|
|
395
375
|
await wrappedPromise;
|
|
396
376
|
// Start decoding but do NOT await — return the in-progress thenable.
|
|
397
377
|
// React can render a Flight thenable directly: it suspends on unresolved
|
|
@@ -411,7 +391,6 @@ export async function fetchRscPayload(
|
|
|
411
391
|
return {
|
|
412
392
|
payload,
|
|
413
393
|
decodePromise,
|
|
414
|
-
headElements,
|
|
415
394
|
segmentInfo,
|
|
416
395
|
params,
|
|
417
396
|
skippedSegments,
|
|
@@ -429,7 +408,6 @@ export async function fetchRscPayload(
|
|
|
429
408
|
return {
|
|
430
409
|
payload: await response.text(),
|
|
431
410
|
decodePromise: null,
|
|
432
|
-
headElements: extractHeadElements(response),
|
|
433
411
|
segmentInfo: extractSegmentInfo(response),
|
|
434
412
|
params: extractParams(response),
|
|
435
413
|
skippedSegments: extractSkippedSegments(response),
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
// Segment Cache — stores the mounted segment tree and prefetched payloads
|
|
2
2
|
// See design/19-client-navigation.md for architecture details.
|
|
3
3
|
|
|
4
|
-
import type { HeadElement } from './head';
|
|
5
|
-
|
|
6
4
|
// ─── Types ───────────────────────────────────────────────────────
|
|
7
5
|
|
|
8
|
-
/** A prefetched RSC result with optional
|
|
6
|
+
/** A prefetched RSC result with optional segment metadata. */
|
|
9
7
|
export interface PrefetchResult {
|
|
10
8
|
payload: unknown;
|
|
11
|
-
headElements: HeadElement[] | null;
|
|
12
9
|
/** Segment metadata from X-Timber-Segments header for populating the segment cache. */
|
|
13
10
|
segmentInfo?: SegmentInfo[] | null;
|
|
14
11
|
/** Route params from X-Timber-Params header for populating useSegmentParams(). */
|
package/src/client/use-cookie.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { useSyncExternalStore } from 'react';
|
|
12
|
+
import { parseCookie, stringifySetCookie } from 'cookie';
|
|
12
13
|
import { getSsrData } from './ssr-data.js';
|
|
13
14
|
import { assertValidCookieName } from '../cookies/validation.js';
|
|
14
15
|
|
|
@@ -41,24 +42,29 @@ const listeners = new Map<string, Set<Listener>>();
|
|
|
41
42
|
/** Parse a cookie name from document.cookie. */
|
|
42
43
|
export function getCookieValue(name: string): string | undefined {
|
|
43
44
|
if (typeof document === 'undefined') return undefined;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
);
|
|
47
|
-
return match ? decodeURIComponent(match[1]) : undefined;
|
|
45
|
+
const cookies = parseCookie(document.cookie);
|
|
46
|
+
return cookies[name];
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
/** Serialize options into a cookie string suffix. */
|
|
51
50
|
export function serializeOptions(options?: ClientCookieOptions): string {
|
|
52
51
|
if (!options) return '; Path=/; SameSite=Lax';
|
|
53
|
-
const parts: string[] = [];
|
|
54
|
-
parts.push(`Path=${options.path ?? '/'}`);
|
|
55
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
56
|
-
if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
|
|
57
|
-
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
58
52
|
const sameSite = options.sameSite ?? 'lax';
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
// Build a Set-Cookie string via the cookie package, then strip the
|
|
54
|
+
// `__placeholder__=` name=value prefix to get just the attributes.
|
|
55
|
+
const full = stringifySetCookie({
|
|
56
|
+
name: '__placeholder__',
|
|
57
|
+
value: '',
|
|
58
|
+
path: options.path ?? '/',
|
|
59
|
+
domain: options.domain,
|
|
60
|
+
maxAge: options.maxAge,
|
|
61
|
+
expires: options.expires,
|
|
62
|
+
sameSite,
|
|
63
|
+
secure: options.secure,
|
|
64
|
+
});
|
|
65
|
+
// Strip everything up to and including the first "; "
|
|
66
|
+
const idx = full.indexOf('; ');
|
|
67
|
+
return idx >= 0 ? full.slice(idx) : '';
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/** Notify all subscribers for a given cookie name. */
|
package/src/config-types.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface TimberUserConfig {
|
|
|
47
47
|
clientJavascript?: boolean | ClientJavascriptConfig;
|
|
48
48
|
adapter?: unknown;
|
|
49
49
|
cacheHandler?: unknown;
|
|
50
|
+
/** CDN purge handler — called on revalidateTag/cache.invalidate to purge CDN-cached responses. */
|
|
51
|
+
cdnPurge?: unknown;
|
|
50
52
|
allowedOrigins?: string[];
|
|
51
53
|
csrf?: boolean;
|
|
52
54
|
limits?: {
|
|
@@ -244,7 +246,7 @@ export interface TimberUserConfig {
|
|
|
244
246
|
* Auto-generate sitemap.xml from the route tree.
|
|
245
247
|
*
|
|
246
248
|
* When enabled, timber walks the file-system route tree and produces a sitemap
|
|
247
|
-
* for all discoverable pages. Dynamic routes with `
|
|
249
|
+
* for all discoverable pages. Dynamic routes with `generateStaticSegmentParams()` are
|
|
248
250
|
* enumerated. Routes protected by `access.ts` are excluded by default.
|
|
249
251
|
*
|
|
250
252
|
* If a user-authored `sitemap.ts` or `sitemap.xml` exists at the app root,
|