@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,688 +0,0 @@
|
|
|
1
|
-
import { o as __exportAll } from "./dist-BA3u1z3W.js";
|
|
2
|
-
import { a as classifyMetadataRoute, c as isDynamicMetadataExtension, n as NULL_BYTE_RE, t as ENCODED_SEPARATOR_RE } from "./canonicalize-DQHyFClh.js";
|
|
3
|
-
import { r as DEFAULT_PAGE_EXTENSIONS, t as classifySegment } from "./segment-classify-Byy425ng.js";
|
|
4
|
-
import { h as swallow } from "./logger-AWfuX-KJ.js";
|
|
5
|
-
import { a as resolveAppDir } from "./plugin-context---kTF5v8.js";
|
|
6
|
-
import { basename, extname, join } from "node:path";
|
|
7
|
-
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
8
|
-
//#region src/routing/scanner.ts
|
|
9
|
-
/**
|
|
10
|
-
* Route discovery scanner.
|
|
11
|
-
*
|
|
12
|
-
* Pure function: (appDir, config) → RouteTree
|
|
13
|
-
*
|
|
14
|
-
* Scans the app/ directory and builds a segment tree recognizing all
|
|
15
|
-
* timber.js file conventions. Does NOT handle request matching — this
|
|
16
|
-
* is discovery only.
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* File convention names that use pageExtensions (can be .tsx, .ts, .jsx, .js, .mdx, etc.)
|
|
20
|
-
*/
|
|
21
|
-
var PAGE_EXT_CONVENTIONS = /* @__PURE__ */ new Set([
|
|
22
|
-
"page",
|
|
23
|
-
"layout",
|
|
24
|
-
"error",
|
|
25
|
-
"default",
|
|
26
|
-
"denied"
|
|
27
|
-
]);
|
|
28
|
-
/**
|
|
29
|
-
* Legacy compat status-code files.
|
|
30
|
-
* Maps legacy file name → HTTP status code for the fallback chain.
|
|
31
|
-
* See design/10-error-handling.md §"Fallback Chain".
|
|
32
|
-
*/
|
|
33
|
-
var LEGACY_STATUS_FILES = {
|
|
34
|
-
"not-found": 404,
|
|
35
|
-
"forbidden": 403,
|
|
36
|
-
"unauthorized": 401
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* File convention names that are always .ts/.tsx (never .mdx etc.)
|
|
40
|
-
*/
|
|
41
|
-
var FIXED_CONVENTIONS = /* @__PURE__ */ new Set([
|
|
42
|
-
"middleware",
|
|
43
|
-
"access",
|
|
44
|
-
"route",
|
|
45
|
-
"params"
|
|
46
|
-
]);
|
|
47
|
-
/**
|
|
48
|
-
* Status-code file patterns:
|
|
49
|
-
* - Exact 3-digit codes: 401.tsx, 429.tsx, 503.tsx
|
|
50
|
-
* - Category catch-alls: 4xx.tsx, 5xx.tsx
|
|
51
|
-
*/
|
|
52
|
-
var STATUS_CODE_PATTERN = /^(\d{3}|[45]xx)$/;
|
|
53
|
-
/**
|
|
54
|
-
* Scan the app/ directory and build the route tree.
|
|
55
|
-
*
|
|
56
|
-
* @param appDir - Absolute path to the app/ directory
|
|
57
|
-
* @param config - Scanner configuration
|
|
58
|
-
* @returns The complete route tree
|
|
59
|
-
*/
|
|
60
|
-
function scanRoutes(appDir, config = {}) {
|
|
61
|
-
const pageExtensions = config.pageExtensions ?? DEFAULT_PAGE_EXTENSIONS;
|
|
62
|
-
const extSet = new Set(pageExtensions);
|
|
63
|
-
const tree = { root: createSegmentNode("", "static", "/") };
|
|
64
|
-
const proxyFile = findFixedFile(appDir, "proxy");
|
|
65
|
-
if (proxyFile) tree.proxy = proxyFile;
|
|
66
|
-
const globalErrorFile = findPageExtFile(appDir, "global-error", extSet);
|
|
67
|
-
if (globalErrorFile) tree.globalError = globalErrorFile;
|
|
68
|
-
scanSegmentFiles(appDir, tree.root, extSet);
|
|
69
|
-
scanChildren(appDir, tree.root, extSet);
|
|
70
|
-
validateRouteGroupCollisions(tree.root);
|
|
71
|
-
validateDuplicateParamNames(tree.root);
|
|
72
|
-
validateUniqueBracketForms(tree.root);
|
|
73
|
-
validateAffixedSiblingConflicts(tree.root);
|
|
74
|
-
return tree;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Create an empty segment node.
|
|
78
|
-
*/
|
|
79
|
-
function createSegmentNode(segmentName, segmentType, urlPath, paramName, interceptionMarker, interceptedSegmentName, paramPrefix, paramSuffix) {
|
|
80
|
-
return {
|
|
81
|
-
segmentName,
|
|
82
|
-
segmentType,
|
|
83
|
-
urlPath,
|
|
84
|
-
paramName,
|
|
85
|
-
paramPrefix,
|
|
86
|
-
paramSuffix,
|
|
87
|
-
interceptionMarker,
|
|
88
|
-
interceptedSegmentName,
|
|
89
|
-
children: [],
|
|
90
|
-
slots: {}
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Compute the URL path for a child segment given its parent's URL path.
|
|
95
|
-
* Route groups, slots, and intercepting routes do NOT add URL depth.
|
|
96
|
-
*/
|
|
97
|
-
function computeUrlPath(parentUrlPath, dirName, segmentType) {
|
|
98
|
-
if (segmentType === "group" || segmentType === "slot" || segmentType === "intercepting") return parentUrlPath;
|
|
99
|
-
return `${parentUrlPath === "/" ? "" : parentUrlPath}/${dirName}`;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Scan a directory for file conventions and populate the segment node.
|
|
103
|
-
*/
|
|
104
|
-
function scanSegmentFiles(dirPath, node, extSet) {
|
|
105
|
-
let entries;
|
|
106
|
-
try {
|
|
107
|
-
entries = readdirSync(dirPath);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
swallow(err, `scanSegmentFiles: unreadable directory ${dirPath}`, { level: "warn" });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
for (const entry of entries) {
|
|
113
|
-
const fullPath = join(dirPath, entry);
|
|
114
|
-
try {
|
|
115
|
-
if (statSync(fullPath).isDirectory()) continue;
|
|
116
|
-
} catch {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const ext = extname(entry).slice(1);
|
|
120
|
-
const name = basename(entry, `.${ext}`);
|
|
121
|
-
if (PAGE_EXT_CONVENTIONS.has(name) && extSet.has(ext)) {
|
|
122
|
-
const key = name;
|
|
123
|
-
const existing = node[key];
|
|
124
|
-
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
125
|
-
node[key] = {
|
|
126
|
-
filePath: fullPath,
|
|
127
|
-
extension: ext
|
|
128
|
-
};
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (FIXED_CONVENTIONS.has(name) && /^[jt]sx?$/.test(ext)) {
|
|
132
|
-
const key = name;
|
|
133
|
-
const existing = node[key];
|
|
134
|
-
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
135
|
-
node[key] = {
|
|
136
|
-
filePath: fullPath,
|
|
137
|
-
extension: ext
|
|
138
|
-
};
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (STATUS_CODE_PATTERN.test(name) && ext === "json") {
|
|
142
|
-
if (!node.jsonStatusFiles) node.jsonStatusFiles = {};
|
|
143
|
-
node.jsonStatusFiles[name] = {
|
|
144
|
-
filePath: fullPath,
|
|
145
|
-
extension: ext
|
|
146
|
-
};
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (STATUS_CODE_PATTERN.test(name) && extSet.has(ext)) {
|
|
150
|
-
if (!node.statusFiles) node.statusFiles = {};
|
|
151
|
-
const existing = node.statusFiles[name];
|
|
152
|
-
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
153
|
-
node.statusFiles[name] = {
|
|
154
|
-
filePath: fullPath,
|
|
155
|
-
extension: ext
|
|
156
|
-
};
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (name in LEGACY_STATUS_FILES && extSet.has(ext)) {
|
|
160
|
-
if (!node.legacyStatusFiles) node.legacyStatusFiles = {};
|
|
161
|
-
const existing = node.legacyStatusFiles[name];
|
|
162
|
-
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
163
|
-
node.legacyStatusFiles[name] = {
|
|
164
|
-
filePath: fullPath,
|
|
165
|
-
extension: ext
|
|
166
|
-
};
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const metaInfo = classifyMetadataRoute(entry);
|
|
170
|
-
if (metaInfo) {
|
|
171
|
-
if (!metaInfo.nestable && node.segmentName !== "") throw new Error(`Build error: '${name}' is a root-only metadata convention and must be in the app root directory.\n File: ${fullPath}\n Move this file to the app root (not a route group or nested segment).`);
|
|
172
|
-
if (!node.metadataRoutes) node.metadataRoutes = {};
|
|
173
|
-
const existing = node.metadataRoutes[name];
|
|
174
|
-
if (existing) {
|
|
175
|
-
const existingIsDynamic = isDynamicMetadataExtension(name, existing.extension);
|
|
176
|
-
const newIsDynamic = isDynamicMetadataExtension(name, ext);
|
|
177
|
-
if (existingIsDynamic === newIsDynamic) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
178
|
-
if (newIsDynamic) node.metadataRoutes[name] = {
|
|
179
|
-
filePath: fullPath,
|
|
180
|
-
extension: ext
|
|
181
|
-
};
|
|
182
|
-
} else node.metadataRoutes[name] = {
|
|
183
|
-
filePath: fullPath,
|
|
184
|
-
extension: ext
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (node.route && node.page) throw new Error(`Build error: route.ts and page.* cannot coexist in the same segment.\n route.ts: ${node.route.filePath}\n page: ${node.page.filePath}\nA URL is either an API endpoint or a rendered page, not both.`);
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Throw a build error for two files claiming the same convention slot in
|
|
192
|
-
* one segment (e.g. page.tsx + page.jsx). Which file readdir returns first
|
|
193
|
-
* is platform-dependent (APFS sorts entries, ext4 does not) — silently
|
|
194
|
-
* picking one would let macOS dev and Linux CI serve different
|
|
195
|
-
* implementations of the same route.
|
|
196
|
-
*/
|
|
197
|
-
function throwDuplicateConvention(convention, firstPath, secondPath) {
|
|
198
|
-
throw new Error(`Build error: duplicate '${convention}' files in the same route segment.\n File 1: ${firstPath}\n File 2: ${secondPath}\nEach segment may define '${convention}' at most once. Remove one of the files.`);
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Recursively scan child directories and build the segment tree.
|
|
202
|
-
*/
|
|
203
|
-
function scanChildren(dirPath, parentNode, extSet) {
|
|
204
|
-
let entries;
|
|
205
|
-
try {
|
|
206
|
-
entries = readdirSync(dirPath);
|
|
207
|
-
} catch (err) {
|
|
208
|
-
swallow(err, `scanChildren: unreadable directory ${dirPath}`, { level: "warn" });
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
for (const entry of entries) {
|
|
212
|
-
const fullPath = join(dirPath, entry);
|
|
213
|
-
try {
|
|
214
|
-
if (!statSync(fullPath).isDirectory()) continue;
|
|
215
|
-
} catch {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (ENCODED_SEPARATOR_RE.test(entry)) throw new Error(`Build error: directory name contains an encoded path delimiter (%2F or %5C).\n Directory: ${fullPath}\nEncoded separators in directory names cause route collisions when decoded. Rename the directory to remove the encoded delimiter.`);
|
|
219
|
-
if (NULL_BYTE_RE.test(entry)) throw new Error(`Build error: directory name contains an encoded null byte (%00).\n Directory: ${fullPath}\nEncoded null bytes in directory names are not allowed. Rename the directory to remove the null byte encoding.`);
|
|
220
|
-
const { type, paramName, paramPrefix, paramSuffix, interceptionMarker, interceptedSegmentName } = classifySegment(entry);
|
|
221
|
-
if (type === "private") continue;
|
|
222
|
-
const childNode = createSegmentNode(entry, type, computeUrlPath(parentNode.urlPath, entry, type), paramName, interceptionMarker, interceptedSegmentName, paramPrefix, paramSuffix);
|
|
223
|
-
scanSegmentFiles(fullPath, childNode, extSet);
|
|
224
|
-
scanChildren(fullPath, childNode, extSet);
|
|
225
|
-
if (type === "slot") {
|
|
226
|
-
const slotName = entry.slice(1);
|
|
227
|
-
parentNode.slots[slotName] = childNode;
|
|
228
|
-
} else parentNode.children.push(childNode);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Validate that route groups don't produce conflicting pages/routes at the same URL path.
|
|
233
|
-
*
|
|
234
|
-
* Two route groups like (auth)/login/page.tsx and (marketing)/login/page.tsx both claim
|
|
235
|
-
* /login — the scanner must detect and reject this at build time.
|
|
236
|
-
*
|
|
237
|
-
* Each parallel slot is validated as its OWN collision space: a slot's pages
|
|
238
|
-
* intentionally share URL paths with the parent tree (that's the point of
|
|
239
|
-
* parallel routes), but two route groups INSIDE one slot claiming the same
|
|
240
|
-
* URL — e.g. @side/(a)/x/page.tsx + @side/(b)/x/page.tsx — still collide.
|
|
241
|
-
*/
|
|
242
|
-
function validateRouteGroupCollisions(root) {
|
|
243
|
-
collectRoutableLeaves(root, /* @__PURE__ */ new Map(), "");
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Walk the segment tree and collect all routable leaves (page or route files),
|
|
247
|
-
* throwing on collision. Each slot recurses with a FRESH collision map —
|
|
248
|
-
* slots are parallel routes that intentionally share URL paths with their
|
|
249
|
-
* parent tree, but collisions within a single slot are real errors.
|
|
250
|
-
*/
|
|
251
|
-
function collectRoutableLeaves(node, seen, segmentPath) {
|
|
252
|
-
if (node.segmentType === "intercepting") return;
|
|
253
|
-
const currentPath = segmentPath ? `${segmentPath}/${node.segmentName}` : node.segmentName || "(root)";
|
|
254
|
-
const routableFile = node.page ?? node.route;
|
|
255
|
-
if (routableFile) {
|
|
256
|
-
const existing = seen.get(node.urlPath);
|
|
257
|
-
if (existing) throw new Error(`Build error: route collision — multiple route groups produce a page/route at the same URL path.\n URL path: ${node.urlPath}\n File 1: ${existing.filePath} (via ${existing.segmentPath})\n File 2: ${routableFile.filePath} (via ${currentPath})\nEach URL path must map to exactly one page or route handler. Rename or move one of the conflicting files.`);
|
|
258
|
-
seen.set(node.urlPath, {
|
|
259
|
-
filePath: routableFile.filePath,
|
|
260
|
-
segmentPath: currentPath
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
for (const child of node.children) collectRoutableLeaves(child, seen, currentPath);
|
|
264
|
-
for (const slotNode of Object.values(node.slots)) collectRoutableLeaves(slotNode, /* @__PURE__ */ new Map(), currentPath);
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Validate that no route chain contains duplicate dynamic param names.
|
|
268
|
-
*
|
|
269
|
-
* Example violation:
|
|
270
|
-
* app/[id]/items/[id]/page.tsx — 'id' appears twice in the ancestor chain.
|
|
271
|
-
*
|
|
272
|
-
* Route groups are transparent — params accumulate through them.
|
|
273
|
-
* Slots are independent — duplicate detection does NOT cross slot boundaries.
|
|
274
|
-
*
|
|
275
|
-
* See design/07-routing.md §"Duplicate Param Name Detection"
|
|
276
|
-
*/
|
|
277
|
-
function validateDuplicateParamNames(root) {
|
|
278
|
-
walkForDuplicateParams(root, /* @__PURE__ */ new Map());
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Recursively walk the segment tree, tracking seen param names → segment paths.
|
|
282
|
-
* Throws on the first duplicate found.
|
|
283
|
-
*/
|
|
284
|
-
function walkForDuplicateParams(node, seen) {
|
|
285
|
-
if (node.paramName) {
|
|
286
|
-
const existing = seen.get(node.paramName);
|
|
287
|
-
if (existing) throw new Error(`[timber] Duplicate param name '${node.paramName}' in route chain.\n First defined at: ${existing}\n Duplicate at: ${node.urlPath || "/"}\n Rename one of the segments to avoid ambiguity.`);
|
|
288
|
-
seen = new Map(seen);
|
|
289
|
-
seen.set(node.paramName, node.urlPath || "/");
|
|
290
|
-
}
|
|
291
|
-
for (const child of node.children) walkForDuplicateParams(child, seen);
|
|
292
|
-
for (const slotNode of Object.values(node.slots)) walkForDuplicateParams(slotNode, new Map(seen));
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Validate that each param name uses a single bracket form across the entire route tree.
|
|
296
|
-
*
|
|
297
|
-
* Different bracket forms produce different types:
|
|
298
|
-
* [id] → string
|
|
299
|
-
* [...id] → string[]
|
|
300
|
-
* [[...id]] → string[] | undefined
|
|
301
|
-
*
|
|
302
|
-
* If the same param name appears with multiple forms, the no-argument
|
|
303
|
-
* getSegmentParams() / useSegmentParams() return type becomes ambiguous.
|
|
304
|
-
*
|
|
305
|
-
* Same name + same form across different routes is fine and expected.
|
|
306
|
-
*/
|
|
307
|
-
function validateUniqueBracketForms(root) {
|
|
308
|
-
collectBracketForms(root, /* @__PURE__ */ new Map());
|
|
309
|
-
}
|
|
310
|
-
function collectBracketForms(node, forms) {
|
|
311
|
-
if (node.paramName && node.segmentType !== "static" && node.segmentType !== "group") {
|
|
312
|
-
const existing = forms.get(node.paramName);
|
|
313
|
-
if (existing && existing.segmentType !== node.segmentType) {
|
|
314
|
-
const bracketExamples = {
|
|
315
|
-
"dynamic": `[${node.paramName}]`,
|
|
316
|
-
"catch-all": `[...${node.paramName}]`,
|
|
317
|
-
"optional-catch-all": `[[...${node.paramName}]]`
|
|
318
|
-
};
|
|
319
|
-
throw new Error(`[timber] Param name '${node.paramName}' uses multiple bracket forms.\n ${bracketExamples[existing.segmentType] ?? existing.segmentType} at: ${existing.urlPath}\n ${bracketExamples[node.segmentType] ?? node.segmentType} at: ${node.urlPath || "/"}\n Each param name must use a single bracket form across all routes.\n Rename one of the segments to fix this (e.g., [${node.paramName}] → [${node.paramName}Id]).`);
|
|
320
|
-
}
|
|
321
|
-
if (!existing) forms.set(node.paramName, {
|
|
322
|
-
segmentType: node.segmentType,
|
|
323
|
-
urlPath: node.urlPath || "/"
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
for (const child of node.children) collectBracketForms(child, forms);
|
|
327
|
-
for (const slotNode of Object.values(node.slots)) collectBracketForms(slotNode, forms);
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Validate that no sibling affixed dynamic segments have equal specificity.
|
|
331
|
-
*
|
|
332
|
-
* Among affixed siblings, longer combined prefix+suffix length wins. When two
|
|
333
|
-
* siblings tie (same total affix length), the router would fall back to
|
|
334
|
-
* filesystem scan order — which is platform-dependent. This is a build error.
|
|
335
|
-
*
|
|
336
|
-
* Route groups are URL-transparent, so affixed segments inside different groups
|
|
337
|
-
* at the same level still conflict with each other.
|
|
338
|
-
*
|
|
339
|
-
* See design/07-routing.md §"Priority"
|
|
340
|
-
*/
|
|
341
|
-
function validateAffixedSiblingConflicts(node) {
|
|
342
|
-
const affixed = collectEffectiveAffixed(node);
|
|
343
|
-
if (affixed.length >= 2) {
|
|
344
|
-
const byLength = /* @__PURE__ */ new Map();
|
|
345
|
-
for (const child of affixed) {
|
|
346
|
-
const len = (child.paramPrefix?.length ?? 0) + (child.paramSuffix?.length ?? 0);
|
|
347
|
-
const group = byLength.get(len);
|
|
348
|
-
if (group) group.push(child);
|
|
349
|
-
else byLength.set(len, [child]);
|
|
350
|
-
}
|
|
351
|
-
for (const [, group] of byLength) if (group.length >= 2) throw new Error(`Build error: ambiguous affixed dynamic siblings with equal specificity.\n Parent: ${node.urlPath || "/"}\n Segments: ${group.map((s) => s.segmentName).join(", ")}\n Combined prefix+suffix length: ${(group[0].paramPrefix?.length ?? 0) + (group[0].paramSuffix?.length ?? 0)}\nThe router cannot deterministically choose between these — filesystem scan order is platform-dependent.\nRename one segment to give it a longer or shorter combined affix.`);
|
|
352
|
-
}
|
|
353
|
-
for (const child of node.children) validateAffixedSiblingConflicts(child);
|
|
354
|
-
for (const slotNode of Object.values(node.slots)) validateAffixedSiblingConflicts(slotNode);
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Collect affixed dynamic segments that are effective children of a node.
|
|
358
|
-
* Route groups are URL-transparent, so we look through them recursively.
|
|
359
|
-
*/
|
|
360
|
-
function collectEffectiveAffixed(node) {
|
|
361
|
-
const result = [];
|
|
362
|
-
for (const child of node.children) if (child.segmentType === "dynamic" && (child.paramPrefix || child.paramSuffix)) result.push(child);
|
|
363
|
-
else if (child.segmentType === "group") result.push(...collectEffectiveAffixed(child));
|
|
364
|
-
return result;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Find a fixed-extension file (proxy.ts) in a directory.
|
|
368
|
-
*/
|
|
369
|
-
function findFixedFile(dirPath, name) {
|
|
370
|
-
for (const ext of ["ts", "tsx"]) {
|
|
371
|
-
const fullPath = join(dirPath, `${name}.${ext}`);
|
|
372
|
-
try {
|
|
373
|
-
if (statSync(fullPath).isFile()) return {
|
|
374
|
-
filePath: fullPath,
|
|
375
|
-
extension: ext
|
|
376
|
-
};
|
|
377
|
-
} catch {}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Find a file using the configured page extensions (tsx, ts, jsx, js, mdx, etc.).
|
|
382
|
-
* Used for app-root conventions like global-error that aren't per-segment.
|
|
383
|
-
*/
|
|
384
|
-
function findPageExtFile(dirPath, name, extSet) {
|
|
385
|
-
for (const ext of extSet) {
|
|
386
|
-
const fullPath = join(dirPath, `${name}.${ext}`);
|
|
387
|
-
try {
|
|
388
|
-
if (statSync(fullPath).isFile()) return {
|
|
389
|
-
filePath: fullPath,
|
|
390
|
-
extension: ext
|
|
391
|
-
};
|
|
392
|
-
} catch {}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/cli-schema-sync.ts
|
|
397
|
-
/**
|
|
398
|
-
* `timber schema sync` — scan filesystem for dynamic segments, append
|
|
399
|
-
* missing entries with `codec.string` defaults, and remove stale entries
|
|
400
|
-
* whose segments no longer exist in the filesystem.
|
|
401
|
-
*
|
|
402
|
-
* Design doc: design/41-global-params.md §Auto-Scaffolding
|
|
403
|
-
*/
|
|
404
|
-
var cli_schema_sync_exports = /* @__PURE__ */ __exportAll({
|
|
405
|
-
collectDynamicSegments: () => collectDynamicSegments,
|
|
406
|
-
defaultCodecForSegment: () => defaultCodecForSegment,
|
|
407
|
-
findSchemaFile: () => findSchemaFile,
|
|
408
|
-
findSegmentParamsBlock: () => findSegmentParamsBlock,
|
|
409
|
-
parseExistingSchemaKeys: () => parseExistingSchemaKeys,
|
|
410
|
-
runSchemaSync: () => runSchemaSync
|
|
411
|
-
});
|
|
412
|
-
function toBracketKey(node) {
|
|
413
|
-
if (node.paramPrefix || node.paramSuffix) return `[${node.paramName}]`;
|
|
414
|
-
switch (node.segmentType) {
|
|
415
|
-
case "catch-all": return `[...${node.paramName}]`;
|
|
416
|
-
case "optional-catch-all": return `[[...${node.paramName}]]`;
|
|
417
|
-
default: return `[${node.paramName}]`;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Collect all unique bracket-keyed dynamic segment names from the route tree.
|
|
422
|
-
* Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.
|
|
423
|
-
*/
|
|
424
|
-
function collectDynamicSegments(appDir) {
|
|
425
|
-
const tree = scanRoutes(appDir);
|
|
426
|
-
const segments = /* @__PURE__ */ new Set();
|
|
427
|
-
walkSegments(tree.root, segments);
|
|
428
|
-
return [...segments].sort();
|
|
429
|
-
}
|
|
430
|
-
function walkSegments(node, segments) {
|
|
431
|
-
if (node.paramName) segments.add(toBracketKey(node));
|
|
432
|
-
for (const child of node.children) walkSegments(child, segments);
|
|
433
|
-
for (const slot of Object.values(node.slots)) walkSegments(slot, segments);
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Replace comment bodies (and optionally string contents) with spaces,
|
|
437
|
-
* preserving length and newlines so every index maps 1:1 to the original.
|
|
438
|
-
* Lets the structural scanning below ignore `segmentParams` mentions and
|
|
439
|
-
* braces inside comments/strings without a TS parser (fine pre-alpha,
|
|
440
|
-
* see TIM-1066).
|
|
441
|
-
*
|
|
442
|
-
* With `maskStrings`, string contents are blanked too — except literal
|
|
443
|
-
* 'segmentParams' strings, kept so a quoted property key stays findable.
|
|
444
|
-
*/
|
|
445
|
-
function maskSource(content, maskStrings) {
|
|
446
|
-
const out = content.split("");
|
|
447
|
-
const n = content.length;
|
|
448
|
-
let i = 0;
|
|
449
|
-
const blank = (from, to) => {
|
|
450
|
-
for (let j = from; j < to; j++) if (content[j] !== "\n") out[j] = " ";
|
|
451
|
-
};
|
|
452
|
-
while (i < n) {
|
|
453
|
-
const ch = content[i];
|
|
454
|
-
const next = content[i + 1];
|
|
455
|
-
if (ch === "/" && next === "/") {
|
|
456
|
-
const start = i;
|
|
457
|
-
while (i < n && content[i] !== "\n") i++;
|
|
458
|
-
blank(start, i);
|
|
459
|
-
} else if (ch === "/" && next === "*") {
|
|
460
|
-
const start = i;
|
|
461
|
-
i += 2;
|
|
462
|
-
while (i < n && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
463
|
-
i = Math.min(i + 2, n);
|
|
464
|
-
blank(start, i);
|
|
465
|
-
} else if (ch === "\"" || ch === "'" || ch === "`") {
|
|
466
|
-
const start = i;
|
|
467
|
-
i++;
|
|
468
|
-
while (i < n && content[i] !== ch) {
|
|
469
|
-
if (content[i] === "\\") i++;
|
|
470
|
-
i++;
|
|
471
|
-
}
|
|
472
|
-
const end = i;
|
|
473
|
-
if (maskStrings && content.slice(start + 1, end) !== "segmentParams") blank(start + 1, end);
|
|
474
|
-
i++;
|
|
475
|
-
} else i++;
|
|
476
|
-
}
|
|
477
|
-
return out.join("");
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Locate the `segmentParams: { ... }` block in a schema file.
|
|
481
|
-
* Returns the indices of the opening and matching closing brace, or null
|
|
482
|
-
* if no segmentParams property is found. Comments and strings are masked
|
|
483
|
-
* first, so a JSDoc example mentioning `segmentParams: { ... }` above the
|
|
484
|
-
* real defineSchema call is not mistaken for the block, and braces inside
|
|
485
|
-
* comments/strings don't skew the depth count. The search is anchored to
|
|
486
|
-
* the `defineSchema(` call when present, so helper objects with their own
|
|
487
|
-
* segmentParams property earlier in the file are skipped.
|
|
488
|
-
*/
|
|
489
|
-
function findSegmentParamsBlock(content) {
|
|
490
|
-
const masked = maskSource(content, true);
|
|
491
|
-
const defineCall = /\bdefineSchema\s*\(/.exec(masked);
|
|
492
|
-
const keyRe = /(['"]?)segmentParams\1\s*:\s*\{/g;
|
|
493
|
-
keyRe.lastIndex = defineCall ? defineCall.index + defineCall[0].length : 0;
|
|
494
|
-
const keyMatch = keyRe.exec(masked);
|
|
495
|
-
if (!keyMatch) return null;
|
|
496
|
-
const openBrace = keyMatch.index + keyMatch[0].length - 1;
|
|
497
|
-
let depth = 1;
|
|
498
|
-
let i = openBrace + 1;
|
|
499
|
-
while (i < masked.length && depth > 0) {
|
|
500
|
-
if (masked[i] === "{") depth++;
|
|
501
|
-
else if (masked[i] === "}") depth--;
|
|
502
|
-
i++;
|
|
503
|
-
}
|
|
504
|
-
if (depth > 0) return null;
|
|
505
|
-
return {
|
|
506
|
-
openBrace,
|
|
507
|
-
closeBrace: i - 1
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Parse existing bracket keys from an app/schema.ts file.
|
|
512
|
-
* Scoped to the segmentParams: { ... } block — bracket keys elsewhere in
|
|
513
|
-
* the file (unrelated objects, comments above defineSchema) are ignored.
|
|
514
|
-
* Comments inside the block are masked, so a commented-out entry does not
|
|
515
|
-
* count as registered.
|
|
516
|
-
*
|
|
517
|
-
* The key charset matches the route classifier (segment-classify.ts):
|
|
518
|
-
* any character except `]`, so hyphenated params like '[my-param]' parse.
|
|
519
|
-
*/
|
|
520
|
-
function parseExistingSchemaKeys(content) {
|
|
521
|
-
const block = findSegmentParamsBlock(content);
|
|
522
|
-
if (!block) return [];
|
|
523
|
-
const blockContent = maskSource(content, false).slice(block.openBrace, block.closeBrace);
|
|
524
|
-
const keys = [];
|
|
525
|
-
const re = /['"](\[{1,2}(?:\.{3})?[^\]]+\]{1,2})['"]\s*:/g;
|
|
526
|
-
let m;
|
|
527
|
-
while ((m = re.exec(blockContent)) !== null) keys.push(m[1]);
|
|
528
|
-
return keys;
|
|
529
|
-
}
|
|
530
|
-
/** Schema file extensions to check. */
|
|
531
|
-
var SCHEMA_EXTENSIONS = [
|
|
532
|
-
".ts",
|
|
533
|
-
".tsx",
|
|
534
|
-
".js",
|
|
535
|
-
".jsx"
|
|
536
|
-
];
|
|
537
|
-
/**
|
|
538
|
-
* Find the schema file path if it exists.
|
|
539
|
-
*/
|
|
540
|
-
function findSchemaFile(appDir) {
|
|
541
|
-
for (const ext of SCHEMA_EXTENSIONS) {
|
|
542
|
-
const p = join(appDir, `schema${ext}`);
|
|
543
|
-
if (existsSync(p)) return p;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function defaultCodecForSegment(seg) {
|
|
547
|
-
if (seg.startsWith("[[...")) return "codec.optionalCatchAll(codec.string)";
|
|
548
|
-
if (seg.startsWith("[...")) return "codec.stringArray";
|
|
549
|
-
return "codec.string";
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Generate the content for a new app/schema.ts with the given segment keys.
|
|
553
|
-
*/
|
|
554
|
-
function generateNewSchema(segments) {
|
|
555
|
-
const lines = [];
|
|
556
|
-
lines.push("import { defineSchema } from '@timber-js/app/params';");
|
|
557
|
-
lines.push("import { codec } from '@timber-js/app/codec';");
|
|
558
|
-
lines.push("");
|
|
559
|
-
lines.push("export default defineSchema({");
|
|
560
|
-
lines.push(" segmentParams: {");
|
|
561
|
-
for (const seg of segments) lines.push(` '${seg}': ${defaultCodecForSegment(seg)},`);
|
|
562
|
-
lines.push(" },");
|
|
563
|
-
lines.push("});");
|
|
564
|
-
lines.push("");
|
|
565
|
-
return lines.join("\n");
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Append missing segment entries to an existing schema file.
|
|
569
|
-
* Inserts new entries before the closing `}` of segmentParams.
|
|
570
|
-
*/
|
|
571
|
-
var CODEC_IMPORT = "import { codec } from '@timber-js/app/codec';";
|
|
572
|
-
function hasCodecImport(content) {
|
|
573
|
-
return /import\s+\{[^}]*\bcodec\b[^}]*\}\s+from\s+['"]@timber-js\/app\/codec['"]/.test(content);
|
|
574
|
-
}
|
|
575
|
-
function ensureCodecImport(content) {
|
|
576
|
-
if (hasCodecImport(content)) return content;
|
|
577
|
-
const lines = content.split("\n");
|
|
578
|
-
let lastImportIndex = -1;
|
|
579
|
-
for (let i = 0; i < lines.length; i++) if (/^\s*import\s/.test(lines[i])) lastImportIndex = i;
|
|
580
|
-
if (lastImportIndex === -1) return "import { codec } from '@timber-js/app/codec';\n" + content;
|
|
581
|
-
lines.splice(lastImportIndex + 1, 0, CODEC_IMPORT);
|
|
582
|
-
return lines.join("\n");
|
|
583
|
-
}
|
|
584
|
-
function appendToSchema(content, newSegments) {
|
|
585
|
-
content = ensureCodecImport(content);
|
|
586
|
-
const block = findSegmentParamsBlock(content);
|
|
587
|
-
if (!block) throw new Error("[timber] Could not find segmentParams in schema file. Expected a defineSchema({ segmentParams: { ... } }) call.");
|
|
588
|
-
const { openBrace, closeBrace } = block;
|
|
589
|
-
const existingLine = content.slice(openBrace, closeBrace).match(/^(\s+)['"]\[/m);
|
|
590
|
-
const indent = existingLine ? existingLine[1] : " ";
|
|
591
|
-
const insertion = newSegments.map((seg) => `${indent}'${seg}': ${defaultCodecForSegment(seg)},`).join("\n");
|
|
592
|
-
const before = content.slice(0, closeBrace);
|
|
593
|
-
const after = content.slice(closeBrace);
|
|
594
|
-
return before + (before.trimEnd().endsWith(",") ? "\n" : "\n") + insertion + "\n" + after;
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Remove entries from the segmentParams block whose keys match `staleSegments`.
|
|
598
|
-
* Removes the entire entry including trailing comma/whitespace. Scoped to
|
|
599
|
-
* the segmentParams block — identical keys in unrelated objects elsewhere
|
|
600
|
-
* in the file are left untouched.
|
|
601
|
-
*/
|
|
602
|
-
function removeFromSchema(content, staleSegments) {
|
|
603
|
-
const block = findSegmentParamsBlock(content);
|
|
604
|
-
if (!block) return content;
|
|
605
|
-
let blockContent = content.slice(block.openBrace, block.closeBrace);
|
|
606
|
-
let blockMasked = maskSource(blockContent, false);
|
|
607
|
-
for (const seg of staleSegments) {
|
|
608
|
-
const escaped = seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
609
|
-
const keyRe = new RegExp(`^[ \t]*['"]${escaped}['"][ \t]*:`, "gm");
|
|
610
|
-
let m;
|
|
611
|
-
while ((m = keyRe.exec(blockMasked)) !== null) {
|
|
612
|
-
const start = m.index;
|
|
613
|
-
let i = m.index + m[0].length;
|
|
614
|
-
let depth = 0;
|
|
615
|
-
let inString = null;
|
|
616
|
-
while (i < blockMasked.length) {
|
|
617
|
-
const ch = blockMasked[i];
|
|
618
|
-
if (inString) {
|
|
619
|
-
if (ch === "\\") i++;
|
|
620
|
-
else if (ch === inString) inString = null;
|
|
621
|
-
} else if (ch === "\"" || ch === "'" || ch === "`") inString = ch;
|
|
622
|
-
else if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
623
|
-
else if (ch === ")" || ch === "}" || ch === "]") depth--;
|
|
624
|
-
else if (depth === 0 && ch === ",") {
|
|
625
|
-
i++;
|
|
626
|
-
break;
|
|
627
|
-
} else if (depth === 0 && (ch === "\n" || ch === "}")) break;
|
|
628
|
-
i++;
|
|
629
|
-
}
|
|
630
|
-
if (i < blockMasked.length && blockMasked[i] === "\n") i++;
|
|
631
|
-
blockContent = blockContent.slice(0, start) + blockContent.slice(i);
|
|
632
|
-
blockMasked = blockMasked.slice(0, start) + blockMasked.slice(i);
|
|
633
|
-
keyRe.lastIndex = start;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return content.slice(0, block.openBrace) + blockContent + content.slice(block.closeBrace);
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Run the schema sync: scan filesystem, diff against schema, append missing.
|
|
640
|
-
*/
|
|
641
|
-
function runSchemaSync(projectRoot) {
|
|
642
|
-
const appDir = resolveAppDir(projectRoot);
|
|
643
|
-
const filesystemSegments = collectDynamicSegments(appDir);
|
|
644
|
-
const schemaPath = findSchemaFile(appDir);
|
|
645
|
-
if (filesystemSegments.length === 0 && !schemaPath) return {
|
|
646
|
-
filesystemSegments: [],
|
|
647
|
-
existingSegments: [],
|
|
648
|
-
addedSegments: [],
|
|
649
|
-
removedSegments: [],
|
|
650
|
-
created: false
|
|
651
|
-
};
|
|
652
|
-
if (!schemaPath) {
|
|
653
|
-
writeFileSync(join(appDir, "schema.ts"), generateNewSchema(filesystemSegments), "utf-8");
|
|
654
|
-
return {
|
|
655
|
-
filesystemSegments,
|
|
656
|
-
existingSegments: [],
|
|
657
|
-
addedSegments: filesystemSegments,
|
|
658
|
-
removedSegments: [],
|
|
659
|
-
created: true
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
const content = readFileSync(schemaPath, "utf-8");
|
|
663
|
-
const existingSegments = parseExistingSchemaKeys(content);
|
|
664
|
-
const missing = filesystemSegments.filter((seg) => !existingSegments.includes(seg));
|
|
665
|
-
const stale = existingSegments.filter((seg) => !filesystemSegments.includes(seg));
|
|
666
|
-
if (missing.length === 0 && stale.length === 0) return {
|
|
667
|
-
filesystemSegments,
|
|
668
|
-
existingSegments,
|
|
669
|
-
addedSegments: [],
|
|
670
|
-
removedSegments: [],
|
|
671
|
-
created: false
|
|
672
|
-
};
|
|
673
|
-
let updated = content;
|
|
674
|
-
if (stale.length > 0) updated = removeFromSchema(updated, stale);
|
|
675
|
-
if (missing.length > 0) updated = appendToSchema(updated, missing);
|
|
676
|
-
writeFileSync(schemaPath, updated, "utf-8");
|
|
677
|
-
return {
|
|
678
|
-
filesystemSegments,
|
|
679
|
-
existingSegments,
|
|
680
|
-
addedSegments: missing,
|
|
681
|
-
removedSegments: stale,
|
|
682
|
-
created: false
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
//#endregion
|
|
686
|
-
export { scanRoutes as i, findSchemaFile as n, parseExistingSchemaKeys as r, cli_schema_sync_exports as t };
|
|
687
|
-
|
|
688
|
-
//# sourceMappingURL=cli-schema-sync-D3naS3eH.js.map
|