@timber-js/app 0.2.0-alpha.184 → 0.2.0-alpha.186
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-O_LsyCE4.js → actions-C-Rw9vPc.js} +3 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
- package/dist/_chunks/als-slots-mFweg276.js +27 -0
- package/dist/_chunks/als-slots-mFweg276.js.map +1 -0
- package/dist/_chunks/base-path-DaQrzbez.js +80 -0
- package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
- package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-Cd0VZ_Pd.js} +3 -3
- package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
- package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
- package/dist/_chunks/error-boundary-DpYRI_I1.js.map +1 -0
- package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
- package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
- package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
- package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
- package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
- package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
- package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
- package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
- package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
- package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
- package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
- package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
- package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
- package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
- package/dist/_chunks/use-segment-params-ClyUNq4d.js +128 -0
- package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
- package/dist/_chunks/{walkers-BtTlKviE.js → walkers-BhhwI9TD.js} +21 -87
- package/dist/_chunks/walkers-BhhwI9TD.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +37 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/fs-identity.d.ts +64 -0
- package/dist/adapters/fs-identity.d.ts.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +2 -3
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +57 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +13 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/fast-hash.d.ts +5 -0
- package/dist/cache/fast-hash.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +20 -9
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts +4 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +3 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +1 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -162
- package/dist/client/history.d.ts +13 -6
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +18 -11
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +4 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +872 -305
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +2 -2
- package/dist/client/navigation-commit.d.ts +71 -0
- package/dist/client/navigation-commit.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +37 -20
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +63 -23
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +81 -0
- package/dist/client/params-context.d.ts.map +1 -0
- package/dist/client/router-effects.d.ts +129 -0
- package/dist/client/router-effects.d.ts.map +1 -0
- package/dist/client/router-lifecycle.d.ts +55 -0
- package/dist/client/router-lifecycle.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +84 -0
- package/dist/client/router-pipeline.d.ts.map +1 -0
- package/dist/client/router-ref.d.ts +1 -1
- package/dist/client/router-ref.d.ts.map +1 -1
- package/dist/client/router-skew.d.ts +21 -0
- package/dist/client/router-skew.d.ts.map +1 -0
- package/dist/client/router-types.d.ts +207 -0
- package/dist/client/router-types.d.ts.map +1 -0
- package/dist/client/router.d.ts +1 -182
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +39 -16
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +79 -34
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +6 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-client.d.ts +69 -0
- package/dist/client/stale-client.d.ts.map +1 -0
- package/dist/client/state.d.ts +10 -1
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +2 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/unload-guard.d.ts +5 -0
- package/dist/client/unload-guard.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +1 -1
- package/dist/client/use-segment-params.d.ts +18 -8
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -3
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +915 -320
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +33 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/request-dep/analysis.d.ts +106 -0
- package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
- package/dist/plugins/request-dep/index.d.ts +75 -0
- package/dist/plugins/request-dep/index.d.ts.map +1 -0
- package/dist/plugins/request-dep/origin.d.ts +61 -0
- package/dist/plugins/request-dep/origin.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts +9 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +149 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +18 -0
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/collision-probe.d.ts +104 -0
- package/dist/routing/collision-probe.d.ts.map +1 -0
- package/dist/routing/collision-spaces.d.ts +26 -0
- package/dist/routing/collision-spaces.d.ts.map +1 -0
- package/dist/routing/index.js +3 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +33 -0
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/segment-keys.d.ts +95 -0
- package/dist/routing/segment-keys.d.ts.map +1 -0
- package/dist/routing/slot-placement.d.ts +48 -0
- package/dist/routing/slot-placement.d.ts.map +1 -0
- package/dist/routing/types.d.ts +1 -3
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +6 -0
- package/dist/routing/walkers.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +0 -6
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +176 -2
- package/dist/search-params/index.js.map +1 -1
- package/dist/segment-params/define.d.ts +0 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +69 -1
- package/dist/segment-params/index.js.map +1 -0
- package/dist/server/actions.d.ts +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +2 -1
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +13 -1
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/chain-url-parts.d.ts +82 -0
- package/dist/server/chain-url-parts.d.ts.map +1 -0
- package/dist/server/children-interception.d.ts +49 -0
- package/dist/server/children-interception.d.ts.map +1 -0
- package/dist/server/default-status-page.d.ts +45 -0
- package/dist/server/default-status-page.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +4 -7
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +14 -3
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +35 -0
- package/dist/server/head-response.d.ts.map +1 -0
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +528 -373
- package/dist/server/internal.js.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +0 -18
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +59 -9
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +11 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +0 -12
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/publish-params.d.ts +33 -0
- package/dist/server/publish-params.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +48 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +32 -2
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +13 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-cache-key-guard.d.ts +78 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -10
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- 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/rsc-payload.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/skippable-prefix.d.ts +23 -0
- package/dist/server/skippable-prefix.d.ts.map +1 -0
- package/dist/server/slot-interception.d.ts +80 -0
- package/dist/server/slot-interception.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +70 -8
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +6 -0
- 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-wrappers.d.ts +24 -13
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +6 -27
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts +6 -0
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/static-not-found.d.ts +52 -0
- package/dist/server/static-not-found.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +3 -10
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/shared/als-slots.d.ts +41 -0
- package/dist/shared/als-slots.d.ts.map +1 -0
- package/dist/shared/base-path.d.ts +56 -0
- package/dist/shared/base-path.d.ts.map +1 -0
- package/dist/shared/param-value.d.ts +80 -0
- package/dist/shared/param-value.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +108 -0
- package/dist/shared/payload-root.d.ts.map +1 -0
- package/dist/shared/rsc-cache-key.d.ts +163 -0
- package/dist/shared/rsc-cache-key.d.ts.map +1 -0
- package/dist/shared/rsc-error-envelope.d.ts +25 -0
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
- package/dist/shared/rsc-manifest.d.ts +28 -0
- package/dist/shared/rsc-manifest.d.ts.map +1 -0
- package/dist/shared/rsc-payload-path.d.ts +42 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -0
- package/dist/shared/segment-info.d.ts +59 -0
- package/dist/shared/segment-info.d.ts.map +1 -0
- package/dist/shared/slot-params.d.ts +51 -0
- package/dist/shared/slot-params.d.ts.map +1 -0
- package/dist/shared/static-platform-files.d.ts +71 -0
- package/dist/shared/static-platform-files.d.ts.map +1 -0
- package/dist/shims/navigation-client.d.ts +8 -19
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +0 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/learn/02-pages-and-layouts.mdx +1 -1
- package/docs/learn/11-error-handling.mdx +4 -3
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +172 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +2 -2
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +86 -11
- package/src/adapters/fs-identity.ts +156 -0
- package/src/adapters/nitro.ts +6 -6
- package/src/adapters/shared.ts +196 -21
- package/src/adapters/types.ts +13 -1
- package/src/cache/fast-hash.ts +5 -0
- package/src/client/browser-entry/action-dispatch.ts +9 -4
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -1
- package/src/client/browser-entry/post-hydration.ts +10 -9
- package/src/client/browser-entry/router-init.ts +104 -48
- package/src/client/browser-entry/rsc-stream.ts +11 -2
- package/src/client/error-boundary.tsx +20 -15
- package/src/client/history.ts +13 -6
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +55 -8
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +174 -0
- package/src/client/navigation-context.ts +40 -67
- package/src/client/navigation-root.tsx +326 -101
- package/src/client/params-context.ts +192 -0
- package/src/client/router-effects.ts +231 -0
- package/src/client/router-lifecycle.ts +288 -0
- package/src/client/router-pipeline.ts +389 -0
- package/src/client/router-ref.ts +1 -1
- package/src/client/router-skew.ts +30 -0
- package/src/client/router-types.ts +230 -0
- package/src/client/router.ts +242 -690
- package/src/client/rsc-fetch.ts +180 -122
- package/src/client/segment-cache.ts +121 -44
- package/src/client/ssr-data.ts +6 -0
- package/src/client/stale-client.ts +191 -0
- package/src/client/state.ts +14 -1
- package/src/client/top-loader.tsx +2 -1
- package/src/client/unload-guard.ts +34 -2
- package/src/client/use-cookie.ts +9 -2
- package/src/client/use-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/index.ts +5 -1
- package/src/plugin-context.ts +34 -1
- package/src/plugins/adapter-build.ts +4 -0
- package/src/plugins/build-manifest.ts +34 -13
- package/src/plugins/entries.ts +4 -0
- package/src/plugins/fonts.ts +11 -8
- package/src/plugins/request-dep/analysis.ts +447 -0
- package/src/plugins/request-dep/index.ts +376 -0
- package/src/plugins/request-dep/origin.ts +310 -0
- package/src/plugins/routing.ts +60 -3
- package/src/plugins/static-build.ts +333 -63
- package/src/routing/codegen-types.ts +18 -0
- package/src/routing/codegen.ts +50 -10
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/interception.ts +896 -50
- package/src/routing/manifest-codegen.ts +6 -9
- package/src/routing/scanner.ts +114 -113
- package/src/routing/segment-classify.ts +57 -0
- package/src/routing/segment-keys.ts +146 -0
- package/src/routing/slot-placement.ts +97 -0
- package/src/routing/types.ts +1 -3
- package/src/routing/walkers.ts +8 -0
- package/src/search-params/define.ts +6 -29
- package/src/segment-params/define.ts +4 -27
- package/src/server/action-handler.ts +3 -1
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +2 -1
- package/src/server/build-manifest.ts +14 -1
- package/src/server/chain-url-parts.ts +163 -0
- package/src/server/children-interception.ts +115 -0
- package/src/server/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +5 -31
- package/src/server/deny-renderer.ts +83 -22
- package/src/server/head-response.ts +54 -0
- package/src/server/param-coercion.ts +48 -18
- package/src/server/pipeline-helpers.ts +0 -45
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +3 -5
- package/src/server/pipeline-phases.ts +68 -16
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-builder.ts +1 -7
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +0 -18
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +89 -98
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +170 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/error-renderer.ts +64 -25
- package/src/server/rsc-entry/helpers.ts +31 -16
- package/src/server/rsc-entry/index.ts +36 -28
- package/src/server/rsc-entry/render-route.ts +12 -7
- package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
- package/src/server/rsc-entry/rsc-payload.ts +14 -25
- package/src/server/rsc-entry/rsc-stream.ts +4 -3
- package/src/server/rsc-entry/ssr-renderer.ts +13 -9
- package/src/server/sitemap-generator.ts +1 -7
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +253 -156
- package/src/server/ssr-bridge-types.ts +6 -0
- package/src/server/ssr-entry.ts +12 -4
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +67 -45
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +4 -47
- package/src/shared/als-slots.ts +72 -0
- package/src/shared/base-path.ts +80 -0
- package/src/shared/param-value.ts +244 -0
- package/src/shared/payload-root.ts +151 -0
- package/src/shared/rsc-cache-key.ts +269 -0
- package/src/shared/rsc-error-envelope.ts +31 -0
- package/src/shared/rsc-manifest.ts +29 -0
- package/src/shared/rsc-payload-path.ts +45 -0
- package/src/shared/segment-info.ts +60 -0
- package/src/shared/slot-params.ts +111 -0
- package/src/shared/static-platform-files.ts +75 -0
- package/src/shims/navigation-client.ts +8 -26
- package/src/shims/navigation.ts +0 -4
- package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-D3naS3eH.js +0 -688
- package/dist/_chunks/cli-schema-sync-D3naS3eH.js.map +0 -1
- package/dist/_chunks/define-COtkxMRT.js +0 -191
- package/dist/_chunks/define-COtkxMRT.js.map +0 -1
- package/dist/_chunks/define-c4au4I9R.js +0 -82
- package/dist/_chunks/define-c4au4I9R.js.map +0 -1
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
- package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
- package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
- package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
- package/dist/_chunks/walkers-BtTlKviE.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/plugins/request-dep.d.ts +0 -32
- package/dist/plugins/request-dep.d.ts.map +0 -1
- package/src/plugins/request-dep.ts +0 -499
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import type { PublishedParams } from '../shared/payload-root.js';
|
|
2
|
+
import type { SegmentInfo, StateTree } from '../shared/segment-info.js';
|
|
1
3
|
/** A prefetched RSC result with optional segment metadata. */
|
|
2
4
|
export interface PrefetchResult {
|
|
3
5
|
payload: unknown;
|
|
6
|
+
/**
|
|
7
|
+
* Completion of the full Flight stream, when the prefetch is still decoding.
|
|
8
|
+
* Carried through to the navigation that consumes the entry so a decode
|
|
9
|
+
* failure reaches *that* navigation's recovery rather than dying with the
|
|
10
|
+
* prefetch (TIM-1275).
|
|
11
|
+
*/
|
|
12
|
+
decodePromise?: Promise<void> | null;
|
|
4
13
|
/** Segment metadata from X-Timber-Segments header for populating the segment cache. */
|
|
5
14
|
segmentInfo?: SegmentInfo[] | null;
|
|
6
|
-
/**
|
|
7
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The params published beside `payload`, split off the same payload root.
|
|
17
|
+
* Carried so a consumed prefetch publishes the params of the tree it is
|
|
18
|
+
* about to render, and not the ones already on screen.
|
|
19
|
+
*/
|
|
20
|
+
params?: PublishedParams | Promise<PublishedParams>;
|
|
8
21
|
/** Segment paths skipped by the server (for client-side merging). */
|
|
9
22
|
skippedSegments?: string[] | null;
|
|
10
23
|
}
|
|
@@ -15,6 +28,11 @@ export interface PrefetchResult {
|
|
|
15
28
|
export interface SegmentNode {
|
|
16
29
|
/** The segment's URL pattern (e.g., "/", "/dashboard", "/projects/[id]") */
|
|
17
30
|
segment: string;
|
|
31
|
+
/**
|
|
32
|
+
* The segment's `app/` directory path, groups included (e.g. `/(browse)/feed`).
|
|
33
|
+
* Undefined for slot nodes and for servers that predate the field.
|
|
34
|
+
*/
|
|
35
|
+
treePath?: string;
|
|
18
36
|
/** The RSC flight payload for this segment (opaque to the cache) */
|
|
19
37
|
payload: unknown;
|
|
20
38
|
/**
|
|
@@ -32,14 +50,6 @@ export interface SegmentNode {
|
|
|
32
50
|
/** Whether this slot's access.ts denied on its last render. */
|
|
33
51
|
denied?: boolean;
|
|
34
52
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Serialized state tree sent via X-Timber-State-Tree header.
|
|
37
|
-
* Only sync segments are included — async segments always re-render.
|
|
38
|
-
*/
|
|
39
|
-
export interface StateTree {
|
|
40
|
-
segments: string[];
|
|
41
|
-
slots?: string[];
|
|
42
|
-
}
|
|
43
53
|
/**
|
|
44
54
|
* Maintains the client-side segment tree representing currently mounted
|
|
45
55
|
* layouts and pages. Used for navigation reconciliation — the router diffs
|
|
@@ -60,29 +70,18 @@ export declare class SegmentCache {
|
|
|
60
70
|
* client can actually merge (i.e., segments whose cached element tree
|
|
61
71
|
* contains an inner SegmentProvider the merger can splice into).
|
|
62
72
|
*
|
|
73
|
+
* `treePaths` is collected UNFILTERED, unlike `segments` and `slots`. Those
|
|
74
|
+
* two answer "what may the server skip re-rendering?", so every reason a
|
|
75
|
+
* segment cannot be reused is a reason to leave it out. `treePaths` answers
|
|
76
|
+
* "what is mounted right now?" for interception scoping — a request-dependent
|
|
77
|
+
* layout is still on screen, and dropping it would silently narrow the scope
|
|
78
|
+
* the server sees and cancel a modal that should open (TIM-1282).
|
|
79
|
+
*
|
|
63
80
|
* This is a performance optimization only, NOT a security boundary.
|
|
64
81
|
* The server always runs all access.ts files regardless of the state tree.
|
|
65
82
|
*/
|
|
66
83
|
serializeStateTree(mergeableFilter?: Set<string>): StateTree;
|
|
67
84
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Segment metadata from the server, sent via X-Timber-Segments header.
|
|
70
|
-
* Describes a rendered segment's path and whether it's async.
|
|
71
|
-
*/
|
|
72
|
-
export interface SegmentInfo {
|
|
73
|
-
path: string;
|
|
74
|
-
/** Outlet key — includes route group name when applicable (e.g., "/(marketing)"). */
|
|
75
|
-
segmentId?: string;
|
|
76
|
-
isRequestDependent: boolean;
|
|
77
|
-
/** True for parallel route slot entries. Slots are keyed by their slot path (e.g., "/@sidebar"). */
|
|
78
|
-
slot?: boolean;
|
|
79
|
-
/** Parent segment path for slot entries. Used to attach the slot to the correct SegmentNode. */
|
|
80
|
-
parentSegment?: string;
|
|
81
|
-
/** True when the slot's access.ts denied on this render. Denied slots are excluded from the state tree. */
|
|
82
|
-
denied?: boolean;
|
|
83
|
-
/** True when the slot was skipped (cached content reused). Payloads with skipped slots are not replayable. */
|
|
84
|
-
skipped?: boolean;
|
|
85
|
-
}
|
|
86
85
|
/**
|
|
87
86
|
* Build a SegmentNode tree from flat segment metadata.
|
|
88
87
|
*
|
|
@@ -95,6 +94,45 @@ export interface SegmentInfo {
|
|
|
95
94
|
* from the tree — pages are never cached across navigations.
|
|
96
95
|
*/
|
|
97
96
|
export declare function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Identifies a prefetched payload. A payload is addressed by *every* input that
|
|
99
|
+
* changes the server's answer, not by the destination alone.
|
|
100
|
+
*
|
|
101
|
+
* The server's answer for a destination depends on where the request came
|
|
102
|
+
* from — that is the whole mechanism behind intercepting routes, and it also
|
|
103
|
+
* drives slot-skip resolution. A prefetch issued from `/gallery` is a modal
|
|
104
|
+
* payload; the same URL prefetched from `/` is the full page. Keying on the
|
|
105
|
+
* destination alone let the first click after a hover be answered with
|
|
106
|
+
* whichever variant happened to be fetched (TIM-1265).
|
|
107
|
+
*
|
|
108
|
+
* `from` alone stopped being enough in TIM-1282. It is a pathname, and once
|
|
109
|
+
* interception scope comes from the rendered tree rather than the address, one
|
|
110
|
+
* pathname no longer names one source tree: `/photo/1` is the address both for
|
|
111
|
+
* a modal open over `(browse)` and for `(landing)`'s canonical page. A key
|
|
112
|
+
* without the scope hands the modal payload to the canonical page's click,
|
|
113
|
+
* which renders it without the server's scope check ever running.
|
|
114
|
+
*
|
|
115
|
+
* The two reads happen at different instants — `scope` is sampled at hover and
|
|
116
|
+
* again at click — which is why a tree that changed in between produces a
|
|
117
|
+
* *miss* and a refetch rather than a wrong hit.
|
|
118
|
+
*/
|
|
119
|
+
export interface PrefetchKey {
|
|
120
|
+
/** Destination URL — hash-less `pathname + search`, as navigate() fetches it. */
|
|
121
|
+
url: string;
|
|
122
|
+
/** Departing pathname — the `X-Timber-URL` the prefetch was issued with. */
|
|
123
|
+
from: string;
|
|
124
|
+
/**
|
|
125
|
+
* The rendered scope the prefetch was issued with — the `treePaths` half of
|
|
126
|
+
* `X-Timber-State-Tree`, joined. Empty when none crossed.
|
|
127
|
+
*/
|
|
128
|
+
scope: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The `scope` half of a `PrefetchKey`, derived from the state tree that will
|
|
132
|
+
* actually be sent. Taking it from the header value rather than re-walking the
|
|
133
|
+
* cache is the point: the key varies by exactly what the request varies by.
|
|
134
|
+
*/
|
|
135
|
+
export declare function prefetchScopeOf(stateTree: StateTree | undefined): string;
|
|
98
136
|
/**
|
|
99
137
|
* Short-lived cache for hover-triggered prefetches. Entries expire after
|
|
100
138
|
* 30 seconds. When a link is clicked, the prefetched payload is consumed
|
|
@@ -106,13 +144,20 @@ export declare function buildSegmentTree(segments: SegmentInfo[]): SegmentNode |
|
|
|
106
144
|
export declare class PrefetchCache {
|
|
107
145
|
private static readonly TTL_MS;
|
|
108
146
|
private entries;
|
|
109
|
-
set(
|
|
110
|
-
get(
|
|
147
|
+
set(key: PrefetchKey, result: PrefetchResult): void;
|
|
148
|
+
get(key: PrefetchKey): PrefetchResult | undefined;
|
|
111
149
|
/** Get and remove the entry (used when navigation consumes a prefetch) */
|
|
112
|
-
consume(
|
|
113
|
-
/**
|
|
114
|
-
|
|
150
|
+
consume(key: PrefetchKey): PrefetchResult | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Store a negative entry — the URL is not a route (non-RSC Content-Type).
|
|
153
|
+
*
|
|
154
|
+
* Keyed like every other entry even though "not a route" does not actually
|
|
155
|
+
* vary by departing URL: a source-independent negative would be a second
|
|
156
|
+
* keying rule to keep correct, and the only cost of the uniform one is a
|
|
157
|
+
* repeated fetch for a link hovered from a second page.
|
|
158
|
+
*/
|
|
159
|
+
setNegative(key: PrefetchKey): void;
|
|
115
160
|
/** Check if the entry is a negative cache entry (URL is not a route). */
|
|
116
|
-
isNegative(
|
|
161
|
+
isNegative(key: PrefetchKey): boolean;
|
|
117
162
|
}
|
|
118
163
|
//# sourceMappingURL=segment-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segment-cache.d.ts","sourceRoot":"","sources":["../../src/client/segment-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"segment-cache.d.ts","sourceRoot":"","sources":["../../src/client/segment-cache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAIxE,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrC,uFAAuF;IACvF,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACpD,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACnC,kEAAkE;IAClE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACjC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAID;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAA0B;IAEtC,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAK5C;IAED,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAI5C;IAED,KAAK,IAAI,IAAI,CAEZ;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAiB3D;CACF;AAgDD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,GAAG,SAAS,CAkEjF;AAYD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAWD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAExE;AAED;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAU;IACxC,OAAO,CAAC,OAAO,CAAoC;IAEnD,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAKlD;IAED,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAShD;IAED,0EAA0E;IAC1E,OAAO,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAMpD;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAElC;IAED,yEAAyE;IACzE,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEpC;CACF"}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* All mutable state is delegated to client/state.ts for singleton guarantees.
|
|
22
22
|
* See design/18-build-system.md §"Singleton State Registry"
|
|
23
23
|
*/
|
|
24
|
+
import type { SlotParamsRecord } from '../shared/slot-params.js';
|
|
24
25
|
export interface SsrData {
|
|
25
26
|
/** The request's URL pathname (e.g. '/dashboard/settings') */
|
|
26
27
|
pathname: string;
|
|
@@ -30,6 +31,11 @@ export interface SsrData {
|
|
|
30
31
|
cookies: Map<string, string>;
|
|
31
32
|
/** The request's route params (e.g. { id: '123' }) */
|
|
32
33
|
params: Record<string, string | string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Per-slot params keyed by slot tree path, absent when the request rendered
|
|
36
|
+
* no slot with params of its own (TIM-1285).
|
|
37
|
+
*/
|
|
38
|
+
slotParams?: SlotParamsRecord;
|
|
33
39
|
/**
|
|
34
40
|
* Mutable reference to NavContext for error boundary → pipeline communication.
|
|
35
41
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-data.d.ts","sourceRoot":"","sources":["../../src/client/ssr-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"ssr-data.d.ts","sourceRoot":"","sources":["../../src/client/ssr-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,MAAM,WAAW,OAAO;IACtB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gDAAgD;IAChD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,sBAAsB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACzE;AAkBD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,OAAO,GAAG,SAAS,GAAG,IAAI,CAEjF;AAMD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,IAAI,OAAO,GAAG,SAAS,CAKhD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stale Client — knowing when this bundle has been superseded.
|
|
3
|
+
*
|
|
4
|
+
* Layer 1 of version skew handling (see design/33-version-skew.md) is
|
|
5
|
+
* proactive: the server compares `X-Timber-Deployment-Id` against the current
|
|
6
|
+
* build and answers a stale client with `X-Timber-Reload: 1`. That covers
|
|
7
|
+
* every RSC navigation and every server action — as long as there is a server
|
|
8
|
+
* in the request path.
|
|
9
|
+
*
|
|
10
|
+
* This module is the reactive half, plus the state both halves feed. It
|
|
11
|
+
* recognizes the two error shapes a stale client produces on its own:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Stale client references** — React's Flight client throws
|
|
14
|
+
* "Could not find the module …" when an RSC payload names a client module
|
|
15
|
+
* ID that isn't in this bundle's manifest.
|
|
16
|
+
* 2. **Chunk load failures** — a content-hashed chunk that no longer exists on
|
|
17
|
+
* the CDN fails to `import()`. Every engine words this differently.
|
|
18
|
+
*
|
|
19
|
+
* It matters most for `output: 'static'`, where there is no server to send the
|
|
20
|
+
* reload header and this is the *only* skew detection that can run.
|
|
21
|
+
*
|
|
22
|
+
* **Nothing here reloads the page.** A stale client is recorded, not acted on;
|
|
23
|
+
* the router turns the next user-initiated navigation into a full document
|
|
24
|
+
* load. See design/33-version-skew.md §"Recovery on the next navigation" for
|
|
25
|
+
* why — a self-issued `location.reload()` cannot tell a recovery from a bounce
|
|
26
|
+
* between two live deployments, and mid-rollout it produces exactly that loop.
|
|
27
|
+
*
|
|
28
|
+
* See TIM-1260, TIM-1275.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* True when the error is React's "client reference not in this manifest"
|
|
32
|
+
* failure — the payload was produced by a build this bundle doesn't match.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isStaleClientReference(error: unknown): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* True when the error is a failed dynamic import or CSS preload — the
|
|
37
|
+
* content-hashed chunk this bundle asks for is gone from the CDN.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isChunkLoadError(error: unknown): boolean;
|
|
40
|
+
/** Whether this client has been superseded by a newer deployment. */
|
|
41
|
+
export declare function isClientStale(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Record that the origin has told us this client is stale — the `X-Timber-Reload`
|
|
44
|
+
* header on an RSC or action response. Authoritative, so it is not gated on
|
|
45
|
+
* anything: a dev server never sends the header in the first place.
|
|
46
|
+
*/
|
|
47
|
+
export declare function markClientStale(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Record staleness if `error` is one of the shapes a superseded bundle
|
|
50
|
+
* produces. Returns whether it did, so a caller mid-navigation can decide to
|
|
51
|
+
* hard-navigate instead of surfacing the error.
|
|
52
|
+
*
|
|
53
|
+
* Inference, unlike {@link markClientStale}, so it is gated on being a
|
|
54
|
+
* production build. This is the single place that gate lives — call sites
|
|
55
|
+
* cannot see each other, and "remember to gate the next one too" is not a
|
|
56
|
+
* guarantee.
|
|
57
|
+
*/
|
|
58
|
+
export declare function markStaleFromError(error: unknown): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Arm the module, and register the listeners that catch stale-client errors
|
|
61
|
+
* never passing through the router: a lazily imported client component, a
|
|
62
|
+
* preloaded stylesheet, anything that rejects outside a navigation.
|
|
63
|
+
*
|
|
64
|
+
* No-op when `deploymentId` is null — that is dev. Called from the browser
|
|
65
|
+
* entry before hydration, so it precedes any render or navigation that could
|
|
66
|
+
* throw.
|
|
67
|
+
*/
|
|
68
|
+
export declare function initStaleClient(deploymentId: string | null): void;
|
|
69
|
+
//# sourceMappingURL=stale-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-client.d.ts","sourceRoot":"","sources":["../../src/client/stale-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA4CH;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIxD;AAuBD,qEAAqE;AACrE,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgB1D;AAeD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAejE"}
|
package/dist/client/state.d.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* See design/18-build-system.md §"Module Singleton Strategy" and
|
|
19
19
|
* §"Singleton State Registry".
|
|
20
20
|
*/
|
|
21
|
-
import type {
|
|
21
|
+
import type { SlotParamsRecord } from '../shared/slot-params.js';
|
|
22
|
+
import type { RouterInstance } from './router-types.js';
|
|
22
23
|
import type { SsrData } from './ssr-data.js';
|
|
23
24
|
/** The global router singleton — set once during bootstrap. */
|
|
24
25
|
export declare let globalRouter: RouterInstance | null;
|
|
@@ -35,6 +36,14 @@ export declare function _setCurrentSsrData(data: SsrData | undefined): void;
|
|
|
35
36
|
/** Current route params snapshot — replaced (not mutated) on each navigation. */
|
|
36
37
|
export declare let currentParams: Record<string, string | string[]>;
|
|
37
38
|
export declare function _setCurrentParams(params: Record<string, string | string[]>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Per-slot params snapshot, keyed by slot tree path — the module-level
|
|
41
|
+
* fallback behind `useSegmentParams(slotPath)` when no NavigationContext is
|
|
42
|
+
* mounted (tests, calls outside a component). Replaced, never mutated, on
|
|
43
|
+
* each navigation. See TIM-1285.
|
|
44
|
+
*/
|
|
45
|
+
export declare let currentSlotParams: SlotParamsRecord | null;
|
|
46
|
+
export declare function _setCurrentSlotParams(slotParams: SlotParamsRecord | null): void;
|
|
38
47
|
/** Listeners notified when currentParams changes. */
|
|
39
48
|
export declare const paramsListeners: Set<() => void>;
|
|
40
49
|
/** Cached search string — avoids reparsing when URL hasn't changed. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/client/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/client/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,+DAA+D;AAC/D,eAAO,IAAI,YAAY,EAAE,cAAc,GAAG,IAAW,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAEpE;AAID;;;GAGG;AACH,eAAO,IAAI,eAAe,EAAE,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;AAEpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,IAAI,CAE3F;AAED,gEAAgE;AAChE,eAAO,IAAI,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;AAE/C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAElE;AAID,iFAAiF;AACjF,eAAO,IAAI,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAM,CAAC;AAEjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAEjF;AAED;;;;;GAKG;AACH,eAAO,IAAI,iBAAiB,EAAE,gBAAgB,GAAG,IAAW,CAAC;AAE7D,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAE/E;AAED,qDAAqD;AACrD,eAAO,MAAM,eAAe,YAAiB,IAAI,CAAG,CAAC;AAIrD,uEAAuE;AACvE,eAAO,IAAI,YAAY,QAAK,CAAC;AAC7B,eAAO,IAAI,kBAAkB,iBAAwB,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAG9E;AAID,oDAAoD;AACpD,eAAO,IAAI,SAAS,SAAQ,CAAC;AAE7B,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElD"}
|
|
@@ -41,7 +41,8 @@ export interface TopLoaderConfig {
|
|
|
41
41
|
/**
|
|
42
42
|
* Internal top-loader component. Injected by NavigationRoot.
|
|
43
43
|
*
|
|
44
|
-
* Reads pending navigation state from
|
|
44
|
+
* Reads pending navigation state from the router's external store, via
|
|
45
|
+
* usePendingNavigation() — the one pending representation (TIM-1307).
|
|
45
46
|
* Phase transitions are derived synchronously during render:
|
|
46
47
|
*
|
|
47
48
|
* hidden → crawling: when isPending becomes true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-loader.d.ts","sourceRoot":"","sources":["../../src/client/top-loader.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAmDD
|
|
1
|
+
{"version":3,"file":"top-loader.d.ts","sourceRoot":"","sources":["../../src/client/top-loader.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAmDD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,eAAe,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CA6F7F"}
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* unloaded so error boundaries and error handlers can suppress abort-related
|
|
9
9
|
* errors during the unload window.
|
|
10
10
|
*
|
|
11
|
+
* This is the single owner of "is this document going away". Anything that
|
|
12
|
+
* needs the answer imports `isPageUnloading()` — an unload flag maintained a
|
|
13
|
+
* second time somewhere else is a flag that will drift out of agreement with
|
|
14
|
+
* this one (TIM-1260).
|
|
15
|
+
*
|
|
11
16
|
* Mutable state is delegated to client/state.ts for singleton guarantees.
|
|
12
17
|
* See design/18-build-system.md §"Singleton State Registry"
|
|
13
18
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unload-guard.d.ts","sourceRoot":"","sources":["../../src/client/unload-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"unload-guard.d.ts","sourceRoot":"","sources":["../../src/client/unload-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2CH;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cookie.d.ts","sourceRoot":"","sources":["../../src/client/use-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"use-cookie.d.ts","sourceRoot":"","sources":["../../src/client/use-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,yCAAyC;IACzC,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,yDAAyD;IACzD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;AASlF,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/D;AAED,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAkBtE;AAUD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAErD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,mBAAmB,GACnC,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC,CA8DhF"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* In timber, navigation calls reactRoot.render() which is a root-level
|
|
15
15
|
* render — startTransition has no effect on root renders.
|
|
16
16
|
*
|
|
17
|
-
* Navigation state (
|
|
17
|
+
* Navigation state (pathname, search) is delivered atomically via
|
|
18
18
|
* NavigationContext embedded in the element tree passed to
|
|
19
19
|
* reactRoot.render(). See design/19-client-navigation.md §"NavigationContext".
|
|
20
20
|
*
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
* (populated by ssr-entry.ts) to ensure correct per-request isolation
|
|
19
19
|
* across concurrent requests with streaming Suspense.
|
|
20
20
|
*
|
|
21
|
-
* Reactivity: On the client, useParams() reads from
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Reactivity: On the client, useParams() reads from ParamsContext, published
|
|
22
|
+
* by the one provider the client renders above the merge point
|
|
23
|
+
* (`PayloadRoot`). Params update atomically with the tree because they travel
|
|
24
|
+
* on the same payload root — there is no separate channel that could be
|
|
25
|
+
* seeded a render early or late (TIM-1294, TIM-1297).
|
|
26
26
|
*
|
|
27
27
|
* All mutable state is delegated to client/state.ts for singleton guarantees.
|
|
28
28
|
* See design/18-build-system.md §"Singleton State Registry"
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* Design doc: design/09-typescript.md §"Typed Routes"
|
|
31
31
|
*/
|
|
32
32
|
import type { Routes } from '../index.js';
|
|
33
|
+
import { type SlotParamsRecord } from '../shared/slot-params.js';
|
|
33
34
|
/**
|
|
34
35
|
* Subscribe to params changes.
|
|
35
36
|
* Retained for backward compatibility with tests that verify the
|
|
@@ -58,6 +59,15 @@ export declare function getSnapshot(): Record<string, string | string[]>;
|
|
|
58
59
|
* (ALS-backed).
|
|
59
60
|
*/
|
|
60
61
|
export declare function setCurrentParams(params: Record<string, string | string[]>): void;
|
|
62
|
+
/**
|
|
63
|
+
* Set the per-slot params snapshot in the module-level store.
|
|
64
|
+
*
|
|
65
|
+
* Paired with `setCurrentParams`: the router calls both on every navigation,
|
|
66
|
+
* including with `null` when a response carries no slot params, so a slot's
|
|
67
|
+
* params from the *previous* route cannot be read on the next one. Fill and
|
|
68
|
+
* serve are paired; so are fill and clear. See TIM-1285.
|
|
69
|
+
*/
|
|
70
|
+
export declare function setCurrentSlotParams(slotParams: SlotParamsRecord | null): void;
|
|
61
71
|
/**
|
|
62
72
|
* Notify all legacy subscribers that params have changed.
|
|
63
73
|
*
|
|
@@ -73,9 +83,9 @@ export declare function notifyParamsListeners(): void;
|
|
|
73
83
|
* The optional `_route` argument exists only for TypeScript narrowing —
|
|
74
84
|
* it does not affect the runtime return value.
|
|
75
85
|
*
|
|
76
|
-
* On the client, reads from
|
|
77
|
-
*
|
|
78
|
-
*
|
|
86
|
+
* On the client, reads from ParamsContext, published by `PayloadRoot` above
|
|
87
|
+
* everything the navigation renders. Params update atomically with the RSC
|
|
88
|
+
* tree — no timing gap.
|
|
79
89
|
*
|
|
80
90
|
* During SSR, reads from the ALS-backed SSR data context to ensure
|
|
81
91
|
* per-request isolation across concurrent requests with streaming Suspense.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-segment-params.d.ts","sourceRoot":"","sources":["../../src/client/use-segment-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAS1C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG1D;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAE/D;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAEhF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,MAAM,EACrD,WAAW,EAAE,CAAC,GACb,MAAM,CAAC,CAAC,CAAC,SAAS;IAAE,aAAa,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AACxF,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"use-segment-params.d.ts","sourceRoot":"","sources":["../../src/client/use-segment-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAS1C,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAOvF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG1D;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAE/D;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAEhF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAE9E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,MAAM,EACrD,WAAW,EAAE,CAAC,GACb,MAAM,CAAC,CAAC,CAAC,SAAS;IAAE,aAAa,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AACxF,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC"}
|
package/dist/cookies/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as assertValidCookieName, t as require_dist } from "../_chunks/dist-BA3u1z3W.js";
|
|
2
|
+
import { n as getSsrData } from "../_chunks/ssr-data-14MXm7Pj.js";
|
|
2
3
|
import { a as resolveCodecOrSchema } from "../_chunks/schema-bridge-DT_Tn0Xf.js";
|
|
3
|
-
import
|
|
4
|
-
import { useSyncExternalStore } from "react";
|
|
4
|
+
import * as React$1 from "react";
|
|
5
5
|
import { getCookieJar } from "@timber-js/app/server";
|
|
6
6
|
//#region src/client/use-cookie.ts
|
|
7
7
|
/**
|
|
@@ -83,7 +83,7 @@ function useCookie(name, defaultOptions) {
|
|
|
83
83
|
if (ssrData) return ssrData.cookies.get(name);
|
|
84
84
|
return getCookieValue(name);
|
|
85
85
|
};
|
|
86
|
-
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
86
|
+
const value = React$1.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
87
87
|
assertValidCookieName(name);
|
|
88
88
|
const setCookie = (newValue, options) => {
|
|
89
89
|
if (typeof newValue !== "string") throw new Error(`[timber] useCookie(${JSON.stringify(name)}): value must be a string, got ${typeof newValue}.\n To store a JSON-serializable value, use defineCookie + jsonCookieCodec from\n '@timber-js/app/cookies' and call its useCookie() hook instead.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/client/use-cookie.ts","../../src/cookies/define-cookie.ts","../../src/cookies/json-cookie.ts"],"sourcesContent":["/**\n * useCookie — reactive client-side cookie hook.\n *\n * Uses useSyncExternalStore for SSR-safe, reactive cookie access.\n * All components reading the same cookie name re-render on change.\n * No cross-tab sync (intentional — see design/29-cookies.md).\n *\n * See design/29-cookies.md §\"useCookie(name) Hook\"\n */\n\nimport { useSyncExternalStore } from 'react';\nimport { parseCookie, stringifySetCookie } from 'cookie';\nimport { getSsrData } from './ssr-data.js';\nimport { assertValidCookieName } from '../cookies/validation.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface ClientCookieOptions {\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Domain scope. Default: omitted (current domain). */\n domain?: string;\n /** Max age in seconds. */\n maxAge?: number;\n /** Expiration date. */\n expires?: Date;\n /** Cross-site policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Only send over HTTPS. Default: true in production. */\n secure?: boolean;\n}\n\nexport type CookieSetter = (value: string, options?: ClientCookieOptions) => void;\n\n// ─── Module-Level Cookie Store ────────────────────────────────────────────\n\ntype Listener = () => void;\n\n/** Per-name subscriber sets. */\nconst listeners = new Map<string, Set<Listener>>();\n\n/** Parse a cookie name from document.cookie. */\nexport function getCookieValue(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const cookies = parseCookie(document.cookie);\n return cookies[name];\n}\n\n/** Serialize options into a cookie string suffix. */\nexport function serializeOptions(options?: ClientCookieOptions): string {\n if (!options) return '; Path=/; SameSite=Lax';\n const sameSite = options.sameSite ?? 'lax';\n // Build a Set-Cookie string via the cookie package, then strip the\n // `__placeholder__=` name=value prefix to get just the attributes.\n const full = stringifySetCookie({\n name: '__placeholder__',\n value: '',\n path: options.path ?? '/',\n domain: options.domain,\n maxAge: options.maxAge,\n expires: options.expires,\n sameSite,\n secure: options.secure,\n });\n // Strip everything up to and including the first \"; \"\n const idx = full.indexOf('; ');\n return idx >= 0 ? full.slice(idx) : '';\n}\n\n/** Notify all subscribers for a given cookie name. */\nfunction notify(name: string): void {\n const subs = listeners.get(name);\n if (subs) {\n for (const fn of subs) fn();\n }\n}\n\n/**\n * Notify useCookie subscribers that a cookie value changed.\n * Called by defineCookie's imperative client .set()/.delete() methods\n * so mounted useCookie() consumers re-render.\n *\n * @internal — framework use only\n */\nexport function notifyCookieChange(name: string): void {\n notify(name);\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────────────\n\n/**\n * Reactive hook for reading/writing a client-side cookie.\n *\n * Returns `[value, setCookie, deleteCookie]`:\n * - `value`: current cookie value (string | undefined)\n * - `setCookie`: sets the cookie and triggers re-renders\n * - `deleteCookie`: deletes the cookie and triggers re-renders\n *\n * @param name - Cookie name.\n * @param defaultOptions - Default options for setCookie calls.\n */\nexport function useCookie(\n name: string,\n defaultOptions?: ClientCookieOptions\n): [value: string | undefined, setCookie: CookieSetter, deleteCookie: () => void] {\n const subscribe = (callback: Listener): (() => void) => {\n let subs = listeners.get(name);\n if (!subs) {\n subs = new Set();\n listeners.set(name, subs);\n }\n subs.add(callback);\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) listeners.delete(name);\n };\n };\n\n const getSnapshot = (): string | undefined => getCookieValue(name);\n const getServerSnapshot = (): string | undefined => {\n // During SSR, read the ALS-backed request cookies.\n const ssrData = getSsrData();\n if (ssrData) return ssrData.cookies.get(name);\n // During client hydration there is no SSR data (it lives in the server's\n // module graph), but React still calls getServerSnapshot. document.cookie\n // holds the same cookies the server rendered with, so reading it here\n // keeps the hydration render consistent with the server HTML.\n // See design/29-cookies.md §\"SSR Hydration\".\n return getCookieValue(name);\n };\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n // Validate the name once per hook instance — names are typically\n // stable string literals, so this catches the bug at first render\n // rather than on every write. Throws via `assertValidCookieName` with\n // a precise error if the name violates RFC 7230 token rules.\n assertValidCookieName(name);\n\n const setCookie: CookieSetter = (newValue: string, options?: ClientCookieOptions) => {\n if (typeof newValue !== 'string') {\n throw new Error(\n `[timber] useCookie(${JSON.stringify(name)}): value must be a string, got ${typeof newValue}.\\n` +\n ` To store a JSON-serializable value, use defineCookie + jsonCookieCodec from\\n` +\n ` '@timber-js/app/cookies' and call its useCookie() hook instead.`\n );\n }\n const merged = { ...defaultOptions, ...options };\n // Auto-encode mirrors the server's `cookies().set()` contract:\n // values are URL-encoded on write, URL-decoded on read, so the\n // logical bytes round-trip losslessly. See design/29-cookies.md\n // §\"Encoding Contract\".\n document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;\n notify(name);\n };\n\n const deleteCookie = (): void => {\n const path = defaultOptions?.path ?? '/';\n const domain = defaultOptions?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notify(name);\n };\n\n return [value, setCookie, deleteCookie];\n}\n","/**\n * defineCookie — typed cookie definitions.\n *\n * Bundles name + codec + options into a reusable CookieDefinition<T>\n * with sync .get(), .set(), .delete() isomorphic methods (server + client)\n * and a .useCookie() client hook.\n *\n * Environment detection is call-time: on the server, getCookieJar() is\n * used directly (ALS-backed). On the client, document.cookie helpers\n * and the useCookie hook are used. No registration pattern — imports\n * resolve via the shims plugin (client bundle gets a stub for\n * @timber-js/app/server).\n *\n * Standard Schema objects (Zod, Valibot, ArkType) are auto-detected in\n * the codec option — no explicit fromSchema() wrapper needed.\n *\n * See design/29-cookies.md §\"Typed Cookies with Schema Validation\"\n */\n\nimport type { CookieOptions } from '../server/cookie-context.js';\nimport type { ClientCookieOptions } from '../client/use-cookie.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nimport type { Codec } from '../codec.js';\nimport type { StandardSchemaV1 } from '../schema-bridge.js';\nimport { resolveCodecOrSchema } from '../schema-bridge.js';\n\n/**\n * A codec that converts between string cookie values and typed values.\n * Type alias for the shared Codec<T> protocol.\n */\nexport type CookieCodec<T> = Codec<T>;\n\n/** Options for defineCookie: codec + CookieOptions merged. */\nexport interface DefineCookieOptions<T, HttpOnly extends boolean = boolean> extends CookieOptions {\n /**\n * Codec for parsing/serializing the cookie value.\n * Accepts a Codec<T> or a Standard Schema object (Zod, Valibot, ArkType)\n * which is auto-wrapped via fromSchema.\n */\n codec: CookieCodec<T> | StandardSchemaV1<T>;\n /**\n * Prevent client-side JS access. Default: true.\n * When true (or omitted), client methods (useCookie, get/set/delete on\n * client) are omitted from the return type and throw at runtime.\n */\n httpOnly?: HttpOnly;\n}\n\n/**\n * Server-only cookie definition. Returned when httpOnly is true or omitted.\n * Client methods are absent from the type — accessing them is a TS error.\n */\nexport interface ServerOnlyCookieDefinition<T> {\n readonly name: string;\n readonly options: CookieOptions;\n readonly codec: CookieCodec<T>;\n\n /** Read the typed value. Sync, isomorphic (server + client). */\n get(): T;\n /** Set the typed value. Sync, isomorphic (server + client). */\n set(value: T): void;\n /** Delete the cookie. Sync, isomorphic (server + client). */\n delete(): void;\n}\n\n/**\n * Full cookie definition with client methods. Returned when httpOnly: false.\n */\nexport interface CookieDefinition<T> extends ServerOnlyCookieDefinition<T> {\n /** Client: React hook for reading/writing this cookie. Returns [value, setter, deleter]. */\n useCookie(): [T, (value: T) => void, () => void];\n}\n\n// ─── Direct Imports (no registration) ─────────────────────────────────────\n//\n// Server: getCookieJar is imported from @timber-js/app/server. In the client\n// bundle, the shims plugin replaces @timber-js/app/server with a stub module\n// where getCookieJar throws on call. We detect the environment at call time\n// by trying getCookieJar() — if it throws the stub error, we fall through\n// to the client path.\n//\n// Client: useCookie, getCookieValue, notifyCookieChange\n// are imported from use-cookie.ts. These are safe in all environments (no\n// node:async_hooks dependency).\n//\n// Schema: fromSchema is a pure function — safe in all environments.\n\nimport { getCookieJar } from '@timber-js/app/server';\nimport {\n useCookie as useRawCookie,\n notifyCookieChange,\n getCookieValue,\n} from '../client/use-cookie.js';\n\n// ─── Environment Detection ────────────────────────────────────────────────\n\n/**\n * Try to get the server cookie jar. Returns null if we're in the client\n * environment (stub throws) or if there's no request context.\n *\n * On the server, getCookieJar() throws \"outside of a request context\" if\n * called without an ALS store — this is a real error and is re-thrown.\n * The client stub throws a different message — that's caught and returns null.\n */\nfunction tryGetServerCookieJar(): ReturnType<typeof getCookieJar> | null {\n try {\n return getCookieJar();\n } catch (e) {\n // Server-side \"no request context\" error — re-throw as a real bug.\n if (e instanceof Error && e.message.includes('outside of a request context')) {\n throw e;\n }\n // Client stub error or other — fall through to client path.\n return null;\n }\n}\n\n// ─── Client Cookie Helpers ────────────────────────────────────────────────\n\n/** Write a cookie via document.cookie and notify useCookie subscribers. */\nfunction setClientCookie(name: string, value: string, options?: ClientCookieOptions): void {\n const parts: string[] = [`${name}=${encodeURIComponent(value)}`];\n const path = options?.path ?? '/';\n parts.push(`Path=${path}`);\n if (options?.domain) parts.push(`Domain=${options.domain}`);\n if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n const sameSite = options?.sameSite ?? 'lax';\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n if (options?.secure) parts.push('Secure');\n document.cookie = parts.join('; ');\n notifyCookieChange(name);\n}\n\n/** Delete a cookie via document.cookie and notify useCookie subscribers. */\nfunction deleteClientCookie(name: string, options?: ClientCookieOptions): void {\n const path = options?.path ?? '/';\n const domain = options?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notifyCookieChange(name);\n}\n\n// ─── Standard Schema Auto-Detection ───────────────────────────────────────\n\nfunction resolveCodec<T>(codecOrSchema: CookieCodec<T> | StandardSchemaV1<T>): CookieCodec<T> {\n return resolveCodecOrSchema('codec', codecOrSchema, 'search') as CookieCodec<T>;\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Define a typed cookie.\n *\n * ```ts\n * import { defineCookie } from '@timber-js/app/cookies';\n * import { z } from 'zod/v4';\n *\n * // httpOnly: false — client methods available\n * export const themeCookie = defineCookie('theme', {\n * codec: z.enum(['light', 'dark', 'system']).default('system'),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * // Server or client\n * const theme = themeCookie.get();\n * themeCookie.set('dark');\n *\n * // Client hook\n * const [theme, setTheme] = themeCookie.useCookie();\n *\n * // httpOnly: true (default) — server-only, no client methods\n * export const sessionCookie = defineCookie('session', {\n * codec: z.string(),\n * });\n * sessionCookie.get(); // works on server\n * sessionCookie.useCookie(); // TS error — httpOnly cookie\n * ```\n */\nexport function defineCookie<T, HttpOnly extends boolean = true>(\n name: string,\n options: DefineCookieOptions<T, HttpOnly>\n): HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T> {\n const { codec: codecOrSchema, ...cookieOpts } = options;\n const codec = resolveCodec(codecOrSchema);\n const resolvedOptions: CookieOptions = { ...cookieOpts };\n const isHttpOnly = options.httpOnly !== false; // default true\n\n function getClientOpts(): ClientCookieOptions {\n return {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n maxAge: resolvedOptions.maxAge,\n expires: resolvedOptions.expires,\n sameSite: resolvedOptions.sameSite,\n secure: resolvedOptions.secure,\n };\n }\n\n const base: ServerOnlyCookieDefinition<T> = {\n name,\n options: resolvedOptions,\n codec,\n\n get(): T {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n return codec.parse(serverJar.get(name));\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').get() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n const raw = getCookieValue(name);\n return codec.parse(raw);\n },\n\n set(value: T): void {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n serverJar.delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n } else {\n serverJar.set(name, serialized, resolvedOptions);\n }\n return;\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').set() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteClientCookie(name, getClientOpts());\n } else {\n setClientCookie(name, serialized, getClientOpts());\n }\n },\n\n delete(): void {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n serverJar.delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n return;\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').delete() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n deleteClientCookie(name, getClientOpts());\n },\n };\n\n if (isHttpOnly) {\n return base as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;\n }\n\n // httpOnly: false — add useCookie client hook\n const full: CookieDefinition<T> = {\n ...base,\n\n useCookie(): [T, (value: T) => void, () => void] {\n const clientOpts = getClientOpts();\n const [raw, setRaw, deleteRaw] = useRawCookie(name, clientOpts);\n const parsed = codec.parse(raw);\n\n const setTyped = (value: T): void => {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteRaw();\n } else {\n setRaw(serialized);\n }\n };\n\n return [parsed, setTyped, deleteRaw];\n },\n };\n\n return full as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;\n}\n","/**\n * jsonCookieCodec — Codec helper for storing JSON-serializable values in\n * cookies.\n *\n * The codec is intentionally minimal: `JSON.stringify` on serialize,\n * `JSON.parse` on parse. The framework handles the URL encoding /\n * decoding around it (see design/29-cookies.md §\"Encoding Contract\"):\n *\n * defineCookie.setCookie(value)\n * → codec.serialize: JSON.stringify → '{\"a\":1}'\n * → cookies().set: encodeURIComponent → '%7B%22a%22%3A1%7D'\n * → wire: Set-Cookie: prefs=%7B%22a%22%3A1%7D\n *\n * defineCookie.getCookie()\n * → wire: Cookie: prefs=%7B%22a%22%3A1%7D\n * → parseCookieHeader: decodeURIComponent → '{\"a\":1}'\n * → codec.parse: JSON.parse → { a: 1 }\n *\n * The same chain works on the client: `useCookie` auto-encodes on writes\n * and auto-decodes on reads, so `jsonCookieCodec` composes with both the\n * server `defineCookie` methods and the client `useCookie` hook with no\n * environment-specific branches.\n *\n * Parsing is total: any non-JSON value (or `undefined`) returns the\n * supplied default. This matches the \"never crash on user-controlled\n * cookie input\" semantics that `fromSchema` uses for typed search params.\n *\n * ```ts\n * import { defineCookie, jsonCookieCodec } from '@timber-js/app/cookies';\n *\n * interface Prefs { lang: string; fontSize: number }\n *\n * export const prefsCookie = defineCookie('prefs', {\n * codec: jsonCookieCodec<Prefs>({ lang: 'en', fontSize: 16 }),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * await prefsCookie.setCookie({ lang: 'fr', fontSize: 18 });\n * ```\n */\n\nimport type { Codec } from '../codec.js';\n\n/**\n * Build a CookieCodec that JSON-encodes the value. The framework's\n * cookie write path then URL-encodes the JSON string into a valid\n * `cookie-octet` form, and the read path reverses both transforms.\n *\n * @param defaultValue - Returned by `parse` when the cookie is missing\n * or the stored value cannot be JSON-parsed. Optional — if omitted,\n * `parse` returns `undefined` on missing/malformed input.\n *\n * The default is **deep-cloned** on every fallback return via\n * `structuredClone`, so mutating the parsed result in one request\n * cannot leak into later requests that fall back to the same\n * default. This matters for long-lived server processes where a\n * codec is defined once at module load and shared across all\n * requests — without the clone, a user doing\n * `const prefs = prefsCookie.get(); prefs.lang = 'fr'` on a missing\n * cookie would mutate the module-level default and poison every\n * later fallback. See the \"shared mutable default\" regression test\n * in `tests/define-cookie.test.ts`.\n */\nexport function jsonCookieCodec<T>(defaultValue?: T): Codec<T> {\n // Resolve the fallback at call time, not capture time, so every\n // fallback return produces a fresh deep copy. structuredClone handles\n // nested objects, arrays, dates, maps, sets — everything\n // JSON-serializable plus more. For primitives and undefined,\n // structuredClone is effectively a no-op.\n const cloneDefault = (): T => {\n if (defaultValue === undefined || defaultValue === null) {\n return defaultValue as T;\n }\n // structuredClone is a Node built-in since Node 17, available in\n // all supported runtimes.\n return structuredClone(defaultValue);\n };\n\n return {\n parse(value: string | string[] | undefined): T {\n if (value === undefined || value === '') {\n return cloneDefault();\n }\n // Cookies are single-valued by name; defensively pick the last\n // entry if a Codec consumer somehow passes an array.\n const raw = Array.isArray(value) ? value[value.length - 1] : value;\n if (raw === undefined || raw === '') {\n return cloneDefault();\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n return cloneDefault();\n }\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return JSON.stringify(value);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/client/use-cookie.ts","../../src/cookies/define-cookie.ts","../../src/cookies/json-cookie.ts"],"sourcesContent":["/**\n * useCookie — reactive client-side cookie hook.\n *\n * Uses useSyncExternalStore for SSR-safe, reactive cookie access.\n * All components reading the same cookie name re-render on change.\n * No cross-tab sync (intentional — see design/29-cookies.md).\n *\n * See design/29-cookies.md §\"useCookie(name) Hook\"\n */\n\n// Namespace access, not `import { useSyncExternalStore }`. This module is\n// reachable from `@timber-js/app/cookies` — `defineCookie()` exposes both a\n// server `.get()` and a client `.useCookie()`, so the entry is isomorphic and\n// loads in the RSC environment. React's `react-server` export withholds\n// `useSyncExternalStore` exactly as it withholds `createContext`, and a *named*\n// ESM import of a missing export fails at module instantiation, before any\n// code runs. Same rule as `client/params-context.ts` (TIM-1297, TIM-1298).\nimport * as React from 'react';\nimport { parseCookie, stringifySetCookie } from 'cookie';\nimport { getSsrData } from './ssr-data.js';\nimport { assertValidCookieName } from '../cookies/validation.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface ClientCookieOptions {\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Domain scope. Default: omitted (current domain). */\n domain?: string;\n /** Max age in seconds. */\n maxAge?: number;\n /** Expiration date. */\n expires?: Date;\n /** Cross-site policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Only send over HTTPS. Default: true in production. */\n secure?: boolean;\n}\n\nexport type CookieSetter = (value: string, options?: ClientCookieOptions) => void;\n\n// ─── Module-Level Cookie Store ────────────────────────────────────────────\n\ntype Listener = () => void;\n\n/** Per-name subscriber sets. */\nconst listeners = new Map<string, Set<Listener>>();\n\n/** Parse a cookie name from document.cookie. */\nexport function getCookieValue(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const cookies = parseCookie(document.cookie);\n return cookies[name];\n}\n\n/** Serialize options into a cookie string suffix. */\nexport function serializeOptions(options?: ClientCookieOptions): string {\n if (!options) return '; Path=/; SameSite=Lax';\n const sameSite = options.sameSite ?? 'lax';\n // Build a Set-Cookie string via the cookie package, then strip the\n // `__placeholder__=` name=value prefix to get just the attributes.\n const full = stringifySetCookie({\n name: '__placeholder__',\n value: '',\n path: options.path ?? '/',\n domain: options.domain,\n maxAge: options.maxAge,\n expires: options.expires,\n sameSite,\n secure: options.secure,\n });\n // Strip everything up to and including the first \"; \"\n const idx = full.indexOf('; ');\n return idx >= 0 ? full.slice(idx) : '';\n}\n\n/** Notify all subscribers for a given cookie name. */\nfunction notify(name: string): void {\n const subs = listeners.get(name);\n if (subs) {\n for (const fn of subs) fn();\n }\n}\n\n/**\n * Notify useCookie subscribers that a cookie value changed.\n * Called by defineCookie's imperative client .set()/.delete() methods\n * so mounted useCookie() consumers re-render.\n *\n * @internal — framework use only\n */\nexport function notifyCookieChange(name: string): void {\n notify(name);\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────────────\n\n/**\n * Reactive hook for reading/writing a client-side cookie.\n *\n * Returns `[value, setCookie, deleteCookie]`:\n * - `value`: current cookie value (string | undefined)\n * - `setCookie`: sets the cookie and triggers re-renders\n * - `deleteCookie`: deletes the cookie and triggers re-renders\n *\n * @param name - Cookie name.\n * @param defaultOptions - Default options for setCookie calls.\n */\nexport function useCookie(\n name: string,\n defaultOptions?: ClientCookieOptions\n): [value: string | undefined, setCookie: CookieSetter, deleteCookie: () => void] {\n const subscribe = (callback: Listener): (() => void) => {\n let subs = listeners.get(name);\n if (!subs) {\n subs = new Set();\n listeners.set(name, subs);\n }\n subs.add(callback);\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) listeners.delete(name);\n };\n };\n\n const getSnapshot = (): string | undefined => getCookieValue(name);\n const getServerSnapshot = (): string | undefined => {\n // During SSR, read the ALS-backed request cookies.\n const ssrData = getSsrData();\n if (ssrData) return ssrData.cookies.get(name);\n // During client hydration there is no SSR data (it lives in the server's\n // module graph), but React still calls getServerSnapshot. document.cookie\n // holds the same cookies the server rendered with, so reading it here\n // keeps the hydration render consistent with the server HTML.\n // See design/29-cookies.md §\"SSR Hydration\".\n return getCookieValue(name);\n };\n\n const value = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n // Validate the name once per hook instance — names are typically\n // stable string literals, so this catches the bug at first render\n // rather than on every write. Throws via `assertValidCookieName` with\n // a precise error if the name violates RFC 7230 token rules.\n assertValidCookieName(name);\n\n const setCookie: CookieSetter = (newValue: string, options?: ClientCookieOptions) => {\n if (typeof newValue !== 'string') {\n throw new Error(\n `[timber] useCookie(${JSON.stringify(name)}): value must be a string, got ${typeof newValue}.\\n` +\n ` To store a JSON-serializable value, use defineCookie + jsonCookieCodec from\\n` +\n ` '@timber-js/app/cookies' and call its useCookie() hook instead.`\n );\n }\n const merged = { ...defaultOptions, ...options };\n // Auto-encode mirrors the server's `cookies().set()` contract:\n // values are URL-encoded on write, URL-decoded on read, so the\n // logical bytes round-trip losslessly. See design/29-cookies.md\n // §\"Encoding Contract\".\n document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;\n notify(name);\n };\n\n const deleteCookie = (): void => {\n const path = defaultOptions?.path ?? '/';\n const domain = defaultOptions?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notify(name);\n };\n\n return [value, setCookie, deleteCookie];\n}\n","/**\n * defineCookie — typed cookie definitions.\n *\n * Bundles name + codec + options into a reusable CookieDefinition<T>\n * with sync .get(), .set(), .delete() isomorphic methods (server + client)\n * and a .useCookie() client hook.\n *\n * Environment detection is call-time: on the server, getCookieJar() is\n * used directly (ALS-backed). On the client, document.cookie helpers\n * and the useCookie hook are used. No registration pattern — imports\n * resolve via the shims plugin (client bundle gets a stub for\n * @timber-js/app/server).\n *\n * Standard Schema objects (Zod, Valibot, ArkType) are auto-detected in\n * the codec option — no explicit fromSchema() wrapper needed.\n *\n * See design/29-cookies.md §\"Typed Cookies with Schema Validation\"\n */\n\nimport type { CookieOptions } from '../server/cookie-context.js';\nimport type { ClientCookieOptions } from '../client/use-cookie.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nimport type { Codec } from '../codec.js';\nimport type { StandardSchemaV1 } from '../schema-bridge.js';\nimport { resolveCodecOrSchema } from '../schema-bridge.js';\n\n/**\n * A codec that converts between string cookie values and typed values.\n * Type alias for the shared Codec<T> protocol.\n */\nexport type CookieCodec<T> = Codec<T>;\n\n/** Options for defineCookie: codec + CookieOptions merged. */\nexport interface DefineCookieOptions<T, HttpOnly extends boolean = boolean> extends CookieOptions {\n /**\n * Codec for parsing/serializing the cookie value.\n * Accepts a Codec<T> or a Standard Schema object (Zod, Valibot, ArkType)\n * which is auto-wrapped via fromSchema.\n */\n codec: CookieCodec<T> | StandardSchemaV1<T>;\n /**\n * Prevent client-side JS access. Default: true.\n * When true (or omitted), client methods (useCookie, get/set/delete on\n * client) are omitted from the return type and throw at runtime.\n */\n httpOnly?: HttpOnly;\n}\n\n/**\n * Server-only cookie definition. Returned when httpOnly is true or omitted.\n * Client methods are absent from the type — accessing them is a TS error.\n */\nexport interface ServerOnlyCookieDefinition<T> {\n readonly name: string;\n readonly options: CookieOptions;\n readonly codec: CookieCodec<T>;\n\n /** Read the typed value. Sync, isomorphic (server + client). */\n get(): T;\n /** Set the typed value. Sync, isomorphic (server + client). */\n set(value: T): void;\n /** Delete the cookie. Sync, isomorphic (server + client). */\n delete(): void;\n}\n\n/**\n * Full cookie definition with client methods. Returned when httpOnly: false.\n */\nexport interface CookieDefinition<T> extends ServerOnlyCookieDefinition<T> {\n /** Client: React hook for reading/writing this cookie. Returns [value, setter, deleter]. */\n useCookie(): [T, (value: T) => void, () => void];\n}\n\n// ─── Direct Imports (no registration) ─────────────────────────────────────\n//\n// Server: getCookieJar is imported from @timber-js/app/server. In the client\n// bundle, the shims plugin replaces @timber-js/app/server with a stub module\n// where getCookieJar throws on call. We detect the environment at call time\n// by trying getCookieJar() — if it throws the stub error, we fall through\n// to the client path.\n//\n// Client: useCookie, getCookieValue, notifyCookieChange\n// are imported from use-cookie.ts. These are safe in all environments (no\n// node:async_hooks dependency).\n//\n// Schema: fromSchema is a pure function — safe in all environments.\n\nimport { getCookieJar } from '@timber-js/app/server';\nimport {\n useCookie as useRawCookie,\n notifyCookieChange,\n getCookieValue,\n} from '../client/use-cookie.js';\n\n// ─── Environment Detection ────────────────────────────────────────────────\n\n/**\n * Try to get the server cookie jar. Returns null if we're in the client\n * environment (stub throws) or if there's no request context.\n *\n * On the server, getCookieJar() throws \"outside of a request context\" if\n * called without an ALS store — this is a real error and is re-thrown.\n * The client stub throws a different message — that's caught and returns null.\n */\nfunction tryGetServerCookieJar(): ReturnType<typeof getCookieJar> | null {\n try {\n return getCookieJar();\n } catch (e) {\n // Server-side \"no request context\" error — re-throw as a real bug.\n if (e instanceof Error && e.message.includes('outside of a request context')) {\n throw e;\n }\n // Client stub error or other — fall through to client path.\n return null;\n }\n}\n\n// ─── Client Cookie Helpers ────────────────────────────────────────────────\n\n/** Write a cookie via document.cookie and notify useCookie subscribers. */\nfunction setClientCookie(name: string, value: string, options?: ClientCookieOptions): void {\n const parts: string[] = [`${name}=${encodeURIComponent(value)}`];\n const path = options?.path ?? '/';\n parts.push(`Path=${path}`);\n if (options?.domain) parts.push(`Domain=${options.domain}`);\n if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n const sameSite = options?.sameSite ?? 'lax';\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n if (options?.secure) parts.push('Secure');\n document.cookie = parts.join('; ');\n notifyCookieChange(name);\n}\n\n/** Delete a cookie via document.cookie and notify useCookie subscribers. */\nfunction deleteClientCookie(name: string, options?: ClientCookieOptions): void {\n const path = options?.path ?? '/';\n const domain = options?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notifyCookieChange(name);\n}\n\n// ─── Standard Schema Auto-Detection ───────────────────────────────────────\n\nfunction resolveCodec<T>(codecOrSchema: CookieCodec<T> | StandardSchemaV1<T>): CookieCodec<T> {\n return resolveCodecOrSchema('codec', codecOrSchema, 'search') as CookieCodec<T>;\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Define a typed cookie.\n *\n * ```ts\n * import { defineCookie } from '@timber-js/app/cookies';\n * import { z } from 'zod/v4';\n *\n * // httpOnly: false — client methods available\n * export const themeCookie = defineCookie('theme', {\n * codec: z.enum(['light', 'dark', 'system']).default('system'),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * // Server or client\n * const theme = themeCookie.get();\n * themeCookie.set('dark');\n *\n * // Client hook\n * const [theme, setTheme] = themeCookie.useCookie();\n *\n * // httpOnly: true (default) — server-only, no client methods\n * export const sessionCookie = defineCookie('session', {\n * codec: z.string(),\n * });\n * sessionCookie.get(); // works on server\n * sessionCookie.useCookie(); // TS error — httpOnly cookie\n * ```\n */\nexport function defineCookie<T, HttpOnly extends boolean = true>(\n name: string,\n options: DefineCookieOptions<T, HttpOnly>\n): HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T> {\n const { codec: codecOrSchema, ...cookieOpts } = options;\n const codec = resolveCodec(codecOrSchema);\n const resolvedOptions: CookieOptions = { ...cookieOpts };\n const isHttpOnly = options.httpOnly !== false; // default true\n\n function getClientOpts(): ClientCookieOptions {\n return {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n maxAge: resolvedOptions.maxAge,\n expires: resolvedOptions.expires,\n sameSite: resolvedOptions.sameSite,\n secure: resolvedOptions.secure,\n };\n }\n\n const base: ServerOnlyCookieDefinition<T> = {\n name,\n options: resolvedOptions,\n codec,\n\n get(): T {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n return codec.parse(serverJar.get(name));\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').get() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n const raw = getCookieValue(name);\n return codec.parse(raw);\n },\n\n set(value: T): void {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n serverJar.delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n } else {\n serverJar.set(name, serialized, resolvedOptions);\n }\n return;\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').set() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteClientCookie(name, getClientOpts());\n } else {\n setClientCookie(name, serialized, getClientOpts());\n }\n },\n\n delete(): void {\n const serverJar = tryGetServerCookieJar();\n if (serverJar) {\n serverJar.delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n return;\n }\n // Client path\n if (isHttpOnly) {\n throw new Error(\n `[timber] defineCookie('${name}').delete() cannot be used with httpOnly cookies — ` +\n `the browser cannot access them. Set httpOnly: false to enable client access.`\n );\n }\n deleteClientCookie(name, getClientOpts());\n },\n };\n\n if (isHttpOnly) {\n return base as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;\n }\n\n // httpOnly: false — add useCookie client hook\n const full: CookieDefinition<T> = {\n ...base,\n\n useCookie(): [T, (value: T) => void, () => void] {\n const clientOpts = getClientOpts();\n const [raw, setRaw, deleteRaw] = useRawCookie(name, clientOpts);\n const parsed = codec.parse(raw);\n\n const setTyped = (value: T): void => {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteRaw();\n } else {\n setRaw(serialized);\n }\n };\n\n return [parsed, setTyped, deleteRaw];\n },\n };\n\n return full as HttpOnly extends false ? CookieDefinition<T> : ServerOnlyCookieDefinition<T>;\n}\n","/**\n * jsonCookieCodec — Codec helper for storing JSON-serializable values in\n * cookies.\n *\n * The codec is intentionally minimal: `JSON.stringify` on serialize,\n * `JSON.parse` on parse. The framework handles the URL encoding /\n * decoding around it (see design/29-cookies.md §\"Encoding Contract\"):\n *\n * defineCookie.setCookie(value)\n * → codec.serialize: JSON.stringify → '{\"a\":1}'\n * → cookies().set: encodeURIComponent → '%7B%22a%22%3A1%7D'\n * → wire: Set-Cookie: prefs=%7B%22a%22%3A1%7D\n *\n * defineCookie.getCookie()\n * → wire: Cookie: prefs=%7B%22a%22%3A1%7D\n * → parseCookieHeader: decodeURIComponent → '{\"a\":1}'\n * → codec.parse: JSON.parse → { a: 1 }\n *\n * The same chain works on the client: `useCookie` auto-encodes on writes\n * and auto-decodes on reads, so `jsonCookieCodec` composes with both the\n * server `defineCookie` methods and the client `useCookie` hook with no\n * environment-specific branches.\n *\n * Parsing is total: any non-JSON value (or `undefined`) returns the\n * supplied default. This matches the \"never crash on user-controlled\n * cookie input\" semantics that `fromSchema` uses for typed search params.\n *\n * ```ts\n * import { defineCookie, jsonCookieCodec } from '@timber-js/app/cookies';\n *\n * interface Prefs { lang: string; fontSize: number }\n *\n * export const prefsCookie = defineCookie('prefs', {\n * codec: jsonCookieCodec<Prefs>({ lang: 'en', fontSize: 16 }),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * await prefsCookie.setCookie({ lang: 'fr', fontSize: 18 });\n * ```\n */\n\nimport type { Codec } from '../codec.js';\n\n/**\n * Build a CookieCodec that JSON-encodes the value. The framework's\n * cookie write path then URL-encodes the JSON string into a valid\n * `cookie-octet` form, and the read path reverses both transforms.\n *\n * @param defaultValue - Returned by `parse` when the cookie is missing\n * or the stored value cannot be JSON-parsed. Optional — if omitted,\n * `parse` returns `undefined` on missing/malformed input.\n *\n * The default is **deep-cloned** on every fallback return via\n * `structuredClone`, so mutating the parsed result in one request\n * cannot leak into later requests that fall back to the same\n * default. This matters for long-lived server processes where a\n * codec is defined once at module load and shared across all\n * requests — without the clone, a user doing\n * `const prefs = prefsCookie.get(); prefs.lang = 'fr'` on a missing\n * cookie would mutate the module-level default and poison every\n * later fallback. See the \"shared mutable default\" regression test\n * in `tests/define-cookie.test.ts`.\n */\nexport function jsonCookieCodec<T>(defaultValue?: T): Codec<T> {\n // Resolve the fallback at call time, not capture time, so every\n // fallback return produces a fresh deep copy. structuredClone handles\n // nested objects, arrays, dates, maps, sets — everything\n // JSON-serializable plus more. For primitives and undefined,\n // structuredClone is effectively a no-op.\n const cloneDefault = (): T => {\n if (defaultValue === undefined || defaultValue === null) {\n return defaultValue as T;\n }\n // structuredClone is a Node built-in since Node 17, available in\n // all supported runtimes.\n return structuredClone(defaultValue);\n };\n\n return {\n parse(value: string | string[] | undefined): T {\n if (value === undefined || value === '') {\n return cloneDefault();\n }\n // Cookies are single-valued by name; defensively pick the last\n // entry if a Codec consumer somehow passes an array.\n const raw = Array.isArray(value) ? value[value.length - 1] : value;\n if (raw === undefined || raw === '') {\n return cloneDefault();\n }\n try {\n return JSON.parse(raw) as T;\n } catch {\n return cloneDefault();\n }\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return JSON.stringify(value);\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;AA+BjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAa1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElG;;;;;;;;;GASG;AAEH,MAAM,WAAW,MAAM;CAAG;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAEvE;AAgGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;AA+BjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAa1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElG;;;;;;;;;GASG;AAEH,MAAM,WAAW,MAAM;CAAG;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAEvE;AAgGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAiXhE;eAEc,MAAM"}
|