@timber-js/app 0.2.0-alpha.185 → 0.2.0-alpha.187
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-35jnMdeJ.js} +4 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-35jnMdeJ.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
- package/dist/_chunks/als-slots-BEEIPKYm.js +26 -0
- package/dist/_chunks/als-slots-BEEIPKYm.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-DjNrIWRR.js} +8 -14
- package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-CGP6BZKO.js} +92 -14
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
- package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
- package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
- package/dist/_chunks/error-boundary-D-ODYX41.js +353 -0
- package/dist/_chunks/error-boundary-D-ODYX41.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/file-cache-DmX7OqZP.js +454 -0
- package/dist/_chunks/file-cache-DmX7OqZP.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-D8xJZXIN.js} +34 -51
- package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
- package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
- package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-rCinWLiE.js} +8 -2
- package/dist/_chunks/plugin-context-rCinWLiE.js.map +1 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
- 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-media-type-DRqE_lD_.js +46 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.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/scanner-C8b0Gcw3.js +1564 -0
- package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.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/segment-context-CjOlyB8Y.js +97 -0
- package/dist/_chunks/segment-context-CjOlyB8Y.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-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
- package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js +398 -0
- package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
- package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
- package/dist/_chunks/walkers-RzN6AFjr.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/compress-module.d.ts +12 -0
- package/dist/adapters/compress-module.d.ts.map +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 +56 -5
- 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/cdn/cloudflare-purge.js +30 -0
- package/dist/cdn/cloudflare-purge.js.map +1 -0
- package/dist/cdn/fastly-purge.js +33 -0
- package/dist/cdn/fastly-purge.js.map +1 -0
- package/dist/cdn/index.js +94 -0
- package/dist/cdn/index.js.map +1 -0
- package/dist/cdn/workers-cache-purge.js +35 -0
- package/dist/cdn/workers-cache-purge.js.map +1 -0
- package/dist/cli-check.d.ts +153 -0
- package/dist/cli-check.d.ts.map +1 -0
- package/dist/cli.d.ts +34 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +46 -21
- package/dist/cli.js.map +1 -1
- 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/index.d.ts +1 -1
- package/dist/client/browser-entry/index.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 +7 -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.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +42 -41
- 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 +877 -311
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +1 -7
- 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 +87 -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 +47 -26
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +87 -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-query-states.d.ts +9 -3
- package/dist/client/use-query-states.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/codec.js +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +5 -5
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1009 -759
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/plugin-context.d.ts +46 -0
- 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.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +160 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-shared.d.ts +3 -44
- package/dist/routing/codegen-shared.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +28 -31
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +51 -0
- package/dist/routing/codegen-write.d.ts.map +1 -0
- 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/convention-lint.d.ts +18 -4
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +16 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +4 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/link-codegen.d.ts +19 -4
- package/dist/routing/link-codegen.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +1 -7
- 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 +0 -6
- 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/schema-bridge.d.ts +60 -9
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +62 -14
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.d.ts +0 -1
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +221 -10
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/parse-total.d.ts +70 -0
- package/dist/search-params/parse-total.d.ts.map +1 -0
- package/dist/search-params/wrappers.d.ts +26 -3
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts +19 -8
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +18 -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/compress.d.ts.map +1 -1
- 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 +148 -15
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +15 -4
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +85 -15
- package/dist/server/error-boundary-wrapper.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.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +3 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +712 -418
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +52 -0
- package/dist/server/metadata-collector.d.ts.map +1 -0
- package/dist/server/param-coercion.d.ts +22 -6
- 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/primitives.d.ts +23 -0
- 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 +22 -3
- 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/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +15 -3
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -17
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +0 -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/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +1 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +15 -2
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.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 +17 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +0 -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 +8 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +28 -36
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +12 -7
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +40 -0
- package/dist/server/utils/element-type.d.ts.map +1 -0
- 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-media-type.d.ts +40 -0
- package/dist/shared/rsc-media-type.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/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/api/33-api-search-params.mdx +38 -16
- package/docs/api/35-api-typescript.mdx +3 -3
- package/docs/api/36-cli.mdx +34 -7
- package/docs/learn/00-introduction.mdx +1 -1
- package/docs/learn/02-pages-and-layouts.mdx +2 -2
- package/docs/learn/05-typed-params.mdx +8 -8
- package/docs/learn/07-typed-routes.mdx +12 -7
- package/docs/learn/11-error-handling.mdx +18 -0
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +173 -3
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/docs/more/50-ai-agent-instructions.mdx +6 -4
- package/package.json +9 -6
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +90 -12
- package/src/adapters/compress-module.ts +79 -1
- 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/cli-check.ts +458 -0
- package/src/cli.ts +59 -24
- package/src/client/browser-entry/action-dispatch.ts +11 -5
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -6
- 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 +85 -16
- package/src/client/history.ts +13 -6
- package/src/client/index.ts +14 -3
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +120 -72
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +197 -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 +393 -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 +243 -707
- package/src/client/rsc-fetch.ts +183 -106
- package/src/client/segment-cache.ts +129 -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-query-states.ts +102 -39
- package/src/client/use-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/cookies/define-cookie.ts +6 -1
- package/src/index.ts +25 -4
- package/src/plugin-context.ts +58 -0
- 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 +97 -102
- package/src/plugins/shims.ts +0 -1
- package/src/plugins/static-build.ts +391 -95
- package/src/routing/codegen-shared.ts +3 -79
- package/src/routing/codegen-types.ts +28 -31
- package/src/routing/codegen-write.ts +139 -0
- package/src/routing/codegen.ts +86 -172
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/convention-lint.ts +139 -40
- package/src/routing/export-detect.ts +151 -7
- package/src/routing/interception.ts +896 -50
- package/src/routing/link-codegen.ts +32 -65
- package/src/routing/manifest-codegen.ts +6 -62
- package/src/routing/scanner.ts +117 -92
- 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 +0 -6
- package/src/routing/walkers.ts +8 -0
- package/src/schema-bridge.ts +180 -58
- package/src/search-params/define.ts +108 -66
- package/src/search-params/index.ts +0 -1
- package/src/search-params/parse-total.ts +78 -0
- package/src/search-params/wrappers.ts +60 -11
- package/src/server/access-gate.tsx +60 -40
- package/src/server/action-handler.ts +4 -5
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +18 -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/compress.ts +9 -1
- package/src/server/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +269 -41
- package/src/server/deny-renderer.ts +111 -39
- package/src/server/error-boundary-wrapper.ts +166 -79
- package/src/server/head-response.ts +54 -0
- package/src/server/index.ts +1 -3
- package/src/server/internal.ts +2 -2
- package/src/server/metadata-collector.ts +115 -0
- package/src/server/param-coercion.ts +51 -69
- package/src/server/pipeline-helpers.ts +2 -47
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +4 -5
- package/src/server/pipeline-phases.ts +77 -25
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +25 -0
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +241 -253
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +164 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/deny-fallback.ts +3 -2
- package/src/server/rsc-entry/error-renderer.ts +94 -35
- package/src/server/rsc-entry/helpers.ts +33 -28
- package/src/server/rsc-entry/index.ts +36 -33
- package/src/server/rsc-entry/render-route.ts +18 -10
- package/src/server/rsc-entry/revalidate-renderer.ts +3 -2
- package/src/server/rsc-entry/rsc-payload.ts +113 -40
- package/src/server/rsc-entry/rsc-stream.ts +29 -5
- package/src/server/rsc-entry/ssr-renderer.ts +38 -22
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +263 -158
- package/src/server/ssr-bridge-types.ts +18 -0
- package/src/server/ssr-entry.ts +20 -10
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +68 -29
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +28 -11
- package/src/server/tree-builder.ts +35 -218
- package/src/server/types.ts +12 -7
- package/src/server/utils/element-type.ts +72 -0
- 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-media-type.ts +43 -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/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/cache-api-B-lhk9p4.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.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/registry-DbJPKoBp.js +0 -20
- package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
- package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
- package/dist/_chunks/use-query-states-DFvWd-EA.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-_6zKFlch.js +0 -1002
- package/dist/_chunks/walkers-_6zKFlch.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/dist/search-params/registry.d.ts +0 -20
- package/dist/search-params/registry.d.ts.map +0 -1
- package/dist/segment-params/define.d.ts +0 -89
- package/dist/segment-params/define.d.ts.map +0 -1
- package/dist/segment-params/index.d.ts +0 -3
- package/dist/segment-params/index.d.ts.map +0 -1
- package/dist/segment-params/index.js +0 -2
- package/src/plugins/request-dep.ts +0 -499
- package/src/search-params/registry.ts +0 -31
- package/src/segment-params/define.ts +0 -249
- package/src/segment-params/index.ts +0 -9
|
@@ -7,12 +7,90 @@
|
|
|
7
7
|
// hand-written string template, any fix to server/compress.ts automatically
|
|
8
8
|
// flows to the production module. See design/18-build-system.md ("entry
|
|
9
9
|
// modules are real TypeScript files, not codegen strings").
|
|
10
|
+
//
|
|
11
|
+
// The transform strips types; it does NOT bundle. The emitted file sits
|
|
12
|
+
// alone in the build output, so any import specifier that survives into it
|
|
13
|
+
// is a module resolution failure at runtime on the deployed server. Shared
|
|
14
|
+
// constants are therefore interpolated here — the same treatment the
|
|
15
|
+
// Cloudflare worker template gives `RSC_KEY_HEADERS` — and anything else
|
|
16
|
+
// that reaches the output as an import is rejected with a build error
|
|
17
|
+
// rather than shipped.
|
|
10
18
|
|
|
11
19
|
import { transformWithOxc } from 'vite';
|
|
12
20
|
// Vite's ?raw import embeds the file as a string at build time — works
|
|
13
21
|
// in both dev (Vite resolves the TS source) and the published package
|
|
14
22
|
// (the string is inlined into the compiled adapter entry).
|
|
15
23
|
import compressSource from '../server/compress.ts?raw';
|
|
24
|
+
import { RSC_CONTENT_TYPE } from '../shared/rsc-media-type.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The one import `server/compress.ts` is allowed to carry, and its inlined
|
|
28
|
+
* form.
|
|
29
|
+
*
|
|
30
|
+
* The binding list is matched exactly rather than as `{…}`. A second binding
|
|
31
|
+
* added to that import would otherwise be erased along with the statement and
|
|
32
|
+
* become a `ReferenceError` in the deployed `_compress.mjs` — the failure this
|
|
33
|
+
* whole module exists to make impossible. Matching exactly means such an
|
|
34
|
+
* import falls through to the rejection below instead.
|
|
35
|
+
*/
|
|
36
|
+
const INLINED_IMPORTS: ReadonlyArray<{ pattern: RegExp; replacement: string }> = [
|
|
37
|
+
{
|
|
38
|
+
pattern:
|
|
39
|
+
/^import\s*\{\s*RSC_CONTENT_TYPE\s*,?\s*\}\s*from\s*["'][^"']*shared\/rsc-media-type\.js["'];?[ \t]*$/m,
|
|
40
|
+
replacement: `const RSC_CONTENT_TYPE = ${JSON.stringify(RSC_CONTENT_TYPE)};`,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Every way a module specifier can reach the emitted file, minus Node
|
|
46
|
+
* builtins.
|
|
47
|
+
*
|
|
48
|
+
* `node:*` is exempt because it resolves on the deployed server exactly as it
|
|
49
|
+
* does here — `_compress.mjs` already imports `node:zlib` and `node:stream`,
|
|
50
|
+
* and self-hosted (Node/Bun) is the only place this file runs. Everything
|
|
51
|
+
* else — relative paths, bare packages — has no resolution target next to a
|
|
52
|
+
* lone `.mjs` in the build output.
|
|
53
|
+
*
|
|
54
|
+
* All three forms are listed rather than the one `server/compress.ts` happens
|
|
55
|
+
* to use, because a re-export or a lazy `await import()` fails in exactly the
|
|
56
|
+
* same place and is exactly as invisible until deploy.
|
|
57
|
+
*/
|
|
58
|
+
const MODULE_SPECIFIERS: readonly RegExp[] = [
|
|
59
|
+
// import … from '…' / export … from '…'
|
|
60
|
+
/^[ \t]*(?:import|export)\b[^\n]*\bfrom\s*["'](?!node:)[^"']+["']/m,
|
|
61
|
+
// import '…' (side effect only)
|
|
62
|
+
/^[ \t]*import\s*["'](?!node:)[^"']+["']/m,
|
|
63
|
+
// import('…') (dynamic, literal specifier)
|
|
64
|
+
/\bimport\s*\(\s*["'](?!node:)[^"']+["']/,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Replace the shared-module imports with their values and reject anything
|
|
69
|
+
* else that would leave a dangling specifier in `_compress.mjs`.
|
|
70
|
+
*
|
|
71
|
+
* Exported separately from `generateCompressModule` so both branches can be
|
|
72
|
+
* driven with fixtures: the source it normally compiles is fixed, so a test
|
|
73
|
+
* of the generator alone can only ever exercise whichever branch that source
|
|
74
|
+
* happens to take today.
|
|
75
|
+
*
|
|
76
|
+
* @internal Exported for testing.
|
|
77
|
+
*/
|
|
78
|
+
export function inlineCompressImports(code: string): string {
|
|
79
|
+
let out = code;
|
|
80
|
+
for (const { pattern, replacement } of INLINED_IMPORTS) {
|
|
81
|
+
out = out.replace(pattern, replacement);
|
|
82
|
+
}
|
|
83
|
+
const surviving = MODULE_SPECIFIERS.map((pattern) => pattern.exec(out)).find(Boolean);
|
|
84
|
+
if (surviving) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`[timber] server/compress.ts is emitted as a standalone _compress.mjs (type-stripped, ` +
|
|
87
|
+
`not bundled), so it cannot import anything at runtime. Found: ${surviving[0].trim()}\n` +
|
|
88
|
+
` Either inline the value in adapters/compress-module.ts (see INLINED_IMPORTS) or ` +
|
|
89
|
+
`keep the dependency out of server/compress.ts.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
16
94
|
|
|
17
95
|
/**
|
|
18
96
|
* Compile server/compress.ts into a standalone ESM module.
|
|
@@ -31,6 +109,6 @@ export async function generateCompressModule(): Promise<string> {
|
|
|
31
109
|
return (
|
|
32
110
|
'// Generated by @timber-js/app — compiled from server/compress.ts.\n' +
|
|
33
111
|
'// Do not edit — this file is regenerated on each build.\n' +
|
|
34
|
-
result.code
|
|
112
|
+
inlineCompressImports(result.code)
|
|
35
113
|
);
|
|
36
114
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the output filesystem considers "the same file".
|
|
3
|
+
*
|
|
4
|
+
* Two build steps compare paths to decide whether two artifacts would land on
|
|
5
|
+
* one file — the render pass (`plugins/static-build.ts`) and the adapter's
|
|
6
|
+
* tree merge (`build-output-helper.ts`). Both are predicting a filesystem
|
|
7
|
+
* decision, so both ask the filesystem rather than comparing raw strings
|
|
8
|
+
* (TIM-1266).
|
|
9
|
+
*
|
|
10
|
+
* Two properties matter, and they are independent:
|
|
11
|
+
*
|
|
12
|
+
* - **Case folding.** macOS APFS and Windows NTFS fold by default, ext4 does
|
|
13
|
+
* not. Dynamic params `Foo` and `foo` are one file on a Mac and two on
|
|
14
|
+
* Linux CI.
|
|
15
|
+
* - **Unicode normalization.** APFS compares normalization-insensitively, so
|
|
16
|
+
* `café` spelled NFC (`é`) and NFD (`e` + combining acute) address one
|
|
17
|
+
* file. This holds even on a *case-sensitive* APFS volume, which is why it
|
|
18
|
+
* is probed separately rather than inferred from case folding.
|
|
19
|
+
*
|
|
20
|
+
* Probed rather than read off `process.platform`: a case-sensitive volume on
|
|
21
|
+
* macOS and a case-insensitive mount on Linux are both ordinary, and the
|
|
22
|
+
* answer that matters is the one for the directory being written.
|
|
23
|
+
*
|
|
24
|
+
* Leaf module — Node built-ins only. Deliberately not in `adapters/shared.ts`,
|
|
25
|
+
* which `server/asset-headers.ts` re-exports from and which therefore must
|
|
26
|
+
* stay free of `node:fs` for the Workers bundle.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { writeFile, stat, rm } from 'node:fs/promises';
|
|
30
|
+
import { randomUUID } from 'node:crypto';
|
|
31
|
+
import { join } from 'node:path';
|
|
32
|
+
|
|
33
|
+
/** How the output filesystem decides that two paths name one file. */
|
|
34
|
+
export interface FsIdentity {
|
|
35
|
+
/** `Foo` and `foo` are the same file. */
|
|
36
|
+
caseInsensitive: boolean;
|
|
37
|
+
/** Canonically equivalent Unicode spellings are the same file. */
|
|
38
|
+
normalizationInsensitive: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Trailing dots and spaces are stripped from each segment, so `foo.` and
|
|
41
|
+
* `foo` are the same file. Win32 does this in path resolution rather than
|
|
42
|
+
* in the filesystem, which is why it is probed like the others.
|
|
43
|
+
*/
|
|
44
|
+
stripsTrailingDotsAndSpaces: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Every path comparison is exact — the conservative answer when unprobed. */
|
|
48
|
+
export const EXACT_FS_IDENTITY: FsIdentity = {
|
|
49
|
+
caseInsensitive: false,
|
|
50
|
+
normalizationInsensitive: false,
|
|
51
|
+
stripsTrailingDotsAndSpaces: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Probe `dir` by writing one file and asking for it under other spellings.
|
|
56
|
+
*
|
|
57
|
+
* Returns {@link EXACT_FS_IDENTITY} when the probe cannot run. That
|
|
58
|
+
* under-detects, but a directory this cannot write to is one the caller is
|
|
59
|
+
* about to fail on anyway, with a clearer error than a missed collision.
|
|
60
|
+
*/
|
|
61
|
+
export async function detectFsIdentity(dir: string): Promise<FsIdentity> {
|
|
62
|
+
// `café` — NFC, with a precomposed é. The UUID contributes the
|
|
63
|
+
// hex letters the case probe needs.
|
|
64
|
+
const name = `.timber-probe-${randomUUID()}-café`;
|
|
65
|
+
const path = join(dir, name);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await writeFile(path, '');
|
|
69
|
+
const [caseInsensitive, normalizationInsensitive, stripsTrailingDotsAndSpaces] =
|
|
70
|
+
await Promise.all([
|
|
71
|
+
exists(join(dir, name.toUpperCase())),
|
|
72
|
+
// NFD spells the same string as `e` + U+0301; a filesystem that
|
|
73
|
+
// resolves it to the NFC file compares normalization-insensitively.
|
|
74
|
+
exists(join(dir, name.normalize('NFD'))),
|
|
75
|
+
// Win32 strips trailing dots and spaces during path resolution, so
|
|
76
|
+
// asking for `name.` reaches `name`.
|
|
77
|
+
exists(join(dir, `${name}.`)),
|
|
78
|
+
]);
|
|
79
|
+
return { caseInsensitive, normalizationInsensitive, stripsTrailingDotsAndSpaces };
|
|
80
|
+
} catch {
|
|
81
|
+
return EXACT_FS_IDENTITY;
|
|
82
|
+
} finally {
|
|
83
|
+
await rm(path, { force: true }).catch(() => {});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Fold a path to the identity the filesystem compares it under.
|
|
89
|
+
*
|
|
90
|
+
* Normalization first, then case: `toLowerCase()` operates per code point, so
|
|
91
|
+
* folding a decomposed string first would leave the combining marks in a
|
|
92
|
+
* different order than folding a precomposed one.
|
|
93
|
+
*
|
|
94
|
+
* An approximation of the filesystem's own algorithm — APFS and NTFS fold per
|
|
95
|
+
* their own Unicode tables, which `toLowerCase()` matches for everything short
|
|
96
|
+
* of the exotic. It errs toward *finding* a collision, and the cost of a false
|
|
97
|
+
* positive is a build error naming two routes rather than a silent overwrite.
|
|
98
|
+
*/
|
|
99
|
+
export function fsKey(path: string, identity: FsIdentity): string {
|
|
100
|
+
let key = identity.caseInsensitive ? caseFold(path) : path;
|
|
101
|
+
if (identity.normalizationInsensitive) key = key.normalize('NFC');
|
|
102
|
+
if (identity.stripsTrailingDotsAndSpaces) key = key.split('/').map(stripTrailing).join('/');
|
|
103
|
+
return key;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Approximate Unicode case folding, which is what case-insensitive
|
|
108
|
+
* filesystems actually compare under.
|
|
109
|
+
*
|
|
110
|
+
* `toLowerCase()` is not it. Greek `σ` and final `ς` are both already
|
|
111
|
+
* lowercase and stay distinct under it, yet NTFS upcases both to `Σ` and APFS
|
|
112
|
+
* folds `ς` to `σ` — so two routes differing only in that letter are one file
|
|
113
|
+
* on both, and a lowercase key admitted them as two claims (TIM-1266).
|
|
114
|
+
* Uppercasing first collapses them.
|
|
115
|
+
*
|
|
116
|
+
* The residual is over-folding, in these cases and a few like them:
|
|
117
|
+
*
|
|
118
|
+
* | pair | folded together here | NTFS / APFS |
|
|
119
|
+
* | --------- | -------------------- | ----------------- |
|
|
120
|
+
* | `σ` / `ς` | yes | yes — the fix |
|
|
121
|
+
* | `ß` / `ss`| yes | no |
|
|
122
|
+
* | `ı` / `i` | yes | no |
|
|
123
|
+
* | `fi` / `fi`| yes | no |
|
|
124
|
+
*
|
|
125
|
+
* That direction is deliberate. Over-folding costs a build error naming two
|
|
126
|
+
* routes, which a developer fixes by renaming one; under-folding costs a
|
|
127
|
+
* payload silently overwritten by another route's, which nothing downstream
|
|
128
|
+
* can detect. An exact fold would need `Intl.Collator`, which compares but
|
|
129
|
+
* emits no sort key, so using it would turn a hash lookup into a pairwise
|
|
130
|
+
* scan over every claimed path.
|
|
131
|
+
*/
|
|
132
|
+
function caseFold(value: string): string {
|
|
133
|
+
return value.toUpperCase().toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Drop trailing dots and spaces from one path segment, as Win32 does.
|
|
138
|
+
*
|
|
139
|
+
* A segment that is nothing but dots and spaces is left alone: `.` and `..`
|
|
140
|
+
* are traversal, which the caller rejects before reaching here, and emptying
|
|
141
|
+
* a segment would merge two unrelated paths rather than the aliases this
|
|
142
|
+
* models.
|
|
143
|
+
*/
|
|
144
|
+
function stripTrailing(segment: string): string {
|
|
145
|
+
const stripped = segment.replace(/[. ]+$/, '');
|
|
146
|
+
return stripped === '' ? segment : stripped;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function exists(path: string): Promise<boolean> {
|
|
150
|
+
try {
|
|
151
|
+
await stat(path);
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/adapters/nitro.ts
CHANGED
|
@@ -10,11 +10,12 @@ import { execFile } from 'node:child_process';
|
|
|
10
10
|
import { join, relative, dirname, basename } from 'node:path';
|
|
11
11
|
import type { TimberPlatformAdapter, TimberConfig } from './types';
|
|
12
12
|
import { generateCompressModule } from './compress-module.js';
|
|
13
|
-
import { IMMUTABLE_CACHE, generateHeadersFile } from './shared.js';
|
|
13
|
+
import { adapterBase, IMMUTABLE_CACHE, generateHeadersFile } from './shared.js';
|
|
14
14
|
import {
|
|
15
15
|
runSharedBuildSteps,
|
|
16
16
|
buildStaticDirectoryOutput,
|
|
17
17
|
findUnhashedStaticFiles,
|
|
18
|
+
writeHeadersFile,
|
|
18
19
|
} from './build-output-helper.js';
|
|
19
20
|
|
|
20
21
|
// ─── Presets ─────────────────────────────────────────────────────────────────
|
|
@@ -229,15 +230,14 @@ export function nitro(options: NitroAdapterOptions = {}): TimberPlatformAdapter
|
|
|
229
230
|
|
|
230
231
|
// Write _headers for cache control + extensionless Content-Type.
|
|
231
232
|
// Netlify supports the _headers convention just like Cloudflare.
|
|
232
|
-
|
|
233
|
-
await writeFile(
|
|
234
|
-
join(publicDir, '_headers'),
|
|
235
|
-
generateHeadersFile(unhashedPaths, config.staticContentTypes)
|
|
236
|
-
);
|
|
233
|
+
await writeHeadersFile(publicDir, config, await findUnhashedStaticFiles(publicDir));
|
|
237
234
|
|
|
238
235
|
// Platform-specific files for static deploy targets.
|
|
239
236
|
const target = options.staticTarget ?? 'generic';
|
|
240
237
|
if (target === 'github-pages') {
|
|
238
|
+
// Deploy-from-a-branch runs Jekyll, which drops `_`-prefixed paths —
|
|
239
|
+
// every `_rsc/` payload. Written unconditionally: GitHub reads only
|
|
240
|
+
// that the file exists, never its contents.
|
|
241
241
|
await writeFile(join(publicDir, '.nojekyll'), '');
|
|
242
242
|
// GitHub Pages doesn't support _redirects files. Warn if any
|
|
243
243
|
// redirects were generated — those routes will 404 on GH Pages.
|
package/src/adapters/shared.ts
CHANGED
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
// IMPORTANT: Do NOT add imports from ../server/, ../client/, ../plugins/,
|
|
9
9
|
// or any module that transitively depends on Vite. A test in
|
|
10
10
|
// tests/adapters/shared-no-forbidden-imports.test.ts enforces this.
|
|
11
|
+
// ../shared/ is exempt: it is dependency-free by the same rule.
|
|
12
|
+
|
|
13
|
+
import { withBasePath } from '../shared/base-path.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The deployment base an adapter should write platform rules against.
|
|
17
|
+
*
|
|
18
|
+
* `config.base` is optional on `TimberConfig` because adapters can be invoked
|
|
19
|
+
* by callers that predate it (a hand-written adapter, a test harness). Falling
|
|
20
|
+
* back to `'/'` reproduces the pre-TIM-1274 output exactly, which is correct
|
|
21
|
+
* for the only deployments those callers can have: a build under a non-root
|
|
22
|
+
* base always goes through `plugins/adapter-build.ts`, which populates it.
|
|
23
|
+
*
|
|
24
|
+
* Typed structurally rather than as `TimberConfig` so this module keeps no
|
|
25
|
+
* import of `./types.js` at runtime.
|
|
26
|
+
*/
|
|
27
|
+
export function adapterBase(config: { base?: string }): string {
|
|
28
|
+
return config.base ?? '/';
|
|
29
|
+
}
|
|
11
30
|
|
|
12
31
|
// ─── Static asset cache headers ──────────────────────────────────────────
|
|
13
32
|
|
|
@@ -33,27 +52,165 @@ export const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
|
|
|
33
52
|
* A `/*` catch-all is NOT used — it would be additive on Cloudflare,
|
|
34
53
|
* producing duplicate Cache-Control directives on hashed assets
|
|
35
54
|
* (invalid per RFC 9111).
|
|
55
|
+
*
|
|
56
|
+
* **Every rule path is written in deployed-URL space** (TIM-1274). The
|
|
57
|
+
* platform matches these patterns against the request path, which under a
|
|
58
|
+
* non-root `base` sits below the prefix: with `base: '/repo/'` the assets are
|
|
59
|
+
* at `/repo/assets/*`, so a rule saying `/assets/*` matches nothing. Silent
|
|
60
|
+
* for the cache rules (slower, still correct) and not silent for the
|
|
61
|
+
* Content-Type rules — an extensionless API route output is unreadable on
|
|
62
|
+
* Cloudflare Workers Static Assets without one.
|
|
63
|
+
*
|
|
64
|
+
* The prefix is applied at exactly one point, `rulePath()`, which every rule
|
|
65
|
+
* in this function goes through. Prefixing the three wildcards and
|
|
66
|
+
* hand-checking that the per-file rules agree is how the next rule gets
|
|
67
|
+
* missed.
|
|
36
68
|
*/
|
|
37
69
|
// Cloudflare Workers Static Assets caps _headers at 100 rules.
|
|
38
70
|
// Reserve 3 for the wildcard hashed-asset rules (/assets/*, /_timber/fonts/*, /_rsc/*).
|
|
39
71
|
const MAX_PER_FILE_RULES = 97;
|
|
40
72
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
export interface HeadersFileOptions {
|
|
74
|
+
/**
|
|
75
|
+
* The deployment base, already normalized by `normalizeBasePath()` —
|
|
76
|
+
* leading and trailing slash, `'/'` at the domain root.
|
|
77
|
+
*
|
|
78
|
+
* Required rather than defaulted: a caller that forgets it would emit
|
|
79
|
+
* root-relative rules that match nothing, which is the bug this closes.
|
|
80
|
+
*/
|
|
81
|
+
base: string;
|
|
82
|
+
/** Unhashed static files (route-space paths) needing per-file cache rules. */
|
|
83
|
+
unhashedPaths?: string[];
|
|
84
|
+
/** Route-space path → Content-Type, for extensionless API route outputs. */
|
|
85
|
+
contentTypes?: Record<string, string>;
|
|
86
|
+
/**
|
|
87
|
+
* Verbatim contents of the user's `public/_headers`, appended after the
|
|
88
|
+
* generated rules. See `mergeUserHeaders` for why it goes last.
|
|
89
|
+
*/
|
|
90
|
+
userRules?: string;
|
|
91
|
+
}
|
|
47
92
|
|
|
48
|
-
|
|
49
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Count the rules in a `_headers` file — a rule is a path line, which is any
|
|
95
|
+
* line that is neither blank, a comment, nor an indented header.
|
|
96
|
+
*
|
|
97
|
+
* @internal Exported for testing.
|
|
98
|
+
*/
|
|
99
|
+
export function countHeaderRules(content: string): number {
|
|
100
|
+
return content
|
|
101
|
+
.split('\n')
|
|
102
|
+
.filter((line) => line.trim() !== '' && !line.startsWith('#') && !/^\s/.test(line)).length;
|
|
103
|
+
}
|
|
50
104
|
|
|
51
|
-
|
|
52
|
-
|
|
105
|
+
/** One `_headers` block: a path pattern and the headers it sets. */
|
|
106
|
+
export interface HeaderRule {
|
|
107
|
+
pattern: string;
|
|
108
|
+
headers: Array<{ name: string; value: string }>;
|
|
109
|
+
}
|
|
53
110
|
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Parse a `_headers` file. A non-indented, non-comment line opens a rule; the
|
|
113
|
+
* indented `Name: value` lines beneath it belong to that rule.
|
|
114
|
+
*
|
|
115
|
+
* @internal Exported for testing.
|
|
116
|
+
*/
|
|
117
|
+
export function parseHeaderRules(content: string): HeaderRule[] {
|
|
118
|
+
const rules: HeaderRule[] = [];
|
|
119
|
+
for (const raw of content.split('\n')) {
|
|
120
|
+
const line = raw.replace(/\r$/, '');
|
|
121
|
+
if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
|
|
122
|
+
if (/^\s/.test(line)) {
|
|
123
|
+
const current = rules[rules.length - 1];
|
|
124
|
+
if (!current) continue; // header with no rule above it — not ours to fix
|
|
125
|
+
const colon = line.indexOf(':');
|
|
126
|
+
if (colon === -1) continue;
|
|
127
|
+
current.headers.push({
|
|
128
|
+
name: line.slice(0, colon).trim(),
|
|
129
|
+
value: line.slice(colon + 1).trim(),
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
rules.push({ pattern: line.trim(), headers: [] });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return rules;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Whether a user pattern can match paths a generated pattern also matches.
|
|
140
|
+
*
|
|
141
|
+
* Deliberately narrow: equality, or a prefix-glob (`/x/*`, `/*`) whose literal
|
|
142
|
+
* prefix contains the other's. Anything cleverer risks claiming an overlap
|
|
143
|
+
* that is not there, and the only thing this drives is a warning — a false
|
|
144
|
+
* positive is noise a developer cannot act on.
|
|
145
|
+
*/
|
|
146
|
+
function patternsOverlap(a: string, b: string): boolean {
|
|
147
|
+
if (a === b) return true;
|
|
148
|
+
const prefixOf = (p: string) => (p.endsWith('*') ? p.slice(0, -1) : null);
|
|
149
|
+
const [pa, pb] = [prefixOf(a), prefixOf(b)];
|
|
150
|
+
if (pa !== null && (b.startsWith(pa) || pa === '')) return true;
|
|
151
|
+
if (pb !== null && (a.startsWith(pb) || pb === '')) return true;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function generateHeadersFile({
|
|
156
|
+
base,
|
|
157
|
+
unhashedPaths,
|
|
158
|
+
contentTypes,
|
|
159
|
+
userRules,
|
|
160
|
+
}: HeadersFileOptions): string {
|
|
161
|
+
/** Route-space rule path → deployed-URL rule path. */
|
|
162
|
+
const rulePath = (path: string) => withBasePath(base, path);
|
|
163
|
+
|
|
164
|
+
// Cloudflare and Netlify apply *every* matching rule, so emitting a header
|
|
165
|
+
// the user also sets on the same path yields two definitions of it — a
|
|
166
|
+
// comma-joined `Cache-Control` that is invalid per RFC 9111, and neither
|
|
167
|
+
// side's intent. This module already refuses to emit a `/*` catch-all for
|
|
168
|
+
// that reason; appending the user's file verbatim reintroduced it through
|
|
169
|
+
// the back door (TIM-1326).
|
|
170
|
+
//
|
|
171
|
+
// An explicit app rule beats a framework default, so where the user names
|
|
172
|
+
// the same pattern and the same header, ours is dropped. Only exact pattern
|
|
173
|
+
// matches are resolved this way: silently dropping asset immutability
|
|
174
|
+
// because a `/*` rule *might* subsume it would be a worse surprise than the
|
|
175
|
+
// duplicate, so overlapping-but-different patterns warn instead.
|
|
176
|
+
const userRuleList = userRules ? parseHeaderRules(userRules) : [];
|
|
177
|
+
const overriddenExactly = new Set(
|
|
178
|
+
userRuleList.flatMap((rule) =>
|
|
179
|
+
rule.headers.map((h) => `${rule.pattern}\0${h.name.toLowerCase()}`)
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
const overridden = (pattern: string, header: string) =>
|
|
183
|
+
overriddenExactly.has(`${pattern}\0${header.toLowerCase()}`);
|
|
184
|
+
|
|
185
|
+
const warnedOverlaps = new Set<string>();
|
|
186
|
+
const emit = (pattern: string, header: string, value: string): string => {
|
|
187
|
+
if (overridden(pattern, header)) return '';
|
|
188
|
+
for (const rule of userRuleList) {
|
|
189
|
+
if (rule.pattern === pattern) continue;
|
|
190
|
+
if (!patternsOverlap(rule.pattern, pattern)) continue;
|
|
191
|
+
for (const h of rule.headers) {
|
|
192
|
+
if (h.name.toLowerCase() !== header.toLowerCase()) continue;
|
|
193
|
+
const key = `${rule.pattern}\0${pattern}\0${header.toLowerCase()}`;
|
|
194
|
+
if (warnedOverlaps.has(key)) continue;
|
|
195
|
+
warnedOverlaps.add(key);
|
|
196
|
+
console.warn(
|
|
197
|
+
`[timber] _headers: your rule "${rule.pattern}" sets ${header}, which ` +
|
|
198
|
+
`timber also sets on "${pattern}". Cloudflare and Netlify apply both, ` +
|
|
199
|
+
`so the response gets two ${header} values. Use the exact pattern ` +
|
|
200
|
+
`"${pattern}" to replace timber's rule, or narrow yours so they do ` +
|
|
201
|
+
`not overlap.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return `\n${pattern}\n ${header}: ${value}\n`;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let content = `# Auto-generated by @timber-js/app — static asset cache headers.
|
|
209
|
+
# See design/25-production-deployments.md §"CDN / Edge Cache"
|
|
56
210
|
`;
|
|
211
|
+
for (const path of ['/assets/*', '/_timber/fonts/*', '/_rsc/*']) {
|
|
212
|
+
content += emit(rulePath(path), 'Cache-Control', IMMUTABLE_CACHE);
|
|
213
|
+
}
|
|
57
214
|
|
|
58
215
|
// Content-Type rules are higher priority than cache headers for
|
|
59
216
|
// extensionless files — without them the file is unreadable.
|
|
@@ -80,18 +237,36 @@ export function generateHeadersFile(
|
|
|
80
237
|
`files will get Cache-Control headers.`
|
|
81
238
|
);
|
|
82
239
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
240
|
+
// Section bodies are built before their comment header so a section the
|
|
241
|
+
// user has overridden entirely does not leave a heading over nothing.
|
|
242
|
+
const body = capped.map((p) => emit(rulePath(p), 'Cache-Control', STATIC_CACHE)).join('');
|
|
243
|
+
if (body !== '') {
|
|
244
|
+
content += `\n# Unhashed static assets — short-lived cache with revalidation\n${body}`;
|
|
88
245
|
}
|
|
89
246
|
}
|
|
90
247
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
248
|
+
const ctBody = ctEntries
|
|
249
|
+
.map(([urlPath, ct]) => emit(rulePath(urlPath), 'Content-Type', ct))
|
|
250
|
+
.join('');
|
|
251
|
+
if (ctBody !== '') {
|
|
252
|
+
content += `\n# Content-Type for extensionless API routes\n${ctBody}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// User rules last, matching the `_redirects` merge. Their paths are left
|
|
256
|
+
// verbatim: `_redirects` prefixes the rules it generates because it wrote
|
|
257
|
+
// them in route space, but these are the user's own text, already authored
|
|
258
|
+
// against the URLs the site is deployed at.
|
|
259
|
+
if (userRules && userRules.trim() !== '') {
|
|
260
|
+
content += `\n# User-authored rules from public/_headers\n\n${userRules.trim()}\n`;
|
|
261
|
+
|
|
262
|
+
const total = countHeaderRules(content);
|
|
263
|
+
if (total > MAX_PER_FILE_RULES + 3) {
|
|
264
|
+
console.warn(
|
|
265
|
+
`[timber] _headers: ${total} rules after merging public/_headers, over ` +
|
|
266
|
+
`Cloudflare's ${MAX_PER_FILE_RULES + 3}-rule limit. Rules past the ` +
|
|
267
|
+
`limit are dropped by the platform — remove some, or move static ` +
|
|
268
|
+
`assets under a directory the wildcard rules already cover.`
|
|
269
|
+
);
|
|
95
270
|
}
|
|
96
271
|
}
|
|
97
272
|
|
package/src/adapters/types.ts
CHANGED
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
export interface TimberConfig {
|
|
12
12
|
/** Absolute path to the project root (Vite root). */
|
|
13
13
|
root?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The resolved deployment base, normalized by `normalizeBasePath()` —
|
|
16
|
+
* leading and trailing slash, `'/'` at the domain root.
|
|
17
|
+
*
|
|
18
|
+
* Adapters write platform files (`_headers`, `_redirects`) whose rules the
|
|
19
|
+
* host matches against the *request* path, so those rules live in deployed-
|
|
20
|
+
* URL space rather than route space and need this prefix (TIM-1274).
|
|
21
|
+
*
|
|
22
|
+
* Populated by `plugins/adapter-build.ts` from `ctx.base`, the single copy
|
|
23
|
+
* lifted in TIM-1261. Adapters must not re-derive it from `config.base`.
|
|
24
|
+
*/
|
|
25
|
+
base?: string;
|
|
14
26
|
output: 'server' | 'static';
|
|
15
27
|
clientJavascriptDisabled?: boolean;
|
|
16
28
|
/**
|
|
@@ -23,7 +35,7 @@ export interface TimberConfig {
|
|
|
23
35
|
* Absolute path to the directory containing pre-rendered static HTML,
|
|
24
36
|
* RSC flight files, and API route outputs. Present only when
|
|
25
37
|
* output: 'static' and the static generation pass completed.
|
|
26
|
-
* Layout: path/index.html, _rsc/path.rsc, api-route-path
|
|
38
|
+
* Layout: path/index.html, _rsc/path/index.rsc, api-route-path
|
|
27
39
|
*/
|
|
28
40
|
staticPagesDir?: string;
|
|
29
41
|
/**
|
package/src/cache/fast-hash.ts
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* Uses BigInt for 64-bit arithmetic — supported in all modern runtimes
|
|
10
10
|
* including Cloudflare Workers. No node:crypto dependency.
|
|
11
11
|
*
|
|
12
|
+
* Server-side only. The `_rsc` payload cache key deliberately does NOT use
|
|
13
|
+
* this — the origin compares that value, which makes it a check rather than
|
|
14
|
+
* a cache key, and FNV is solvable for a chosen digest. See
|
|
15
|
+
* `shared/rsc-cache-key.ts`.
|
|
16
|
+
*
|
|
12
17
|
* See TIM-370.
|
|
13
18
|
*/
|
|
14
19
|
|