@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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/AGENTS.md +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2500 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -15,10 +15,9 @@ argument-hint: [setup]
|
|
|
15
15
|
import { createRouter } from "@rangojs/router";
|
|
16
16
|
import { urlpatterns } from "./urls";
|
|
17
17
|
|
|
18
|
-
const router = createRouter<
|
|
18
|
+
const router = createRouter<AppBindings>({
|
|
19
19
|
document: Document,
|
|
20
|
-
|
|
21
|
-
});
|
|
20
|
+
}).routes(urlpatterns);
|
|
22
21
|
|
|
23
22
|
// Server-side named-route reverse (type-safe via routeMap)
|
|
24
23
|
export const reverse = router.reverse;
|
|
@@ -26,6 +25,109 @@ export const reverse = router.reverse;
|
|
|
26
25
|
export default router;
|
|
27
26
|
```
|
|
28
27
|
|
|
28
|
+
### Which global type should I use?
|
|
29
|
+
|
|
30
|
+
Use the generated route map by default. Manual `RegisteredRoutes` augmentation
|
|
31
|
+
is only needed when you want the richer `typeof router.routeMap` shape
|
|
32
|
+
available globally.
|
|
33
|
+
|
|
34
|
+
- `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
|
|
35
|
+
Use for `Handler<"name">` (type annotation), `Prerender<"name">(...)` (function
|
|
36
|
+
call with type arg for param inference), server `ctx.reverse()`, and
|
|
37
|
+
named-route param/search inference.
|
|
38
|
+
- `typeof router.routeMap` — the real merged route map from your router
|
|
39
|
+
instance, including response-route metadata such as `{ path, response }`.
|
|
40
|
+
- `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
|
|
41
|
+
to global utilities that need the exact router-builder map, especially
|
|
42
|
+
`Rango.PathResponse`.
|
|
43
|
+
|
|
44
|
+
### Generated Route Type Surfaces
|
|
45
|
+
|
|
46
|
+
There are three distinct typing surfaces. They are **not** interchangeable —
|
|
47
|
+
pick the one that matches what you need to type:
|
|
48
|
+
|
|
49
|
+
| Surface | Source | Scope | Gives | Does not give |
|
|
50
|
+
| ------------------- | ---------------------------------------- | ------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
51
|
+
| `GeneratedRouteMap` | `router.named-routes.gen.ts` (auto) | global | route names, path params, search schemas | response/MIME payloads |
|
|
52
|
+
| `routes` | per-module `*.gen.ts` (`rango generate`) | local | local names, params, search | the global app map |
|
|
53
|
+
| `RegisteredRoutes` | manual `extends typeof router.routeMap` | global | paths, params, **response payloads** | the `Handler`/`Prerender` default (those read `GeneratedRouteMap` to avoid a `router.tsx` cycle) |
|
|
54
|
+
|
|
55
|
+
Key consequence: `href()` and the ambient `Rango.Path` type are typed from
|
|
56
|
+
whichever map is present — they prefer `RegisteredRoutes` when you wire it, otherwise fall back to
|
|
57
|
+
the auto-generated `GeneratedRouteMap`, so **`rango generate` alone gives you
|
|
58
|
+
path-checked `href()`** with no manual augmentation. Response and MIME payload
|
|
59
|
+
inference is the exception: it comes only from `typeof router.routeMap` (via
|
|
60
|
+
`RegisteredRoutes`), because `GeneratedRouteMap` carries paths + search but no
|
|
61
|
+
payloads — so `Rango.PathResponse` resolves to `never` until you wire
|
|
62
|
+
`RegisteredRoutes`.
|
|
63
|
+
|
|
64
|
+
Recommended setup:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// router.tsx
|
|
68
|
+
import { createRouter } from "@rangojs/router";
|
|
69
|
+
import { urlpatterns } from "./urls";
|
|
70
|
+
import type { AppBindings, AppVars } from "./env";
|
|
71
|
+
|
|
72
|
+
export const router = createRouter<AppBindings>({}).routes(urlpatterns);
|
|
73
|
+
|
|
74
|
+
declare global {
|
|
75
|
+
namespace Rango {
|
|
76
|
+
interface Env extends AppBindings {}
|
|
77
|
+
interface Vars extends AppVars {}
|
|
78
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Single-App Setup Checklist
|
|
84
|
+
|
|
85
|
+
For one app, keep the ambient types, generated named-routes file, and router
|
|
86
|
+
instance in the same TypeScript program:
|
|
87
|
+
|
|
88
|
+
```jsonc
|
|
89
|
+
// tsconfig.json
|
|
90
|
+
{
|
|
91
|
+
"compilerOptions": {
|
|
92
|
+
"strict": true,
|
|
93
|
+
"moduleResolution": "bundler",
|
|
94
|
+
"jsx": "react-jsx",
|
|
95
|
+
"noEmit": true,
|
|
96
|
+
},
|
|
97
|
+
"include": ["src"],
|
|
98
|
+
"files": ["src/router.tsx"],
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Then generate the route types from the router file:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx rango generate src/router.tsx
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This creates `src/router.named-routes.gen.ts`, which augments
|
|
109
|
+
`Rango.GeneratedRouteMap`. Keep that generated file committed with the router
|
|
110
|
+
source. The `files` entry keeps `router.tsx` in the program even when nothing
|
|
111
|
+
imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
|
|
112
|
+
`Rango.RegisteredRoutes` augmentation are visible to handlers, loaders, actions,
|
|
113
|
+
and client helpers.
|
|
114
|
+
|
|
115
|
+
### Named Routes, `$$routeNames`, And `router.routeMap`
|
|
116
|
+
|
|
117
|
+
There are two runtime/type surfaces with similar names:
|
|
118
|
+
|
|
119
|
+
- `router.named-routes.gen.ts` exports `NamedRoutes` and augments
|
|
120
|
+
`Rango.GeneratedRouteMap`. The Vite plugin imports that file internally and
|
|
121
|
+
injects it as `$$routeNames` so `router.reverse` has the static route-name map.
|
|
122
|
+
App code should not pass or import `$$routeNames` directly.
|
|
123
|
+
- `router.routeMap` is the public router instance property for type extraction.
|
|
124
|
+
Use `typeof router.routeMap` when augmenting `Rango.RegisteredRoutes` for
|
|
125
|
+
global response payload helpers such as `Rango.PathResponse`.
|
|
126
|
+
|
|
127
|
+
Do not document or use a public `router.routeNames` API unless one is
|
|
128
|
+
intentionally added. Today, the public extraction surface is `router.routeMap`;
|
|
129
|
+
the generated file and `$$routeNames` are build machinery.
|
|
130
|
+
|
|
29
131
|
## Route Definition with Type-Safe Names
|
|
30
132
|
|
|
31
133
|
```typescript
|
|
@@ -45,34 +147,34 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
45
147
|
|
|
46
148
|
## Type-Safe href()
|
|
47
149
|
|
|
48
|
-
### Server: ctx.reverse with
|
|
150
|
+
### Server: ctx.reverse with route names
|
|
151
|
+
|
|
152
|
+
In route handlers, `ctx.reverse()` uses two namespaces:
|
|
49
153
|
|
|
50
|
-
|
|
51
|
-
from
|
|
154
|
+
- **`.name`** — local route, resolved within the current `include()` scope
|
|
155
|
+
- **`name`** — global route, from the named-routes definition
|
|
52
156
|
|
|
53
157
|
```typescript
|
|
54
158
|
import type { Handler } from "@rangojs/router";
|
|
55
159
|
|
|
56
160
|
export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
57
|
-
ctx.reverse("
|
|
58
|
-
ctx.reverse("
|
|
59
|
-
ctx.reverse("blog.post", {
|
|
60
|
-
|
|
61
|
-
return <ProductPage slug={ctx.params.slug} />;
|
|
161
|
+
ctx.reverse(".cart"); // Local: /shop/cart
|
|
162
|
+
ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
|
|
163
|
+
ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
|
|
62
164
|
};
|
|
63
165
|
```
|
|
64
166
|
|
|
65
|
-
For
|
|
66
|
-
|
|
167
|
+
For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
|
|
168
|
+
and pass it as the second generic to `Handler` or `Prerender`:
|
|
67
169
|
|
|
68
170
|
```typescript
|
|
69
|
-
import {
|
|
171
|
+
import type { Handler } from "@rangojs/router";
|
|
70
172
|
import type { routes } from "./shop.gen.js";
|
|
71
173
|
|
|
72
|
-
export const ProductHandler: Handler<"product", routes> = (ctx) => {
|
|
73
|
-
|
|
74
|
-
reverse("
|
|
75
|
-
reverse("
|
|
174
|
+
export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
|
|
175
|
+
ctx.reverse(".cart"); // Type-safe local name
|
|
176
|
+
ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
|
|
177
|
+
ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
|
|
76
178
|
};
|
|
77
179
|
```
|
|
78
180
|
|
|
@@ -95,6 +197,107 @@ function ShopNav() {
|
|
|
95
197
|
}
|
|
96
198
|
```
|
|
97
199
|
|
|
200
|
+
`href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
|
|
201
|
+
it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
|
|
202
|
+
alone type-checks `href()` paths with no manual augmentation. The augmentation
|
|
203
|
+
below is only needed for **`Rango.PathResponse`** (response-payload inference), which
|
|
204
|
+
`GeneratedRouteMap` cannot provide:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
declare global {
|
|
208
|
+
namespace Rango {
|
|
209
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
For wrapper helpers, type the path parameter as `Rango.Path`. It is ambient (no
|
|
215
|
+
import) and shares `href()`'s compile-time path checking, so a wrapper stays in
|
|
216
|
+
sync with your routes automatically:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { href } from "@rangojs/router/client";
|
|
220
|
+
|
|
221
|
+
export const appHref = (path: Rango.Path): string => href(path);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
For response-route payloads, `Rango.PathResponse<T>` is the ambient lookup. It
|
|
225
|
+
accepts a route _pattern_ **or** a concrete path, so it also serves as the return
|
|
226
|
+
type of a typed `fetch` wrapper. It only resolves once `RegisteredRoutes` carries
|
|
227
|
+
response metadata:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { href } from "@rangojs/router/client";
|
|
231
|
+
|
|
232
|
+
type Product = Rango.PathResponse<"/api/products/:id">; // by pattern
|
|
233
|
+
type Same = Rango.PathResponse<"/api/products/42">; // by concrete path
|
|
234
|
+
|
|
235
|
+
// Response inferred from the concrete path passed in:
|
|
236
|
+
async function get<T extends Rango.Path>(
|
|
237
|
+
path: T,
|
|
238
|
+
): Promise<Rango.PathResponse<T>> {
|
|
239
|
+
return fetch(href(path)).then((r) => r.json());
|
|
240
|
+
}
|
|
241
|
+
const product = await get("/api/products/42"); // Product (bare value)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
|
|
245
|
+
route can match several patterns and union their responses.
|
|
246
|
+
|
|
247
|
+
`Rango.PathResponse` describes the JSON **wire** shape, not the handler's raw
|
|
248
|
+
return. A `path.json()` handler returning `{ createdAt: Date }` resolves here to
|
|
249
|
+
`{ createdAt: string }` (bare value), matching what `r.json()` yields. This
|
|
250
|
+
is applied via the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
|
|
251
|
+
honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). A separate
|
|
252
|
+
`Rango.FlightSerialize<T>` models the higher-fidelity RSC Flight boundary
|
|
253
|
+
(loaders / RSC props, where `Date` is preserved) — do **not** use it for
|
|
254
|
+
`path.json()`.
|
|
255
|
+
|
|
256
|
+
### Overriding serialization globally
|
|
257
|
+
|
|
258
|
+
For your own types, the zero-config way to control the JSON wire shape is a
|
|
259
|
+
`toJSON()` method — `Rango.JsonSerialize` honors it, and it matches the runtime
|
|
260
|
+
exactly (`JSON.stringify` calls `toJSON()`):
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
class Money {
|
|
264
|
+
constructor(private cents: number) {}
|
|
265
|
+
toJSON(): number {
|
|
266
|
+
return this.cents;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Rango.JsonSerialize<Money> is number; Rango.PathResponse reflects it.
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
To override a transform for types you **don't** own (or for the Flight boundary,
|
|
273
|
+
which has no `toJSON()`), augment its override slot. Because `Rango.JsonSerialize`
|
|
274
|
+
/ `Rango.FlightSerialize` are type _aliases_ (TS can't merge those), you provide a
|
|
275
|
+
single member that is your **complete** transform, delegating to the built-in for
|
|
276
|
+
the cases you don't change:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
declare global {
|
|
280
|
+
namespace Rango {
|
|
281
|
+
interface JsonSerializeOverride<T> {
|
|
282
|
+
app: T extends Decimal ? string : Rango.JsonSerializeBuiltin<T>;
|
|
283
|
+
}
|
|
284
|
+
interface FlightSerializeOverride<T> {
|
|
285
|
+
app: T extends Money ? number : Rango.FlightSerializeBuiltin<T>;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Rango.JsonSerialize<Decimal> -> string; Rango.FlightSerialize<Money> -> number;
|
|
290
|
+
// everything else stays on the built-in, recursively (nested fields too).
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Rules: provide **exactly one** member (the slot is read as
|
|
294
|
+
`Override<T>[keyof Override<T>]`, so multiple members union and conflict).
|
|
295
|
+
Overrides win over `toJSON()` and apply at every nesting level. Caveat for JSON:
|
|
296
|
+
the `path.json()` runtime is plain `JSON.stringify`, which only honors `toJSON()`,
|
|
297
|
+
so a `JsonSerializeOverride` that disagrees with what the runtime emits will lie —
|
|
298
|
+
prefer `toJSON()` for your own types and use the slot only for types you can't
|
|
299
|
+
modify.
|
|
300
|
+
|
|
98
301
|
See `/links` for full URL generation guide.
|
|
99
302
|
|
|
100
303
|
## Environment Type Setup
|
|
@@ -103,50 +306,57 @@ Define your app's environment for type-safe bindings and variables:
|
|
|
103
306
|
|
|
104
307
|
```typescript
|
|
105
308
|
// env.ts
|
|
106
|
-
import type { RouterEnv } from "@rangojs/router";
|
|
107
309
|
|
|
108
|
-
// Cloudflare bindings
|
|
109
|
-
interface AppBindings {
|
|
310
|
+
// Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
|
|
311
|
+
export interface AppBindings {
|
|
110
312
|
DB: D1Database;
|
|
111
313
|
KV: KVNamespace;
|
|
112
314
|
CACHE: KVNamespace;
|
|
113
315
|
AI: Ai;
|
|
114
316
|
}
|
|
115
317
|
|
|
116
|
-
// Variables set by middleware
|
|
117
|
-
interface AppVariables {
|
|
318
|
+
// Variables set by middleware — declared via global namespace augmentation
|
|
319
|
+
export interface AppVariables {
|
|
118
320
|
user?: { id: string; email: string; role: string };
|
|
119
321
|
requestId?: string;
|
|
120
322
|
permissions?: string[];
|
|
121
323
|
}
|
|
122
|
-
|
|
123
|
-
// Combined environment type
|
|
124
|
-
export type AppEnv = RouterEnv<AppBindings, AppVariables>;
|
|
125
324
|
```
|
|
126
325
|
|
|
127
326
|
### Using Environment Types
|
|
128
327
|
|
|
129
328
|
```typescript
|
|
130
329
|
// router.tsx
|
|
131
|
-
import type {
|
|
330
|
+
import type { AppBindings, AppVariables } from "./env";
|
|
132
331
|
|
|
133
|
-
const router = createRouter<
|
|
332
|
+
const router = createRouter<AppBindings>({
|
|
134
333
|
document: Document,
|
|
135
|
-
|
|
136
|
-
});
|
|
334
|
+
}).routes(urlpatterns);
|
|
137
335
|
|
|
138
|
-
//
|
|
139
|
-
|
|
336
|
+
// Register bindings and variables globally for implicit typing
|
|
337
|
+
declare global {
|
|
338
|
+
namespace Rango {
|
|
339
|
+
interface Env extends AppBindings {}
|
|
340
|
+
interface Vars extends AppVariables {}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
140
343
|
|
|
141
|
-
|
|
142
|
-
|
|
344
|
+
// middleware - typed via ctx.set / ctx.get
|
|
345
|
+
import type { Middleware } from "@rangojs/router";
|
|
346
|
+
|
|
347
|
+
export const authMiddleware: Middleware = async (ctx, next) => {
|
|
348
|
+
ctx.set("user", {
|
|
349
|
+
id: "123",
|
|
350
|
+
email: "user@example.com",
|
|
351
|
+
role: "admin",
|
|
352
|
+
});
|
|
143
353
|
await next();
|
|
144
|
-
}
|
|
354
|
+
};
|
|
145
355
|
|
|
146
356
|
// loaders - typed context
|
|
147
|
-
export const UserLoader = createLoader(
|
|
148
|
-
const db = ctx.env.
|
|
149
|
-
const userId = ctx.
|
|
357
|
+
export const UserLoader = createLoader(async (ctx) => {
|
|
358
|
+
const db = ctx.env.DB; // D1Database (plain bindings)
|
|
359
|
+
const userId = ctx.get("user")?.id; // from Rango.Vars
|
|
150
360
|
return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
|
|
151
361
|
});
|
|
152
362
|
```
|
|
@@ -158,8 +368,9 @@ Register environment types globally for implicit typing:
|
|
|
158
368
|
```typescript
|
|
159
369
|
// router.tsx
|
|
160
370
|
declare global {
|
|
161
|
-
namespace
|
|
162
|
-
interface Env extends
|
|
371
|
+
namespace Rango {
|
|
372
|
+
interface Env extends AppBindings {}
|
|
373
|
+
interface Vars extends AppVariables {}
|
|
163
374
|
}
|
|
164
375
|
}
|
|
165
376
|
```
|
|
@@ -168,10 +379,10 @@ Now handlers have typed context without explicit imports:
|
|
|
168
379
|
|
|
169
380
|
```typescript
|
|
170
381
|
// In loaders
|
|
171
|
-
export const DashboardLoader = createLoader(
|
|
172
|
-
// ctx.env.
|
|
173
|
-
// ctx.
|
|
174
|
-
const user = ctx.
|
|
382
|
+
export const DashboardLoader = createLoader(async (ctx) => {
|
|
383
|
+
// ctx.env.DB is typed from global Rango.Env
|
|
384
|
+
// ctx.get("user") is typed from global Rango.Vars
|
|
385
|
+
const user = ctx.get("user");
|
|
175
386
|
return { user };
|
|
176
387
|
});
|
|
177
388
|
```
|
|
@@ -185,7 +396,7 @@ Add a `search` schema to `path()` options for type-safe query parameters:
|
|
|
185
396
|
path("/search", SearchPage, {
|
|
186
397
|
name: "search",
|
|
187
398
|
search: { q: "string", page: "number?", sort: "string?" },
|
|
188
|
-
})
|
|
399
|
+
});
|
|
189
400
|
```
|
|
190
401
|
|
|
191
402
|
### Handler with typed search params
|
|
@@ -198,8 +409,8 @@ global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
|
|
|
198
409
|
import type { Handler } from "@rangojs/router";
|
|
199
410
|
|
|
200
411
|
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
201
|
-
// ctx.
|
|
202
|
-
const { q, page, sort } = ctx.
|
|
412
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
413
|
+
const { q, page, sort } = ctx.search;
|
|
203
414
|
return <SearchResults q={q} page={page} sort={sort} />;
|
|
204
415
|
};
|
|
205
416
|
```
|
|
@@ -208,15 +419,21 @@ This avoids circular references because `Handler` defaults to `GeneratedRouteMap
|
|
|
208
419
|
(from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
|
|
209
420
|
|
|
210
421
|
You can also pass an explicit route map for per-module isolation (opt-in,
|
|
211
|
-
after running `npx rango generate`)
|
|
422
|
+
after running `npx rango generate`). With a local map, the route name is
|
|
423
|
+
**dot-prefixed** so params and search resolve from `routes`, not the global map:
|
|
212
424
|
|
|
213
425
|
```typescript
|
|
214
426
|
import type { Handler } from "@rangojs/router";
|
|
215
427
|
import type { routes } from "./urls.gen.js";
|
|
216
428
|
|
|
217
|
-
export const SearchPage: Handler<"search", routes> = (ctx) => { ... };
|
|
429
|
+
export const SearchPage: Handler<".search", routes> = (ctx) => { ... };
|
|
218
430
|
```
|
|
219
431
|
|
|
432
|
+
Note the difference: `Handler<"search">` (no dot) resolves against the global
|
|
433
|
+
`GeneratedRouteMap`; `Handler<".search", routes>` resolves against the local
|
|
434
|
+
`routes` map. Mixing them — `Handler<"search", routes>` — silently ignores
|
|
435
|
+
`routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
|
|
436
|
+
|
|
220
437
|
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
221
438
|
Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
|
|
222
439
|
Routes without a `search` schema keep the standard `URLSearchParams` behavior.
|
|
@@ -230,12 +447,18 @@ import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
|
230
447
|
|
|
231
448
|
// RouteSearchParams<"name"> resolves the search schema to a typed object
|
|
232
449
|
type SP = RouteSearchParams<"search">;
|
|
233
|
-
// { q: string; page?: number; sort?: string }
|
|
450
|
+
// { q: string | undefined; page?: number; sort?: string }
|
|
234
451
|
|
|
235
452
|
// RouteParams<"name"> resolves URL params from the route pattern
|
|
236
453
|
type P = RouteParams<"blogPost">;
|
|
237
454
|
// { slug: string }
|
|
238
455
|
|
|
456
|
+
// Optional URL params (`:slug?`) resolve to `string | undefined`
|
|
457
|
+
// because absent segments are omitted from `ctx.params` at runtime.
|
|
458
|
+
type C = RouteParams<"checkout">;
|
|
459
|
+
// { step?: string }
|
|
460
|
+
// → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
|
|
461
|
+
|
|
239
462
|
// Use in component props
|
|
240
463
|
interface SearchResultsProps {
|
|
241
464
|
params: RouteSearchParams<"search">;
|
|
@@ -260,18 +483,34 @@ use `{ path, search }` objects:
|
|
|
260
483
|
```typescript
|
|
261
484
|
// router.named-routes.gen.ts (auto-generated)
|
|
262
485
|
export const NamedRoutes = {
|
|
263
|
-
"search.index": {
|
|
264
|
-
|
|
486
|
+
"search.index": {
|
|
487
|
+
path: "/search",
|
|
488
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
489
|
+
},
|
|
490
|
+
"home.index": "/", // No search schema -> plain string
|
|
265
491
|
} as const;
|
|
266
492
|
```
|
|
267
493
|
|
|
494
|
+
You never open a `.gen.ts` by hand. Treat the generated types as call-site
|
|
495
|
+
honesty checks, not modules to read:
|
|
496
|
+
|
|
497
|
+
- **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
|
|
498
|
+
`Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
|
|
499
|
+
globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
|
|
500
|
+
- **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
|
|
501
|
+
pattern for `useReverse(routes)` and explicit local handler typing
|
|
502
|
+
(`Handler<".name", routes>`). See `/links`.
|
|
503
|
+
|
|
504
|
+
If a type error points at a generated map instead of your call site, that's a
|
|
505
|
+
smell — fix the call site (or regenerate), never edit the generated file.
|
|
506
|
+
|
|
268
507
|
## Loader Type Safety
|
|
269
508
|
|
|
270
509
|
Loaders have typed return values:
|
|
271
510
|
|
|
272
511
|
```typescript
|
|
273
512
|
// loaders/product.ts
|
|
274
|
-
export const ProductLoader = createLoader(
|
|
513
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
275
514
|
return {
|
|
276
515
|
id: ctx.params.slug,
|
|
277
516
|
name: "Widget",
|
|
@@ -280,7 +519,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
|
|
|
280
519
|
});
|
|
281
520
|
|
|
282
521
|
// In server component - type is inferred
|
|
283
|
-
import { useLoader } from "@rangojs/router";
|
|
522
|
+
import { useLoader } from "@rangojs/router/client";
|
|
284
523
|
|
|
285
524
|
async function ProductPage() {
|
|
286
525
|
const product = await useLoader(ProductLoader);
|
|
@@ -290,40 +529,110 @@ async function ProductPage() {
|
|
|
290
529
|
|
|
291
530
|
// In client component - same type
|
|
292
531
|
"use client";
|
|
293
|
-
import {
|
|
532
|
+
import { useLoader } from "@rangojs/router/client";
|
|
294
533
|
|
|
295
534
|
function ProductPrice() {
|
|
296
|
-
const {
|
|
297
|
-
//
|
|
535
|
+
const { data } = useLoader(ProductLoader);
|
|
536
|
+
// data: { id: string; name: string; price: number }
|
|
537
|
+
const product = data;
|
|
298
538
|
return <span>${product.price}</span>;
|
|
299
539
|
}
|
|
300
540
|
```
|
|
301
541
|
|
|
302
|
-
##
|
|
542
|
+
## Typed Context Variables
|
|
303
543
|
|
|
304
|
-
|
|
544
|
+
`createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
|
|
545
|
+
handler-to-layout data contracts explicit and compile-time verified:
|
|
305
546
|
|
|
306
547
|
```typescript
|
|
307
|
-
|
|
308
|
-
import { createHandle } from "@rangojs/router";
|
|
548
|
+
import { createVar } from "@rangojs/router";
|
|
309
549
|
|
|
310
|
-
//
|
|
311
|
-
|
|
550
|
+
// Define a typed token (shared between producer and consumer)
|
|
551
|
+
interface PaginationData {
|
|
552
|
+
current: number;
|
|
553
|
+
total: number;
|
|
554
|
+
perPage: number;
|
|
555
|
+
}
|
|
556
|
+
export const Pagination = createVar<PaginationData>();
|
|
312
557
|
|
|
313
|
-
//
|
|
314
|
-
|
|
558
|
+
// Non-cacheable var — reading inside cache() or "use cache" throws at runtime
|
|
559
|
+
const Session = createVar<SessionData>({ cache: false });
|
|
560
|
+
```
|
|
315
561
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
562
|
+
`createVar` accepts an optional options object. The `cache` option (default
|
|
563
|
+
`true`) controls whether the var's values can be read inside cache scopes.
|
|
564
|
+
Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
|
|
565
|
+
marks a specific write as non-cacheable even if the var itself is cacheable.
|
|
566
|
+
"Least cacheable wins" — if either says `cache: false`, the value throws on
|
|
567
|
+
read inside `cache()` or `"use cache"`.
|
|
568
|
+
|
|
569
|
+
### Producer (handler or middleware)
|
|
321
570
|
|
|
322
|
-
|
|
571
|
+
```typescript
|
|
572
|
+
import { Pagination } from "../vars/pagination.js";
|
|
573
|
+
|
|
574
|
+
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
575
|
+
ctx.set(Pagination, { // type-checked
|
|
576
|
+
current: 1,
|
|
577
|
+
total: 10,
|
|
578
|
+
perPage: 5,
|
|
579
|
+
});
|
|
580
|
+
return <Articles />;
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Consumer (layout, parallel, or any context with get)
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { Pagination } from "../vars/pagination.js";
|
|
588
|
+
|
|
589
|
+
export function PaginationLayout(ctx: any) {
|
|
590
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
591
|
+
if (!pagination) return <Outlet />;
|
|
592
|
+
return <nav>Page {pagination.current} of {pagination.total}</nav>;
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Why not just use Rango.Vars?
|
|
597
|
+
|
|
598
|
+
`Rango.Vars` (via global namespace augmentation) provides app-global typing for
|
|
599
|
+
`ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
|
|
600
|
+
shared app-wide. `createVar<T>()` is for route-local or feature-scoped
|
|
601
|
+
context -- the producer and consumer import the same token, creating a
|
|
602
|
+
scoped contract without polluting global types.
|
|
603
|
+
|
|
604
|
+
Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
605
|
+
`ctx.get(Pagination)` (scoped via createVar) work side by side.
|
|
606
|
+
|
|
607
|
+
## Handle Type Safety
|
|
608
|
+
|
|
609
|
+
Handles have typed data:
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
613
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
614
|
+
// Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
615
|
+
// BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
|
|
616
|
+
|
|
617
|
+
// In route handler — push is fully typed
|
|
618
|
+
path("/shop/product/:slug", (ctx) => {
|
|
619
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
620
|
+
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
621
|
+
return <ProductPage />;
|
|
622
|
+
}, { name: "product" });
|
|
623
|
+
|
|
624
|
+
// In client — typed array
|
|
625
|
+
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
323
626
|
function BreadcrumbNav() {
|
|
324
627
|
const crumbs = useHandle(Breadcrumbs);
|
|
325
|
-
// crumbs:
|
|
628
|
+
// crumbs: BreadcrumbItem[]
|
|
326
629
|
}
|
|
630
|
+
|
|
631
|
+
// Custom handles also work the same way
|
|
632
|
+
import { createHandle } from "@rangojs/router";
|
|
633
|
+
export const PageTitle = createHandle<string, string>(
|
|
634
|
+
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
635
|
+
);
|
|
327
636
|
```
|
|
328
637
|
|
|
329
638
|
## Ref Prop Type Safety (Loaders & Handles)
|
|
@@ -337,24 +646,24 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
337
646
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
338
647
|
});
|
|
339
648
|
|
|
340
|
-
//
|
|
341
|
-
|
|
649
|
+
// Built-in Breadcrumbs — or any custom handle created with createHandle()
|
|
650
|
+
```
|
|
342
651
|
|
|
652
|
+
```tsx
|
|
343
653
|
// Client component — typeof infers all generics
|
|
344
654
|
"use client";
|
|
345
|
-
import { useLoader, useHandle } from "@rangojs/router/client";
|
|
655
|
+
import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
346
656
|
import type { ProductLoader } from "../loaders";
|
|
347
|
-
import type { Breadcrumbs } from "../handles";
|
|
348
657
|
|
|
349
658
|
function MyComponent({
|
|
350
659
|
loader,
|
|
351
660
|
handle,
|
|
352
661
|
}: {
|
|
353
|
-
loader: typeof ProductLoader;
|
|
354
|
-
handle: typeof Breadcrumbs;
|
|
662
|
+
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
663
|
+
handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
|
|
355
664
|
}) {
|
|
356
|
-
const { data } = useLoader(loader);
|
|
357
|
-
const crumbs = useHandle(handle);
|
|
665
|
+
const { data } = useLoader(loader); // data is typed
|
|
666
|
+
const crumbs = useHandle(handle); // crumbs is typed array
|
|
358
667
|
// ...
|
|
359
668
|
}
|
|
360
669
|
```
|
|
@@ -363,6 +672,42 @@ RSC Flight serialization calls `toJSON()` on both loaders and handles,
|
|
|
363
672
|
sending only `{ __brand, $$id }` to the client. The hooks recover the
|
|
364
673
|
full functionality from module-level registries.
|
|
365
674
|
|
|
675
|
+
## Stable identity: `path#export`
|
|
676
|
+
|
|
677
|
+
Loaders, handles, cached functions (`functionId`), and server actions
|
|
678
|
+
(`actionId`) all share one identity scheme: `{modulePath}#{exportName}`,
|
|
679
|
+
injected at build by the `exposeInternalIds` and `exposeActionId` Vite plugins.
|
|
680
|
+
This is also the identity React server actions carry across the Flight boundary,
|
|
681
|
+
which is why a `revalidate()` predicate sees an action as a `path#export` string:
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
revalidate(
|
|
685
|
+
({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined,
|
|
686
|
+
);
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
`actionId` is the only stable reference React exposes across the Flight boundary,
|
|
690
|
+
so it stays as the floor and escape hatch. The hand-written-string surface
|
|
691
|
+
(`actionId?.includes("cart.ts#")`) is brittle: a renamed action or moved file
|
|
692
|
+
silently stops matching with no compile error. Prefer **`ctx.isAction()`** in a
|
|
693
|
+
revalidate predicate — it resolves the action's id from an imported reference, so
|
|
694
|
+
a rename is a type error in one place instead of silent drift:
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import { addToCart, removeFromCart } from "./actions/cart";
|
|
698
|
+
import * as CartActions from "./actions/cart";
|
|
699
|
+
|
|
700
|
+
revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
|
|
701
|
+
revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
|
|
702
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
`ctx.isAction()` (only available on the revalidate predicate's context) returns a
|
|
706
|
+
raw boolean — combine with `|| undefined` for the "revalidate on match, else
|
|
707
|
+
defer" intent. It resolves the reference the same way the router derives
|
|
708
|
+
`actionId` (`$id` in production, `$$id` in dev), so matching
|
|
709
|
+
works in both modes. `actionId` stays available for advanced cases.
|
|
710
|
+
|
|
366
711
|
## Location State Type Safety
|
|
367
712
|
|
|
368
713
|
```typescript
|
|
@@ -398,9 +743,37 @@ function ProductHeader() {
|
|
|
398
743
|
|
|
399
744
|
## Multi-Project tsconfig Setup
|
|
400
745
|
|
|
401
|
-
For monorepos or multi-app setups,
|
|
402
|
-
|
|
403
|
-
|
|
746
|
+
For monorepos or multi-app setups, each app should have its own TypeScript
|
|
747
|
+
program. Do not typecheck two Rango apps with different `Rango.Env`,
|
|
748
|
+
`Rango.Vars`, or `Rango.RegisteredRoutes` declarations in one tsconfig, because
|
|
749
|
+
ambient global interfaces merge across the whole program.
|
|
750
|
+
|
|
751
|
+
### Multiple routers in one program
|
|
752
|
+
|
|
753
|
+
`Rango.GeneratedRouteMap` is a **single global interface**. Each router's
|
|
754
|
+
generated `router.named-routes.gen.ts` augments it, so two routers in the **same
|
|
755
|
+
TS program** that define overlapping route names (e.g. both have a `home`) make
|
|
756
|
+
the augmentations collide:
|
|
757
|
+
|
|
758
|
+
```text
|
|
759
|
+
Interface 'GeneratedRouteMap' cannot simultaneously extend ...
|
|
760
|
+
Named property 'home' ... are not identical.
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
This is the multi-router / host-router case. Resolve it by:
|
|
764
|
+
|
|
765
|
+
- **Separate TS programs** — give each router its own tsconfig (as below) so only
|
|
766
|
+
one generated map is in scope per program. Recommended.
|
|
767
|
+
- **Unique route-name prefixes** — name routes per router (`appA.home`,
|
|
768
|
+
`appB.home`) so the merged global map has no duplicate keys.
|
|
769
|
+
|
|
770
|
+
A single global generated map is a single-router convenience; global named-route
|
|
771
|
+
typing across multiple routers in one program is not supported today (it would
|
|
772
|
+
need per-router scoping in the generated map).
|
|
773
|
+
|
|
774
|
+
Use a shared base tsconfig for common compiler options, then make every app
|
|
775
|
+
tsconfig include its own source tree, its own `router.tsx`, and the generated
|
|
776
|
+
`router.named-routes.gen.ts` that lives beside that router.
|
|
404
777
|
|
|
405
778
|
```jsonc
|
|
406
779
|
// tsconfig.base.json (root)
|
|
@@ -416,8 +789,8 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
416
789
|
"skipLibCheck": true,
|
|
417
790
|
"isolatedModules": true,
|
|
418
791
|
"esModuleInterop": true,
|
|
419
|
-
"resolveJsonModule": true
|
|
420
|
-
}
|
|
792
|
+
"resolveJsonModule": true,
|
|
793
|
+
},
|
|
421
794
|
}
|
|
422
795
|
```
|
|
423
796
|
|
|
@@ -426,7 +799,7 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
426
799
|
{
|
|
427
800
|
"extends": "../../tsconfig.base.json",
|
|
428
801
|
"include": ["src"],
|
|
429
|
-
"files": ["src/router.tsx"]
|
|
802
|
+
"files": ["src/router.tsx"],
|
|
430
803
|
}
|
|
431
804
|
```
|
|
432
805
|
|
|
@@ -435,19 +808,66 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
435
808
|
{
|
|
436
809
|
"extends": "../../tsconfig.base.json",
|
|
437
810
|
"include": ["src"],
|
|
438
|
-
"files": ["src/router.tsx"]
|
|
811
|
+
"files": ["src/router.tsx"],
|
|
439
812
|
}
|
|
440
813
|
```
|
|
441
814
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
815
|
+
Run generation per app:
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
npx rango generate apps/shop/src/router.tsx
|
|
819
|
+
npx rango generate apps/blog/src/router.tsx
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
If an app has multiple tsconfigs (`tsconfig.app.json`, `tsconfig.test.json`,
|
|
823
|
+
`tsconfig.worker.json`), every tsconfig that typechecks Rango handlers,
|
|
824
|
+
components, loaders, actions, or client navigation must see the same app-local
|
|
825
|
+
type surfaces:
|
|
826
|
+
|
|
827
|
+
```jsonc
|
|
828
|
+
// apps/shop/tsconfig.test.json
|
|
829
|
+
{
|
|
830
|
+
"extends": "./tsconfig.json",
|
|
831
|
+
"include": ["src", "tests"],
|
|
832
|
+
"files": ["src/router.tsx"],
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
The `files` array ensures `router.tsx` is always included even if nothing
|
|
837
|
+
directly imports it. The generated `router.named-routes.gen.ts` is normally
|
|
838
|
+
covered by `include: ["src"]`; if a tsconfig uses a narrow `include`, add the
|
|
839
|
+
generated file explicitly. Each app gets its own typed environment and named
|
|
840
|
+
route map without interfering with other apps.
|
|
841
|
+
|
|
842
|
+
For response and MIME payload lookup in each app, augment `RegisteredRoutes`
|
|
843
|
+
inside that app's router file:
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
// apps/shop/src/router.tsx
|
|
847
|
+
export const router = createRouter<ShopEnv>({ document: Document }).routes(
|
|
848
|
+
urlpatterns,
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
declare global {
|
|
852
|
+
namespace Rango {
|
|
853
|
+
interface Env extends ShopEnv {}
|
|
854
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
```
|
|
445
858
|
|
|
446
859
|
## Complete Type-Safe Setup
|
|
447
860
|
|
|
448
861
|
```typescript
|
|
449
862
|
// 1. env.ts - Environment types
|
|
450
|
-
export
|
|
863
|
+
export interface AppBindings {
|
|
864
|
+
DB: D1Database;
|
|
865
|
+
KV: KVNamespace;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export interface AppVariables {
|
|
869
|
+
user?: { id: string; email: string; role: string };
|
|
870
|
+
}
|
|
451
871
|
|
|
452
872
|
// 2. urls.tsx - Route definitions with names
|
|
453
873
|
import { urls } from "@rangojs/router";
|
|
@@ -463,34 +883,43 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
|
463
883
|
]),
|
|
464
884
|
]);
|
|
465
885
|
|
|
466
|
-
// 3. router.tsx -
|
|
467
|
-
const router = createRouter<
|
|
886
|
+
// 3. router.tsx - Create router and export reverse
|
|
887
|
+
const router = createRouter<AppBindings>({
|
|
468
888
|
document: Document,
|
|
469
|
-
|
|
470
|
-
});
|
|
889
|
+
}).routes(urlpatterns);
|
|
471
890
|
|
|
891
|
+
// Register bindings and variables globally for implicit typing
|
|
472
892
|
declare global {
|
|
473
|
-
namespace
|
|
474
|
-
interface Env extends
|
|
893
|
+
namespace Rango {
|
|
894
|
+
interface Env extends AppBindings {}
|
|
895
|
+
interface Vars extends AppVariables {}
|
|
475
896
|
}
|
|
476
897
|
}
|
|
477
898
|
|
|
899
|
+
export const reverse = router.reverse;
|
|
478
900
|
export default router;
|
|
479
901
|
|
|
480
|
-
// 4.
|
|
481
|
-
|
|
902
|
+
// 4. Run `npx rango generate src/router.tsx` to generate
|
|
903
|
+
// router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
|
|
904
|
+
// No manual RegisteredRoutes declaration is needed for named-route handlers,
|
|
905
|
+
// ctx.reverse, prerender, href(), or Rango.Path. Add `RegisteredRoutes
|
|
906
|
+
// extends typeof router.routeMap` when global response payload helpers such
|
|
907
|
+
// as Rango.PathResponse need the richer router.routeMap metadata.
|
|
908
|
+
|
|
909
|
+
// 5. loaders/*.ts - Type-safe loaders
|
|
910
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
482
911
|
// ctx.params: { slug: string }
|
|
483
|
-
// ctx.
|
|
484
|
-
// ctx.env.
|
|
912
|
+
// ctx.get("user"): User | undefined (from Rango.Vars)
|
|
913
|
+
// ctx.env.DB: D1Database (plain bindings from Rango.Env)
|
|
485
914
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
486
915
|
});
|
|
487
916
|
|
|
488
|
-
//
|
|
917
|
+
// 6. Server: ctx.reverse for named routes
|
|
489
918
|
path("/product/:slug", (ctx) => {
|
|
490
919
|
return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
|
|
491
920
|
}, { name: "product" })
|
|
492
921
|
|
|
493
|
-
//
|
|
922
|
+
// 7. Client: useHref for mounted paths, href for absolute
|
|
494
923
|
"use client";
|
|
495
924
|
import { useHref, href, Link } from "@rangojs/router/client";
|
|
496
925
|
<Link to={href("/shop/product/widget")}>Widget</Link>
|