@timber-js/app 0.2.0-alpha.196 → 0.2.0-alpha.198
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-CWYtq6ii.js → actions-BS-m5SLv.js} +3 -3
- package/dist/_chunks/{actions-CWYtq6ii.js.map → actions-BS-m5SLv.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
- package/dist/_chunks/{cache-api-CQeYzA5g.js → cache-api-DqzgTEqk.js} +4 -49
- package/dist/_chunks/cache-api-DqzgTEqk.js.map +1 -0
- package/dist/_chunks/{chains-h7EO-u3n.js → chains-CZG7E5zg.js} +2 -2
- package/dist/_chunks/{chains-h7EO-u3n.js.map → chains-CZG7E5zg.js.map} +1 -1
- package/dist/_chunks/{cli-check-BVthpfLS.js → cli-check-dVDi1GQz.js} +3 -3
- package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-dVDi1GQz.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js → cli-schema-sync-DTy_-Msq.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js.map → cli-schema-sync-DTy_-Msq.js.map} +1 -1
- package/dist/_chunks/{cloudflare-BKJC3SC_.js → cloudflare-BFb__LYG.js} +2 -2
- package/dist/_chunks/{cloudflare-BKJC3SC_.js.map → cloudflare-BFb__LYG.js.map} +1 -1
- package/dist/_chunks/{convention-lint-DO10_pVl.js → convention-lint-Ph6luW4c.js} +4 -2
- package/dist/_chunks/convention-lint-Ph6luW4c.js.map +1 -0
- package/dist/_chunks/{error-boundary-D-lkwyaD.js → error-boundary-BvRCCmbN.js} +3 -3
- package/dist/_chunks/{error-boundary-D-lkwyaD.js.map → error-boundary-BvRCCmbN.js.map} +1 -1
- package/dist/_chunks/{href-validation-CMc5JRls.js → href-validation-BIrxavIy.js} +74 -2
- package/dist/_chunks/href-validation-BIrxavIy.js.map +1 -0
- package/dist/_chunks/{live-graph-Bx4HodF1.js → live-graph-BXDsdzBv.js} +3 -3
- package/dist/_chunks/{live-graph-Bx4HodF1.js.map → live-graph-BXDsdzBv.js.map} +1 -1
- package/dist/_chunks/{logger-pumCm3Il.js → logger-DDirEsn7.js} +3 -4
- package/dist/_chunks/{logger-pumCm3Il.js.map → logger-DDirEsn7.js.map} +1 -1
- package/dist/_chunks/navigation-root-B00jjGd5.js +233 -0
- package/dist/_chunks/navigation-root-B00jjGd5.js.map +1 -0
- package/dist/_chunks/{segment-context-CjOlyB8Y.js → param-value-C8TNYchQ.js} +2 -33
- package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
- package/dist/_chunks/{poison-scan-BAxfTT5L.js → poison-scan-BoDLgbix.js} +2 -2
- package/dist/_chunks/{poison-scan-BAxfTT5L.js.map → poison-scan-BoDLgbix.js.map} +1 -1
- package/dist/_chunks/{router-ref-BzqbPwYC.js → router-ref-8gr8qsxN.js} +2 -2
- package/dist/_chunks/{router-ref-BzqbPwYC.js.map → router-ref-8gr8qsxN.js.map} +1 -1
- package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js → rsc-cache-key-ClUiXQnK.js} +2 -2
- package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js.map → rsc-cache-key-ClUiXQnK.js.map} +1 -1
- package/dist/_chunks/{scanner-BRIOmHE2.js → scanner-tdFPvDYi.js} +174 -7
- package/dist/_chunks/scanner-tdFPvDYi.js.map +1 -0
- package/dist/_chunks/segment-context-D9_89u34.js +34 -0
- package/dist/_chunks/segment-context-D9_89u34.js.map +1 -0
- package/dist/_chunks/singleflight-2lUWfcAk.js +54 -0
- package/dist/_chunks/singleflight-2lUWfcAk.js.map +1 -0
- package/dist/_chunks/{ssr-data-Ya2HJPFp.js → ssr-data-BQGhTPAK.js} +2 -17
- package/dist/_chunks/ssr-data-BQGhTPAK.js.map +1 -0
- package/dist/_chunks/{walkers-BU6z9xRV.js → walkers-DNX05dC0.js} +2 -2
- package/dist/_chunks/{walkers-BU6z9xRV.js.map → walkers-DNX05dC0.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/nitro.d.ts +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +1 -1
- package/dist/cache/singleflight.d.ts +2 -0
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/hydrate.d.ts +21 -15
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +4 -3
- package/dist/client/browser-entry/index.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 +17 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/global-context.d.ts +15 -0
- package/dist/client/global-context.d.ts.map +1 -0
- package/dist/client/index.js +138 -35
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +0 -1
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +206 -55
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/location-search.d.ts +12 -0
- package/dist/client/location-search.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +18 -0
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +13 -11
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +47 -108
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +136 -0
- package/dist/client/navigation-transition.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +4 -5
- package/dist/client/params-context.d.ts.map +1 -1
- package/dist/client/react-root.d.ts +44 -0
- package/dist/client/react-root.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +2 -2
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +12 -2
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +39 -0
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +25 -14
- package/dist/client/segment-outlet.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -9
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-content-cache-context.d.ts +35 -0
- package/dist/client/slot-content-cache-context.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +8 -2
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +0 -15
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +13 -11
- package/dist/client/use-pathname.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +13 -13
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/client/use-segment-params.d.ts +18 -68
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/config-types.d.ts +17 -0
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/holding-server.d.ts +15 -10
- package/dist/dev-tools/holding-server.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -43
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +2 -2
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception-overlap.d.ts +35 -0
- package/dist/routing/interception-overlap.d.ts.map +1 -0
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +3 -1
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +6 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/csp-nonce.d.ts +45 -0
- package/dist/server/csp-nonce.d.ts.map +1 -0
- package/dist/server/default-status-page.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/flight-scripts.d.ts +5 -2
- package/dist/server/flight-scripts.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +17 -2
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +3 -2
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +86 -37
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-render.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +3 -17
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/nuqs-ssr-provider.d.ts +7 -3
- package/dist/server/nuqs-ssr-provider.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/prebuilt/key-discipline.d.ts +32 -3
- package/dist/server/prebuilt/key-discipline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +4 -3
- package/dist/server/render-utils.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +22 -2
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +5 -1
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +59 -27
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/shims/navigation-rsc.d.ts +21 -0
- package/dist/shims/navigation-rsc.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +1 -0
- package/docs/api/35-api-typescript.mdx +4 -83
- package/docs/learn/03-fetching-data.mdx +1 -1
- package/docs/learn/{03b-access-control.mdx → 04-access-control.mdx} +2 -17
- package/docs/learn/05-the-flush-point.mdx +175 -0
- package/docs/learn/{05-typed-params.mdx → 06-typed-params.mdx} +1 -1
- package/docs/learn/07-typed-routes.mdx +25 -49
- package/docs/learn/{08-streaming.mdx → 09-streaming.mdx} +1 -7
- package/docs/learn/{10-middleware.mdx → 11-middleware.mdx} +1 -0
- package/package.json +3 -3
- package/src/adapters/nitro.ts +7 -7
- package/src/cache/singleflight.ts +5 -0
- package/src/client/browser-entry/hydrate.ts +54 -104
- package/src/client/browser-entry/index.ts +16 -6
- package/src/client/browser-entry/post-hydration.ts +3 -2
- package/src/client/browser-entry/router-init.ts +84 -33
- package/src/client/global-context.ts +31 -0
- package/src/client/internal.ts +1 -2
- package/src/client/link.tsx +18 -18
- package/src/client/location-search.ts +15 -0
- package/src/client/navigation-api.ts +4 -2
- package/src/client/navigation-commit.ts +48 -2
- package/src/client/navigation-context.ts +25 -37
- package/src/client/navigation-root.tsx +55 -411
- package/src/client/navigation-transition.ts +278 -0
- package/src/client/nuqs-adapter.tsx +4 -5
- package/src/client/params-context.ts +13 -18
- package/src/client/react-root.ts +72 -0
- package/src/client/router-lifecycle.ts +1 -1
- package/src/client/router-pipeline.ts +96 -22
- package/src/client/router-types.ts +12 -2
- package/src/client/router.ts +48 -36
- package/src/client/segment-cache.ts +70 -2
- package/src/client/segment-context.ts +7 -4
- package/src/client/segment-outlet.tsx +41 -86
- package/src/client/segment-update-context.ts +7 -26
- package/src/client/slot-content-cache-context.ts +43 -0
- package/src/client/ssr-data.ts +8 -2
- package/src/client/state.ts +0 -26
- package/src/client/use-pathname.ts +21 -31
- package/src/client/use-search-params.ts +31 -29
- package/src/client/use-segment-params.ts +27 -126
- package/src/config-types.ts +17 -0
- package/src/config-validation.ts +17 -0
- package/src/dev-tools/holding-server.ts +23 -12
- package/src/index.ts +26 -11
- package/src/plugins/dev-server.ts +9 -12
- package/src/plugins/entries.ts +3 -0
- package/src/plugins/shims.ts +8 -7
- package/src/plugins/static-build.ts +9 -5
- package/src/react-canary.d.ts +2 -0
- package/src/routing/codegen-write.ts +2 -0
- package/src/routing/interception-overlap.ts +141 -0
- package/src/routing/interception.ts +118 -5
- package/src/rsc-runtime/ssr.ts +3 -2
- package/src/server/als-registry.ts +6 -0
- package/src/server/csp-nonce.ts +70 -0
- package/src/server/default-status-page.ts +1 -0
- package/src/server/deny-renderer.ts +7 -3
- package/src/server/flight-scripts.ts +9 -4
- package/src/server/html-injector-core.ts +26 -9
- package/src/server/html-injectors.ts +8 -8
- package/src/server/metadata-render.ts +26 -4
- package/src/server/node-stream-transforms.ts +7 -20
- package/src/server/nuqs-ssr-provider.tsx +8 -7
- package/src/server/pipeline-phases.ts +5 -0
- package/src/server/prebuilt/key-discipline.ts +82 -13
- package/src/server/prebuilt-runtime.ts +2 -2
- package/src/server/primitives.ts +4 -4
- package/src/server/render-utils.ts +8 -4
- package/src/server/rsc-entry/action-middleware-runner.ts +7 -0
- package/src/server/rsc-entry/error-renderer.ts +5 -2
- package/src/server/rsc-entry/index.ts +8 -0
- package/src/server/rsc-entry/ssr-renderer.ts +11 -4
- package/src/server/ssr-bridge-types.ts +22 -2
- package/src/server/ssr-entry.ts +35 -28
- package/src/server/ssr-render.ts +13 -4
- package/src/server/ssr-wrappers.tsx +81 -61
- package/src/server/types.ts +10 -0
- package/src/shared/slot-params.ts +3 -4
- package/src/shims/navigation-rsc.ts +47 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
- package/dist/_chunks/cache-api-CQeYzA5g.js.map +0 -1
- package/dist/_chunks/convention-lint-DO10_pVl.js.map +0 -1
- package/dist/_chunks/href-validation-CMc5JRls.js.map +0 -1
- package/dist/_chunks/scanner-BRIOmHE2.js.map +0 -1
- package/dist/_chunks/segment-context-CjOlyB8Y.js.map +0 -1
- package/dist/_chunks/slot-params-BCTmZkQB.js +0 -76
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +0 -1
- package/dist/_chunks/ssr-data-Ya2HJPFp.js.map +0 -1
- package/dist/_chunks/use-segment-params-DzTBpkvj.js +0 -398
- package/dist/_chunks/use-segment-params-DzTBpkvj.js.map +0 -1
- package/docs/learn/04-loading-states.mdx +0 -67
- package/docs/learn/04b-the-flush-point.mdx +0 -115
- package/docs/learn/12-client-navigation.mdx +0 -176
- package/docs/learn/13-configuration.mdx +0 -166
- package/docs/more/01-advanced-routing.mdx +0 -344
- package/docs/more/02-advanced-forms.mdx +0 -137
- package/docs/more/03-coming-from-nextjs.mdx +0 -186
- package/docs/more/04-metadata-and-fonts.mdx +0 -193
- package/docs/more/04b-mdx.mdx +0 -229
- package/docs/more/05-content-collections.mdx +0 -90
- package/docs/more/06-instrumentation.mdx +0 -214
- package/docs/more/07-security.mdx +0 -129
- package/docs/more/08-developer-experience.mdx +0 -134
- package/docs/more/40-why-timber.mdx +0 -50
- package/docs/more/41-timber-vs-nextjs.mdx +0 -81
- package/docs/more/42-timber-vs-others.mdx +0 -68
- package/docs/more/50-ai-agent-instructions.mdx +0 -171
- /package/docs/learn/{06-forms-and-actions.mdx → 08-forms-and-actions.mdx} +0 -0
- /package/docs/learn/{09-caching.mdx → 10-caching.mdx} +0 -0
- /package/docs/learn/{11-error-handling.mdx → 12-error-handling.mdx} +0 -0
- /package/docs/learn/{14-deploying.mdx → 13-deploying.mdx} +0 -0
package/dist/client/internal.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { n as isRscContentType, t as RSC_CONTENT_TYPE } from "../_chunks/rsc-media-type-DDc7duTD.js";
|
|
2
|
-
import { a as randomRscCacheKey, n as RSC_KEY_PARAM, o as recordLookup, s as rscCacheKey } from "../_chunks/rsc-cache-key-
|
|
2
|
+
import { a as randomRscCacheKey, n as RSC_KEY_PARAM, o as recordLookup, s as rscCacheKey } from "../_chunks/rsc-cache-key-ClUiXQnK.js";
|
|
3
3
|
import { i as withBasePath, r as stripBasePath } from "../_chunks/base-path-DaQrzbez.js";
|
|
4
4
|
import { t as rscPayloadPath } from "../_chunks/rsc-payload-path-B_LBodc2.js";
|
|
5
|
-
import {
|
|
6
|
-
import { n as
|
|
7
|
-
import {
|
|
8
|
-
import { n as
|
|
5
|
+
import { n as createSingleflight, t as SingleflightTimeoutError } from "../_chunks/singleflight-2lUWfcAk.js";
|
|
6
|
+
import { c as cachedSearchParams, i as _setCachedSearch, n as getSsrData, r as setSsrData, s as cachedSearch, t as clearSsrData } from "../_chunks/ssr-data-BQGhTPAK.js";
|
|
7
|
+
import { n as getRouterOrNull, r as setGlobalRouter, t as getRouter } from "../_chunks/router-ref-8gr8qsxN.js";
|
|
8
|
+
import { i as markStaleFromError, n as isClientStale, r as markClientStale, t as TimberErrorBoundary } from "../_chunks/error-boundary-BvRCCmbN.js";
|
|
9
|
+
import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-D9_89u34.js";
|
|
9
10
|
import "../_chunks/rsc-error-envelope-tT5PJs4q.js";
|
|
10
|
-
import { a as
|
|
11
|
+
import { a as useNavigationContext, i as setNavigationState, n as NavigationProvider, r as getNavigationState, t as setHardNavigating } from "../_chunks/navigation-root-B00jjGd5.js";
|
|
11
12
|
import { t as bindUseQueryStates } from "../_chunks/use-query-states-I3JMng6J.js";
|
|
12
13
|
//#region src/shared/payload-root.ts
|
|
13
14
|
/**
|
|
@@ -212,6 +213,12 @@ function buildSegmentTree(segments) {
|
|
|
212
213
|
/** Sentinel value for negative cache entries (URL is not a route). */
|
|
213
214
|
var NEGATIVE_ENTRY = Object.freeze({ payload: null });
|
|
214
215
|
/**
|
|
216
|
+
* Timeout for the in-flight singleflight (TIM-1438). Per CLAUDE.md's
|
|
217
|
+
* singleflight rule: "Never write a coalescing Map without a timeout."
|
|
218
|
+
* A hung prefetch cannot block clicks or suppress hovers forever.
|
|
219
|
+
*/
|
|
220
|
+
var PREFETCH_SINGLEFLIGHT_TIMEOUT_MS = 5e3;
|
|
221
|
+
/**
|
|
215
222
|
* Compose the map key. Length-prefixed rather than delimiter-joined for the
|
|
216
223
|
* same reason `shared/rsc-cache-key.ts` is: any delimiter is forgeable by a
|
|
217
224
|
* value containing it, and a URL may contain any character a delimiter could.
|
|
@@ -232,12 +239,19 @@ function prefetchScopeOf(stateTree) {
|
|
|
232
239
|
* 30 seconds. When a link is clicked, the prefetched payload is consumed
|
|
233
240
|
* (moved to the history stack) and removed from this cache.
|
|
234
241
|
*
|
|
242
|
+
* In-flight dedup (TIM-1438): concurrent fetches for the same key are
|
|
243
|
+
* coalesced by a `createSingleflight` instance. A hover starts a flight;
|
|
244
|
+
* a click for the same key joins it instead of issuing a duplicate. The
|
|
245
|
+
* singleflight enforces a 5-second timeout and cleans up automatically
|
|
246
|
+
* on settlement.
|
|
247
|
+
*
|
|
235
248
|
* timber.js does NOT prefetch on viewport intersection — only explicit
|
|
236
249
|
* hover on <Link prefetch> triggers a prefetch.
|
|
237
250
|
*/
|
|
238
251
|
var PrefetchCache = class PrefetchCache {
|
|
239
252
|
static TTL_MS = 3e4;
|
|
240
253
|
entries = /* @__PURE__ */ new Map();
|
|
254
|
+
flights = createSingleflight({ timeoutMs: PREFETCH_SINGLEFLIGHT_TIMEOUT_MS });
|
|
241
255
|
set(key, result) {
|
|
242
256
|
this.entries.set(prefetchMapKey(key), {
|
|
243
257
|
result,
|
|
@@ -254,6 +268,10 @@ var PrefetchCache = class PrefetchCache {
|
|
|
254
268
|
}
|
|
255
269
|
return entry.result;
|
|
256
270
|
}
|
|
271
|
+
/** True if a ready or negative entry exists for this key. */
|
|
272
|
+
has(key) {
|
|
273
|
+
return this.get(key) !== void 0;
|
|
274
|
+
}
|
|
257
275
|
/** Get and remove the entry (used when navigation consumes a prefetch) */
|
|
258
276
|
consume(key) {
|
|
259
277
|
const result = this.get(key);
|
|
@@ -261,6 +279,47 @@ var PrefetchCache = class PrefetchCache {
|
|
|
261
279
|
return result;
|
|
262
280
|
}
|
|
263
281
|
/**
|
|
282
|
+
* Fetch or coalesce with an in-flight fetch for this key (TIM-1438).
|
|
283
|
+
*
|
|
284
|
+
* Concurrent callers (hover + click, repeated hovers) get the same
|
|
285
|
+
* promise. On success the result is stored as a ready entry. On
|
|
286
|
+
* NonRscResponse a negative entry is stored and the outcome is
|
|
287
|
+
* `{ kind: 'non-route' }`. Other errors (network, version skew)
|
|
288
|
+
* reject — the singleflight cleans up the key and subsequent callers
|
|
289
|
+
* retry.
|
|
290
|
+
*
|
|
291
|
+
* The singleflight signal is passed to `doFetch` so a timed-out flight
|
|
292
|
+
* aborts the underlying fetch rather than leaving it running.
|
|
293
|
+
*/
|
|
294
|
+
fetchOrCoalesce(key, doFetch, isNonRoute) {
|
|
295
|
+
return this.flights.do(prefetchMapKey(key), async (signal) => {
|
|
296
|
+
try {
|
|
297
|
+
const result = await doFetch(signal);
|
|
298
|
+
if (!signal.aborted) this.set(key, result);
|
|
299
|
+
return {
|
|
300
|
+
kind: "ready",
|
|
301
|
+
result
|
|
302
|
+
};
|
|
303
|
+
} catch (err) {
|
|
304
|
+
if (isNonRoute(err)) {
|
|
305
|
+
if (!signal.aborted) this.setNegative(key);
|
|
306
|
+
return { kind: "non-route" };
|
|
307
|
+
}
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Join an in-flight singleflight fetch if one exists for this key.
|
|
314
|
+
* Returns the in-flight promise or undefined. Used by the click path to
|
|
315
|
+
* coalesce with a hover prefetch without starting a new flight — when no
|
|
316
|
+
* flight exists, the click issues its own fetch with the navigation's
|
|
317
|
+
* abort signal so superseded navigations abort immediately (TIM-1438).
|
|
318
|
+
*/
|
|
319
|
+
joinInflight(key) {
|
|
320
|
+
return this.flights.get(prefetchMapKey(key));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
264
323
|
* Store a negative entry — the URL is not a route (non-RSC Content-Type).
|
|
265
324
|
*
|
|
266
325
|
* Keyed like every other entry even though "not a route" does not actually
|
|
@@ -319,6 +378,31 @@ var HistoryStack = class {
|
|
|
319
378
|
*
|
|
320
379
|
* See design/19-client-navigation.md §"State Update Invariants".
|
|
321
380
|
*/
|
|
381
|
+
/** Whether a response skipped layouts — its payload merges over the tree on screen. */
|
|
382
|
+
function isPartialNavigation(skippedSegments) {
|
|
383
|
+
return skippedSegments != null && skippedSegments.length > 0;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Whether a payload can stand on its own when replayed from the history
|
|
387
|
+
* stack. See `NavigationCommitInput.skippedSegments`.
|
|
388
|
+
*/
|
|
389
|
+
function isReplayable(opts) {
|
|
390
|
+
return !isPartialNavigation(opts.skippedSegments) && !hasSkippedSlot(opts.segmentInfo);
|
|
391
|
+
}
|
|
392
|
+
function hasSkippedSlot(segmentInfo) {
|
|
393
|
+
return segmentInfo?.some((s) => s.slot && s.skipped) ?? false;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The metadata a history entry keeps. `skipped` describes the *response*
|
|
397
|
+
* — "this slot's content was omitted" — and the entry stores no such
|
|
398
|
+
* payload (it stores null). Left on the entry, the flag outlives the
|
|
399
|
+
* response: `applyRevalidation()` reuses the entry's metadata beside a full
|
|
400
|
+
* re-render and would have that judged non-replayable too.
|
|
401
|
+
*/
|
|
402
|
+
function storedSegmentInfo(segmentInfo) {
|
|
403
|
+
if (!hasSkippedSlot(segmentInfo)) return segmentInfo;
|
|
404
|
+
return segmentInfo.map(({ skipped: _skipped, ...rest }) => rest);
|
|
405
|
+
}
|
|
322
406
|
/**
|
|
323
407
|
* Derive the navigation state (pathname + search) a URL renders with.
|
|
324
408
|
*
|
|
@@ -402,6 +486,7 @@ function createNavigationCommitter(deps) {
|
|
|
402
486
|
prepareNavigation(url, opts) {
|
|
403
487
|
const navState = deriveNavigationState(url);
|
|
404
488
|
const segmentInfo = opts.status === void 0 || opts.status < 400 ? opts.segmentInfo : [];
|
|
489
|
+
const payload = isReplayable(opts) ? opts.payload : null;
|
|
405
490
|
return {
|
|
406
491
|
navState,
|
|
407
492
|
commit() {
|
|
@@ -409,9 +494,9 @@ function createNavigationCommitter(deps) {
|
|
|
409
494
|
else if (segmentInfo?.length === 0 || opts.clearSegmentCacheOnEmpty) segmentCache.clear();
|
|
410
495
|
setNavigationState(navState);
|
|
411
496
|
historyStack.push(url, {
|
|
412
|
-
payload
|
|
497
|
+
payload,
|
|
413
498
|
params: opts.params,
|
|
414
|
-
segmentInfo
|
|
499
|
+
segmentInfo: storedSegmentInfo(segmentInfo)
|
|
415
500
|
});
|
|
416
501
|
}
|
|
417
502
|
};
|
|
@@ -990,6 +1075,44 @@ function hardNavigate(url, fromUrl) {
|
|
|
990
1075
|
} else window.location.href = url;
|
|
991
1076
|
}
|
|
992
1077
|
//#endregion
|
|
1078
|
+
//#region src/client/navigation-transition.ts
|
|
1079
|
+
var NAV_TRANSITION_KEY = Symbol.for("__timber_nav_transition_counter");
|
|
1080
|
+
function getTransitionCounter() {
|
|
1081
|
+
const g = globalThis;
|
|
1082
|
+
const existing = g[NAV_TRANSITION_KEY];
|
|
1083
|
+
if (!existing) {
|
|
1084
|
+
const created = {
|
|
1085
|
+
id: 0,
|
|
1086
|
+
waiters: /* @__PURE__ */ new Set()
|
|
1087
|
+
};
|
|
1088
|
+
g[NAV_TRANSITION_KEY] = created;
|
|
1089
|
+
return created;
|
|
1090
|
+
}
|
|
1091
|
+
existing.waiters ??= /* @__PURE__ */ new Set();
|
|
1092
|
+
return existing;
|
|
1093
|
+
}
|
|
1094
|
+
/** Bump the counter and wake everything waiting on an older transition. */
|
|
1095
|
+
function bumpTransitionCounter() {
|
|
1096
|
+
const counter = getTransitionCounter();
|
|
1097
|
+
counter.id += 1;
|
|
1098
|
+
for (const wake of [...counter.waiters]) wake();
|
|
1099
|
+
return counter.id;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Invalidate all in-flight navigation transitions. Any navigateTransition()
|
|
1103
|
+
* call whose perform() has not yet committed will reject with AbortError
|
|
1104
|
+
* instead of committing its element.
|
|
1105
|
+
*
|
|
1106
|
+
* Called by the router when a render supersedes in-flight navigations
|
|
1107
|
+
* WITHOUT going through navigateTransition() — the cached popstate replay
|
|
1108
|
+
* renders directly, which doesn't bump the counter, so a stale forward
|
|
1109
|
+
* navigation's render would otherwise pass the `counter.id !== transId`
|
|
1110
|
+
* guard and commit the forward page over the replayed back page (TIM-1022).
|
|
1111
|
+
*/
|
|
1112
|
+
function supersedeNavigationTransitions() {
|
|
1113
|
+
bumpTransitionCounter();
|
|
1114
|
+
}
|
|
1115
|
+
//#endregion
|
|
993
1116
|
//#region src/client/router-lifecycle.ts
|
|
994
1117
|
/**
|
|
995
1118
|
* Navigation Lifecycle — who owns the router, and when a fetch may be cut.
|
|
@@ -1180,6 +1303,19 @@ function createNavigationLifecycle(deps) {
|
|
|
1180
1303
|
* See design/19-client-navigation.md §"Partial Rendering" and §"Prefetching".
|
|
1181
1304
|
*/
|
|
1182
1305
|
/**
|
|
1306
|
+
* Race a promise against an abort signal. The promise continues regardless —
|
|
1307
|
+
* only the *await* gives up. Used so a click superseded by a newer navigation
|
|
1308
|
+
* stops waiting for a shared prefetch flight without aborting the flight
|
|
1309
|
+
* itself (TIM-1438).
|
|
1310
|
+
*/
|
|
1311
|
+
function raceAbort(promise, signal) {
|
|
1312
|
+
if (!signal) return promise;
|
|
1313
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
1314
|
+
return Promise.race([promise, new Promise((_, reject) => {
|
|
1315
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1316
|
+
})]);
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1183
1319
|
* Normalize a departing URL to the pathname the server is told about.
|
|
1184
1320
|
*
|
|
1185
1321
|
* This is the value sent as `X-Timber-URL` *and* the source half of the
|
|
@@ -1217,9 +1353,6 @@ function prefetchKeyFor(url, departingUrl, stateTree) {
|
|
|
1217
1353
|
scope: prefetchScopeOf(stateTree)
|
|
1218
1354
|
};
|
|
1219
1355
|
}
|
|
1220
|
-
function isPartialNavigation(skippedSegments) {
|
|
1221
|
-
return skippedSegments != null && skippedSegments.length > 0;
|
|
1222
|
-
}
|
|
1223
1356
|
/**
|
|
1224
1357
|
* Build a segment updates map for partial navigation. Identifies the
|
|
1225
1358
|
* first non-skipped segment and maps it to the payload content.
|
|
@@ -1251,9 +1384,14 @@ function observeSkew(decodePromise) {
|
|
|
1251
1384
|
return decodePromise;
|
|
1252
1385
|
}
|
|
1253
1386
|
function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepareNavigation, markHandedOff, forgetOlderHandoffs }) {
|
|
1254
|
-
/**
|
|
1255
|
-
|
|
1256
|
-
|
|
1387
|
+
/**
|
|
1388
|
+
* Render a decoded RSC payload into the DOM, publishing `commit` when the
|
|
1389
|
+
* tree is current. The renderer owns that moment (see `RootRenderer`);
|
|
1390
|
+
* with no renderer there is no tree to wait for, so it publishes now.
|
|
1391
|
+
*/
|
|
1392
|
+
function renderPayload(payload, navState, params, commit) {
|
|
1393
|
+
if (deps.renderRoot) deps.renderRoot(payload, navState, params, commit);
|
|
1394
|
+
else commit();
|
|
1257
1395
|
}
|
|
1258
1396
|
/**
|
|
1259
1397
|
* Resolve thenable payloads in the test/fallback path (no navigateTransition).
|
|
@@ -1307,8 +1445,9 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
|
|
|
1307
1445
|
}
|
|
1308
1446
|
const result = await perform();
|
|
1309
1447
|
handOff();
|
|
1310
|
-
commitAndForget(result.commit)
|
|
1311
|
-
if (
|
|
1448
|
+
const commit = commitAndForget(result.commit);
|
|
1449
|
+
if (isPartialNavigation(result.skippedSegments)) commit();
|
|
1450
|
+
else renderPayload(result.payload, result.navState, await result.params, commit);
|
|
1312
1451
|
}
|
|
1313
1452
|
/**
|
|
1314
1453
|
* Core navigation logic shared between the transition and fallback paths.
|
|
@@ -1331,6 +1470,26 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
|
|
|
1331
1470
|
skippedSegments: prefetched.skippedSegments ?? null,
|
|
1332
1471
|
status: prefetched.status ?? 200
|
|
1333
1472
|
} : void 0;
|
|
1473
|
+
if (result === void 0) {
|
|
1474
|
+
const inflight = prefetchCache.joinInflight(cacheKey);
|
|
1475
|
+
if (inflight) try {
|
|
1476
|
+
const outcome = await raceAbort(inflight, options.signal);
|
|
1477
|
+
if (outcome.kind === "non-route") throw new NonRscResponse(url);
|
|
1478
|
+
prefetchCache.consume(cacheKey);
|
|
1479
|
+
result = {
|
|
1480
|
+
payload: outcome.result.payload,
|
|
1481
|
+
params: outcome.result.params ?? readPublishedParams(void 0),
|
|
1482
|
+
decodePromise: outcome.result.decodePromise ?? null,
|
|
1483
|
+
segmentInfo: outcome.result.segmentInfo ?? null,
|
|
1484
|
+
skippedSegments: outcome.result.skippedSegments ?? null,
|
|
1485
|
+
status: outcome.result.status ?? 200
|
|
1486
|
+
};
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
1489
|
+
if (options.signal?.aborted) throw options.signal.reason;
|
|
1490
|
+
if (error instanceof SingleflightTimeoutError || error instanceof TypeError) {} else throw error;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1334
1493
|
if (result === void 0) result = await fetchRscPayload(url, deps, stateTree, currentUrl, options.signal);
|
|
1335
1494
|
const commitHistoryUrl = () => {
|
|
1336
1495
|
if (options.skipHistory) return;
|
|
@@ -1348,13 +1507,12 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
|
|
|
1348
1507
|
};
|
|
1349
1508
|
const payload = await resolveForFallback(result.payload);
|
|
1350
1509
|
const params = await result.params;
|
|
1351
|
-
const isPartial = isPartialNavigation(result.skippedSegments);
|
|
1352
|
-
const hasSkippedSlots = result.segmentInfo?.some((s) => s.slot && s.skipped) ?? false;
|
|
1353
1510
|
const { navState, commit } = prepareNavigation(url, {
|
|
1354
|
-
payload
|
|
1511
|
+
payload,
|
|
1355
1512
|
params,
|
|
1356
1513
|
segmentInfo: result.segmentInfo,
|
|
1357
|
-
status: result.status
|
|
1514
|
+
status: result.status,
|
|
1515
|
+
skippedSegments: result.skippedSegments
|
|
1358
1516
|
});
|
|
1359
1517
|
return {
|
|
1360
1518
|
...result,
|
|
@@ -1469,7 +1627,8 @@ function createRouter(deps) {
|
|
|
1469
1627
|
payload,
|
|
1470
1628
|
params,
|
|
1471
1629
|
segmentInfo: result.segmentInfo,
|
|
1472
|
-
status: result.status
|
|
1630
|
+
status: result.status,
|
|
1631
|
+
skippedSegments: result.skippedSegments
|
|
1473
1632
|
});
|
|
1474
1633
|
return {
|
|
1475
1634
|
...result,
|
|
@@ -1501,8 +1660,7 @@ function createRouter(deps) {
|
|
|
1501
1660
|
segmentInfo: entry.segmentInfo,
|
|
1502
1661
|
clearSegmentCacheOnEmpty: true
|
|
1503
1662
|
});
|
|
1504
|
-
commit
|
|
1505
|
-
renderPayload(entry.payload, navState, entry.params);
|
|
1663
|
+
renderPayload(entry.payload, navState, entry.params, commit);
|
|
1506
1664
|
restoreScrollAfterPaint(scrollY);
|
|
1507
1665
|
}, externalSignal);
|
|
1508
1666
|
else await fetchCommitAndRender(url, {
|
|
@@ -1514,6 +1672,9 @@ function createRouter(deps) {
|
|
|
1514
1672
|
/**
|
|
1515
1673
|
* Prefetch an RSC payload for a URL and store it in the prefetch cache.
|
|
1516
1674
|
* Called on hover of <Link prefetch> elements.
|
|
1675
|
+
*
|
|
1676
|
+
* Concurrent hovers and a click during the round-trip coalesce via the
|
|
1677
|
+
* singleflight inside PrefetchCache (TIM-1438).
|
|
1517
1678
|
*/
|
|
1518
1679
|
function prefetch(url) {
|
|
1519
1680
|
if (isClientStale()) return;
|
|
@@ -1522,16 +1683,11 @@ function createRouter(deps) {
|
|
|
1522
1683
|
const stateTree = currentStateTree();
|
|
1523
1684
|
const cacheKey = prefetchKeyFor(fetchUrl, deps.getCurrentUrl(), stateTree);
|
|
1524
1685
|
const from = cacheKey.from;
|
|
1525
|
-
if (prefetchCache.
|
|
1686
|
+
if (prefetchCache.has(cacheKey)) return;
|
|
1526
1687
|
if (historyStack.has(fetchUrl)) return;
|
|
1527
|
-
fetchRscPayload(fetchUrl, deps, stateTree, from).then((
|
|
1528
|
-
result.decodePromise?.catch((error) => void recordSkew(error));
|
|
1529
|
-
prefetchCache.set(cacheKey, result);
|
|
1688
|
+
prefetchCache.fetchOrCoalesce(cacheKey, (signal) => fetchRscPayload(fetchUrl, deps, stateTree, from, signal), (err) => err instanceof NonRscResponse).then((outcome) => {
|
|
1689
|
+
if (outcome.kind === "ready") outcome.result.decodePromise?.catch((error) => void recordSkew(error));
|
|
1530
1690
|
}, (error) => {
|
|
1531
|
-
if (error instanceof NonRscResponse) {
|
|
1532
|
-
prefetchCache.setNegative(cacheKey);
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
1691
|
recordSkew(error);
|
|
1536
1692
|
});
|
|
1537
1693
|
}
|
|
@@ -1553,8 +1709,7 @@ function createRouter(deps) {
|
|
|
1553
1709
|
params,
|
|
1554
1710
|
segmentInfo: existingEntry?.segmentInfo
|
|
1555
1711
|
});
|
|
1556
|
-
commit
|
|
1557
|
-
renderPayload(tree, navState, params);
|
|
1712
|
+
renderPayload(tree, navState, params, commit);
|
|
1558
1713
|
},
|
|
1559
1714
|
initSegmentCache: (segments) => updateSegmentCache(segments),
|
|
1560
1715
|
segmentCache,
|
|
@@ -1570,25 +1725,23 @@ function createRouter(deps) {
|
|
|
1570
1725
|
* Returns a read-only URLSearchParams instance reflecting the current
|
|
1571
1726
|
* URL's query string. Updates when client-side navigation changes the URL.
|
|
1572
1727
|
*
|
|
1573
|
-
*
|
|
1574
|
-
* with the RSC tree render during full navigations, AND by
|
|
1575
|
-
* syncShallowSearch() for shallow URL updates (nuqs shallow: true,
|
|
1576
|
-
* replaceUrl, or any external pushState/replaceState that changes the
|
|
1577
|
-
* query string). See router-init.ts.
|
|
1728
|
+
* One unconditional read of NavigationContext, on every side (TIM-1425):
|
|
1578
1729
|
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1730
|
+
* - In the browser, the provider is updated atomically with the RSC tree
|
|
1731
|
+
* during full navigations, AND by syncShallowSearch() for shallow URL
|
|
1732
|
+
* updates (nuqs shallow: true, replaceUrl, or any external
|
|
1733
|
+
* pushState/replaceState that changes the query string). See router-init.ts.
|
|
1734
|
+
* - During SSR, the wrapper chain mounts the same provider carrying the
|
|
1735
|
+
* request's RAW query string (TIM-1424), so repeated keys
|
|
1736
|
+
* (`?tag=a&tag=b`) survive — the collapsed record the ALS context holds
|
|
1737
|
+
* cannot represent them.
|
|
1738
|
+
* - In the RSC environment, this module is never evaluated — the shims plugin
|
|
1739
|
+
* resolves next/navigation to navigation-rsc.ts (TIM-1420).
|
|
1584
1740
|
*
|
|
1585
1741
|
* Unlike Next.js's ReadonlyURLSearchParams, this returns a standard
|
|
1586
1742
|
* URLSearchParams. Mutation methods (set, delete, append) work on the
|
|
1587
1743
|
* local copy but do NOT affect the URL — use the router or nuqs for that.
|
|
1588
1744
|
*
|
|
1589
|
-
* During SSR, reads the request search params from the SSR ALS context
|
|
1590
|
-
* (populated by ssr-entry.ts) instead of window.location.
|
|
1591
|
-
*
|
|
1592
1745
|
* Compatible with Next.js's `useSearchParams()` from `next/navigation`.
|
|
1593
1746
|
*/
|
|
1594
1747
|
function getSearchParams(search) {
|
|
@@ -1602,19 +1755,17 @@ function getSearchParams(search) {
|
|
|
1602
1755
|
/**
|
|
1603
1756
|
* Read the current URL search params.
|
|
1604
1757
|
*
|
|
1758
|
+
* Throws when no NavigationProvider is above the caller.
|
|
1759
|
+
*
|
|
1605
1760
|
* Compatible with Next.js's `useSearchParams()` from `next/navigation`.
|
|
1606
1761
|
*/
|
|
1607
1762
|
function useSearchParams() {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
const ssrData = getSsrData();
|
|
1613
|
-
if (ssrData) return new URLSearchParams(ssrData.searchParams);
|
|
1614
|
-
if (typeof window !== "undefined") return getSearchParams(window.location.search);
|
|
1615
|
-
return new URLSearchParams();
|
|
1763
|
+
const nav = useNavigationContext();
|
|
1764
|
+
if (nav === null) throw new Error("[timber] useSearchParams() was called outside the timber app tree (no NavigationProvider found). In tests, render the component inside the timber providers.");
|
|
1765
|
+
if (typeof window === "undefined" || getSsrData()) return new URLSearchParams(nav.search);
|
|
1766
|
+
return getSearchParams(nav.search);
|
|
1616
1767
|
}
|
|
1617
1768
|
//#endregion
|
|
1618
|
-
export { HistoryStack, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData,
|
|
1769
|
+
export { HistoryStack, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData, setGlobalRouter, setNavigationState, setSsrData, useSearchParams, useSegmentContext };
|
|
1619
1770
|
|
|
1620
1771
|
//# sourceMappingURL=internal.js.map
|