@rangojs/router 0.0.0-experimental.125 → 0.0.0-experimental.126
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/README.md +6 -4
- package/dist/__internal.d.ts +83 -0
- package/dist/__internal.d.ts.map +1 -0
- package/dist/__internal.js +19 -0
- package/dist/__internal.js.map +1 -0
- package/dist/__mocks__/version.d.ts +7 -0
- package/dist/__mocks__/version.d.ts.map +1 -0
- package/dist/__mocks__/version.js +7 -0
- package/dist/__mocks__/version.js.map +1 -0
- package/dist/__tests__/client-href.test.d.ts +2 -0
- package/dist/__tests__/client-href.test.d.ts.map +1 -0
- package/dist/__tests__/client-href.test.js +74 -0
- package/dist/__tests__/client-href.test.js.map +1 -0
- package/dist/__tests__/component-utils.test.d.ts +2 -0
- package/dist/__tests__/component-utils.test.d.ts.map +1 -0
- package/dist/__tests__/component-utils.test.js +51 -0
- package/dist/__tests__/component-utils.test.js.map +1 -0
- package/dist/__tests__/event-controller.test.d.ts +2 -0
- package/dist/__tests__/event-controller.test.d.ts.map +1 -0
- package/dist/__tests__/event-controller.test.js +538 -0
- package/dist/__tests__/event-controller.test.js.map +1 -0
- package/dist/__tests__/helpers/route-tree.d.ts +118 -0
- package/dist/__tests__/helpers/route-tree.d.ts.map +1 -0
- package/dist/__tests__/helpers/route-tree.js +374 -0
- package/dist/__tests__/helpers/route-tree.js.map +1 -0
- package/dist/__tests__/match-result.test.d.ts +2 -0
- package/dist/__tests__/match-result.test.d.ts.map +1 -0
- package/dist/__tests__/match-result.test.js +154 -0
- package/dist/__tests__/match-result.test.js.map +1 -0
- package/dist/__tests__/navigation-store.test.d.ts +2 -0
- package/dist/__tests__/navigation-store.test.d.ts.map +1 -0
- package/dist/__tests__/navigation-store.test.js +440 -0
- package/dist/__tests__/navigation-store.test.js.map +1 -0
- package/dist/__tests__/partial-update.test.d.ts +2 -0
- package/dist/__tests__/partial-update.test.d.ts.map +1 -0
- package/dist/__tests__/partial-update.test.js +1009 -0
- package/dist/__tests__/partial-update.test.js.map +1 -0
- package/dist/__tests__/reverse-types.test.d.ts +8 -0
- package/dist/__tests__/reverse-types.test.d.ts.map +1 -0
- package/dist/__tests__/reverse-types.test.js +656 -0
- package/dist/__tests__/reverse-types.test.js.map +1 -0
- package/dist/__tests__/route-definition.test.d.ts +2 -0
- package/dist/__tests__/route-definition.test.d.ts.map +1 -0
- package/dist/__tests__/route-definition.test.js +55 -0
- package/dist/__tests__/route-definition.test.js.map +1 -0
- package/dist/__tests__/router-helpers.test.d.ts +2 -0
- package/dist/__tests__/router-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/router-helpers.test.js +377 -0
- package/dist/__tests__/router-helpers.test.js.map +1 -0
- package/dist/__tests__/router-integration-2.test.d.ts +2 -0
- package/dist/__tests__/router-integration-2.test.d.ts.map +1 -0
- package/dist/__tests__/router-integration-2.test.js +426 -0
- package/dist/__tests__/router-integration-2.test.js.map +1 -0
- package/dist/__tests__/router-integration.test.d.ts +2 -0
- package/dist/__tests__/router-integration.test.d.ts.map +1 -0
- package/dist/__tests__/router-integration.test.js +1051 -0
- package/dist/__tests__/router-integration.test.js.map +1 -0
- package/dist/__tests__/search-params.test.d.ts +5 -0
- package/dist/__tests__/search-params.test.d.ts.map +1 -0
- package/dist/__tests__/search-params.test.js +306 -0
- package/dist/__tests__/search-params.test.js.map +1 -0
- package/dist/__tests__/segment-system.test.d.ts +2 -0
- package/dist/__tests__/segment-system.test.d.ts.map +1 -0
- package/dist/__tests__/segment-system.test.js +627 -0
- package/dist/__tests__/segment-system.test.js.map +1 -0
- package/dist/__tests__/static-handler-types.test.d.ts +8 -0
- package/dist/__tests__/static-handler-types.test.d.ts.map +1 -0
- package/dist/__tests__/static-handler-types.test.js +63 -0
- package/dist/__tests__/static-handler-types.test.js.map +1 -0
- package/dist/__tests__/urls.test.d.ts +2 -0
- package/dist/__tests__/urls.test.d.ts.map +1 -0
- package/dist/__tests__/urls.test.js +421 -0
- package/dist/__tests__/urls.test.js.map +1 -0
- package/dist/__tests__/use-mount.test.d.ts +2 -0
- package/dist/__tests__/use-mount.test.d.ts.map +1 -0
- package/dist/__tests__/use-mount.test.js +35 -0
- package/dist/__tests__/use-mount.test.js.map +1 -0
- package/dist/bin/rango.d.ts +2 -0
- package/dist/bin/rango.d.ts.map +1 -0
- package/dist/bin/rango.js.map +1 -0
- package/dist/browser/event-controller.d.ts +191 -0
- package/dist/browser/event-controller.d.ts.map +1 -0
- package/dist/browser/event-controller.js +559 -0
- package/dist/browser/event-controller.js.map +1 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/link-interceptor.d.ts +38 -0
- package/dist/browser/link-interceptor.d.ts.map +1 -0
- package/dist/browser/link-interceptor.js +99 -0
- package/dist/browser/link-interceptor.js.map +1 -0
- package/dist/browser/logging.d.ts +10 -0
- package/dist/browser/logging.d.ts.map +1 -0
- package/dist/browser/logging.js +29 -0
- package/dist/browser/logging.js.map +1 -0
- package/dist/browser/lru-cache.d.ts +17 -0
- package/dist/browser/lru-cache.d.ts.map +1 -0
- package/dist/browser/lru-cache.js +50 -0
- package/dist/browser/lru-cache.js.map +1 -0
- package/dist/browser/merge-segment-loaders.d.ts +39 -0
- package/dist/browser/merge-segment-loaders.d.ts.map +1 -0
- package/dist/browser/merge-segment-loaders.js +102 -0
- package/dist/browser/merge-segment-loaders.js.map +1 -0
- package/dist/browser/navigation-bridge.d.ts +102 -0
- package/dist/browser/navigation-bridge.d.ts.map +1 -0
- package/dist/browser/navigation-bridge.js +708 -0
- package/dist/browser/navigation-bridge.js.map +1 -0
- package/dist/browser/navigation-client.d.ts +25 -0
- package/dist/browser/navigation-client.d.ts.map +1 -0
- package/dist/browser/navigation-client.js +157 -0
- package/dist/browser/navigation-client.js.map +1 -0
- package/dist/browser/navigation-store.d.ts +101 -0
- package/dist/browser/navigation-store.d.ts.map +1 -0
- package/dist/browser/navigation-store.js +625 -0
- package/dist/browser/navigation-store.js.map +1 -0
- package/dist/browser/partial-update.d.ts +75 -0
- package/dist/browser/partial-update.d.ts.map +1 -0
- package/dist/browser/partial-update.js +426 -0
- package/dist/browser/partial-update.js.map +1 -0
- package/dist/browser/react/Link.d.ts +86 -0
- package/dist/browser/react/Link.d.ts.map +1 -0
- package/dist/browser/react/Link.js +128 -0
- package/dist/browser/react/Link.js.map +1 -0
- package/dist/browser/react/NavigationProvider.d.ts +63 -0
- package/dist/browser/react/NavigationProvider.d.ts.map +1 -0
- package/dist/browser/react/NavigationProvider.js +216 -0
- package/dist/browser/react/NavigationProvider.js.map +1 -0
- package/dist/browser/react/ScrollRestoration.d.ts +75 -0
- package/dist/browser/react/ScrollRestoration.d.ts.map +1 -0
- package/dist/browser/react/ScrollRestoration.js +57 -0
- package/dist/browser/react/ScrollRestoration.js.map +1 -0
- package/dist/browser/react/context.d.ts +46 -0
- package/dist/browser/react/context.d.ts.map +1 -0
- package/dist/browser/react/context.js +10 -0
- package/dist/browser/react/context.js.map +1 -0
- package/dist/browser/react/index.d.ts +11 -0
- package/dist/browser/react/index.d.ts.map +1 -0
- package/dist/browser/react/index.js +22 -0
- package/dist/browser/react/index.js.map +1 -0
- package/dist/browser/react/location-state-shared.d.ts +63 -0
- package/dist/browser/react/location-state-shared.d.ts.map +1 -0
- package/dist/browser/react/location-state-shared.js +81 -0
- package/dist/browser/react/location-state-shared.js.map +1 -0
- package/dist/browser/react/location-state.d.ts +23 -0
- package/dist/browser/react/location-state.d.ts.map +1 -0
- package/dist/browser/react/location-state.js +29 -0
- package/dist/browser/react/location-state.js.map +1 -0
- package/dist/browser/react/mount-context.d.ts +24 -0
- package/dist/browser/react/mount-context.d.ts.map +1 -0
- package/dist/browser/react/mount-context.js +24 -0
- package/dist/browser/react/mount-context.js.map +1 -0
- package/dist/browser/react/use-action.d.ts +64 -0
- package/dist/browser/react/use-action.d.ts.map +1 -0
- package/dist/browser/react/use-action.js +134 -0
- package/dist/browser/react/use-action.js.map +1 -0
- package/dist/browser/react/use-client-cache.d.ts +41 -0
- package/dist/browser/react/use-client-cache.d.ts.map +1 -0
- package/dist/browser/react/use-client-cache.js +39 -0
- package/dist/browser/react/use-client-cache.js.map +1 -0
- package/dist/browser/react/use-handle.d.ts +31 -0
- package/dist/browser/react/use-handle.d.ts.map +1 -0
- package/dist/browser/react/use-handle.js +144 -0
- package/dist/browser/react/use-handle.js.map +1 -0
- package/dist/browser/react/use-href.d.ts +33 -0
- package/dist/browser/react/use-href.d.ts.map +1 -0
- package/dist/browser/react/use-href.js +39 -0
- package/dist/browser/react/use-href.js.map +1 -0
- package/dist/browser/react/use-link-status.d.ts +37 -0
- package/dist/browser/react/use-link-status.d.ts.map +1 -0
- package/dist/browser/react/use-link-status.js +99 -0
- package/dist/browser/react/use-link-status.js.map +1 -0
- package/dist/browser/react/use-mount.d.ts +25 -0
- package/dist/browser/react/use-mount.d.ts.map +1 -0
- package/dist/browser/react/use-mount.js +30 -0
- package/dist/browser/react/use-mount.js.map +1 -0
- package/dist/browser/react/use-navigation.d.ts +27 -0
- package/dist/browser/react/use-navigation.d.ts.map +1 -0
- package/dist/browser/react/use-navigation.js +87 -0
- package/dist/browser/react/use-navigation.js.map +1 -0
- package/dist/browser/react/use-segments.d.ts +38 -0
- package/dist/browser/react/use-segments.d.ts.map +1 -0
- package/dist/browser/react/use-segments.js +130 -0
- package/dist/browser/react/use-segments.js.map +1 -0
- package/dist/browser/request-controller.d.ts +26 -0
- package/dist/browser/request-controller.d.ts.map +1 -0
- package/dist/browser/request-controller.js +147 -0
- package/dist/browser/request-controller.js.map +1 -0
- package/dist/browser/rsc-router.d.ts +129 -0
- package/dist/browser/rsc-router.d.ts.map +1 -0
- package/dist/browser/rsc-router.js +195 -0
- package/dist/browser/rsc-router.js.map +1 -0
- package/dist/browser/scroll-restoration.d.ts +93 -0
- package/dist/browser/scroll-restoration.d.ts.map +1 -0
- package/dist/browser/scroll-restoration.js +321 -0
- package/dist/browser/scroll-restoration.js.map +1 -0
- package/dist/browser/segment-structure-assert.d.ts +17 -0
- package/dist/browser/segment-structure-assert.d.ts.map +1 -0
- package/dist/browser/segment-structure-assert.js +59 -0
- package/dist/browser/segment-structure-assert.js.map +1 -0
- package/dist/browser/server-action-bridge.d.ts +26 -0
- package/dist/browser/server-action-bridge.d.ts.map +1 -0
- package/dist/browser/server-action-bridge.js +668 -0
- package/dist/browser/server-action-bridge.js.map +1 -0
- package/dist/browser/shallow.d.ts +12 -0
- package/dist/browser/shallow.d.ts.map +1 -0
- package/dist/browser/shallow.js +34 -0
- package/dist/browser/shallow.js.map +1 -0
- package/dist/browser/types.d.ts +369 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/build/__tests__/generate-cli.test.d.ts +2 -0
- package/dist/build/__tests__/generate-cli.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-cli.test.js +237 -0
- package/dist/build/__tests__/generate-cli.test.js.map +1 -0
- package/dist/build/__tests__/generate-manifest.test.d.ts +2 -0
- package/dist/build/__tests__/generate-manifest.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-manifest.test.js +119 -0
- package/dist/build/__tests__/generate-manifest.test.js.map +1 -0
- package/dist/build/__tests__/generate-route-types.test.d.ts +2 -0
- package/dist/build/__tests__/generate-route-types.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-route-types.test.js +620 -0
- package/dist/build/__tests__/generate-route-types.test.js.map +1 -0
- package/dist/build/__tests__/per-router-manifest.test.d.ts +2 -0
- package/dist/build/__tests__/per-router-manifest.test.d.ts.map +1 -0
- package/dist/build/__tests__/per-router-manifest.test.js +308 -0
- package/dist/build/__tests__/per-router-manifest.test.js.map +1 -0
- package/dist/build/generate-manifest.d.ts +81 -0
- package/dist/build/generate-manifest.d.ts.map +1 -0
- package/dist/build/generate-manifest.js +276 -0
- package/dist/build/generate-manifest.js.map +1 -0
- package/dist/build/generate-route-types.d.ts +115 -0
- package/dist/build/generate-route-types.d.ts.map +1 -0
- package/dist/build/generate-route-types.js +740 -0
- package/dist/build/generate-route-types.js.map +1 -0
- package/dist/build/index.d.ts +21 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +21 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/route-trie.d.ts +71 -0
- package/dist/build/route-trie.d.ts.map +1 -0
- package/dist/build/route-trie.js +175 -0
- package/dist/build/route-trie.js.map +1 -0
- package/dist/cache/__tests__/cache-scope.test.d.ts +2 -0
- package/dist/cache/__tests__/cache-scope.test.d.ts.map +1 -0
- package/dist/cache/__tests__/cache-scope.test.js +208 -0
- package/dist/cache/__tests__/cache-scope.test.js.map +1 -0
- package/dist/cache/__tests__/document-cache.test.d.ts +2 -0
- package/dist/cache/__tests__/document-cache.test.d.ts.map +1 -0
- package/dist/cache/__tests__/document-cache.test.js +345 -0
- package/dist/cache/__tests__/document-cache.test.js.map +1 -0
- package/dist/cache/__tests__/memory-segment-store.test.d.ts +2 -0
- package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +1 -0
- package/dist/cache/__tests__/memory-segment-store.test.js +425 -0
- package/dist/cache/__tests__/memory-segment-store.test.js.map +1 -0
- package/dist/cache/__tests__/memory-store.test.d.ts +2 -0
- package/dist/cache/__tests__/memory-store.test.d.ts.map +1 -0
- package/dist/cache/__tests__/memory-store.test.js +367 -0
- package/dist/cache/__tests__/memory-store.test.js.map +1 -0
- package/dist/cache/cache-scope.d.ts +102 -0
- package/dist/cache/cache-scope.d.ts.map +1 -0
- package/dist/cache/cache-scope.js +440 -0
- package/dist/cache/cache-scope.js.map +1 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +2 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +1 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.js +330 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +1 -0
- package/dist/cache/cf/cf-cache-store.d.ts +165 -0
- package/dist/cache/cf/cf-cache-store.d.ts.map +1 -0
- package/dist/cache/cf/cf-cache-store.js +242 -0
- package/dist/cache/cf/cf-cache-store.js.map +1 -0
- package/dist/cache/cf/index.d.ts +14 -0
- package/dist/cache/cf/index.d.ts.map +1 -0
- package/dist/cache/cf/index.js +17 -0
- package/dist/cache/cf/index.js.map +1 -0
- package/dist/cache/document-cache.d.ts +64 -0
- package/dist/cache/document-cache.d.ts.map +1 -0
- package/dist/cache/document-cache.js +228 -0
- package/dist/cache/document-cache.js.map +1 -0
- package/dist/cache/index.d.ts +19 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +21 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/memory-segment-store.d.ts +110 -0
- package/dist/cache/memory-segment-store.d.ts.map +1 -0
- package/dist/cache/memory-segment-store.js +117 -0
- package/dist/cache/memory-segment-store.js.map +1 -0
- package/dist/cache/memory-store.d.ts +41 -0
- package/dist/cache/memory-store.d.ts.map +1 -0
- package/dist/cache/memory-store.js +191 -0
- package/dist/cache/memory-store.js.map +1 -0
- package/dist/cache/types.d.ts +317 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +12 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/client.d.ts +248 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +367 -0
- package/dist/client.js.map +1 -0
- package/dist/client.rsc.d.ts +26 -0
- package/dist/client.rsc.d.ts.map +1 -0
- package/dist/client.rsc.js +46 -0
- package/dist/client.rsc.js.map +1 -0
- package/dist/component-utils.d.ts +36 -0
- package/dist/component-utils.d.ts.map +1 -0
- package/dist/component-utils.js +61 -0
- package/dist/component-utils.js.map +1 -0
- package/dist/components/DefaultDocument.d.ts +13 -0
- package/dist/components/DefaultDocument.d.ts.map +1 -0
- package/dist/components/DefaultDocument.js +15 -0
- package/dist/components/DefaultDocument.js.map +1 -0
- package/dist/debug.d.ts +58 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +157 -0
- package/dist/debug.js.map +1 -0
- package/dist/default-error-boundary.d.ts +11 -0
- package/dist/default-error-boundary.d.ts.map +1 -0
- package/dist/default-error-boundary.js +45 -0
- package/dist/default-error-boundary.js.map +1 -0
- package/dist/deps/browser.d.ts +2 -0
- package/dist/deps/browser.d.ts.map +1 -0
- package/dist/deps/browser.js +3 -0
- package/dist/deps/browser.js.map +1 -0
- package/dist/deps/html-stream-client.d.ts +2 -0
- package/dist/deps/html-stream-client.d.ts.map +1 -0
- package/dist/deps/html-stream-client.js +3 -0
- package/dist/deps/html-stream-client.js.map +1 -0
- package/dist/deps/html-stream-server.d.ts +2 -0
- package/dist/deps/html-stream-server.d.ts.map +1 -0
- package/dist/deps/html-stream-server.js +3 -0
- package/dist/deps/html-stream-server.js.map +1 -0
- package/dist/deps/rsc.d.ts +2 -0
- package/dist/deps/rsc.d.ts.map +1 -0
- package/dist/deps/rsc.js +4 -0
- package/dist/deps/rsc.js.map +1 -0
- package/dist/deps/ssr.d.ts +2 -0
- package/dist/deps/ssr.d.ts.map +1 -0
- package/dist/deps/ssr.js +3 -0
- package/dist/deps/ssr.js.map +1 -0
- package/dist/errors.d.ts +174 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +241 -0
- package/dist/errors.js.map +1 -0
- package/dist/handle.d.ts +78 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +82 -0
- package/dist/handle.js.map +1 -0
- package/dist/handles/MetaTags.d.ts +14 -0
- package/dist/handles/MetaTags.d.ts.map +1 -0
- package/dist/handles/MetaTags.js +136 -0
- package/dist/handles/MetaTags.js.map +1 -0
- package/{src/handles/index.ts → dist/handles/index.d.ts} +1 -2
- package/dist/handles/index.d.ts.map +1 -0
- package/dist/handles/index.js +6 -0
- package/dist/handles/index.js.map +1 -0
- package/dist/handles/meta.d.ts +39 -0
- package/dist/handles/meta.d.ts.map +1 -0
- package/dist/handles/meta.js +202 -0
- package/dist/handles/meta.js.map +1 -0
- package/dist/host/__tests__/errors.test.d.ts +2 -0
- package/dist/host/__tests__/errors.test.d.ts.map +1 -0
- package/dist/host/__tests__/errors.test.js +76 -0
- package/dist/host/__tests__/errors.test.js.map +1 -0
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts +2 -0
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +1 -0
- package/dist/host/__tests__/pattern-comprehensive.test.js +732 -0
- package/dist/host/__tests__/pattern-comprehensive.test.js.map +1 -0
- package/dist/host/__tests__/pattern-matcher.test.d.ts +2 -0
- package/dist/host/__tests__/pattern-matcher.test.d.ts.map +1 -0
- package/dist/host/__tests__/pattern-matcher.test.js +251 -0
- package/dist/host/__tests__/pattern-matcher.test.js.map +1 -0
- package/dist/host/__tests__/router.test.d.ts +2 -0
- package/dist/host/__tests__/router.test.d.ts.map +1 -0
- package/dist/host/__tests__/router.test.js +241 -0
- package/dist/host/__tests__/router.test.js.map +1 -0
- package/dist/host/__tests__/testing.test.d.ts +2 -0
- package/dist/host/__tests__/testing.test.d.ts.map +1 -0
- package/dist/host/__tests__/testing.test.js +64 -0
- package/dist/host/__tests__/testing.test.js.map +1 -0
- package/dist/host/__tests__/utils.test.d.ts +2 -0
- package/dist/host/__tests__/utils.test.d.ts.map +1 -0
- package/dist/host/__tests__/utils.test.js +29 -0
- package/dist/host/__tests__/utils.test.js.map +1 -0
- package/dist/host/cookie-handler.d.ts +34 -0
- package/dist/host/cookie-handler.d.ts.map +1 -0
- package/dist/host/cookie-handler.js +124 -0
- package/dist/host/cookie-handler.js.map +1 -0
- package/dist/host/errors.d.ts +56 -0
- package/dist/host/errors.d.ts.map +1 -0
- package/dist/host/errors.js +79 -0
- package/dist/host/errors.js.map +1 -0
- package/dist/host/index.d.ts +29 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +32 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/pattern-matcher.d.ts +36 -0
- package/dist/host/pattern-matcher.d.ts.map +1 -0
- package/dist/host/pattern-matcher.js +172 -0
- package/dist/host/pattern-matcher.js.map +1 -0
- package/dist/host/router.d.ts +26 -0
- package/dist/host/router.d.ts.map +1 -0
- package/dist/host/router.js +218 -0
- package/dist/host/router.js.map +1 -0
- package/dist/host/testing.d.ts +36 -0
- package/dist/host/testing.d.ts.map +1 -0
- package/dist/host/testing.js +55 -0
- package/dist/host/testing.js.map +1 -0
- package/dist/host/types.d.ts +115 -0
- package/dist/host/types.d.ts.map +1 -0
- package/dist/host/types.js +7 -0
- package/dist/host/types.js.map +1 -0
- package/dist/host/utils.d.ts +21 -0
- package/dist/host/utils.d.ts.map +1 -0
- package/dist/host/utils.js +23 -0
- package/dist/host/utils.js.map +1 -0
- package/dist/href-client.d.ts +131 -0
- package/dist/href-client.d.ts.map +1 -0
- package/dist/href-client.js +64 -0
- package/dist/href-client.js.map +1 -0
- package/dist/href-context.d.ts +29 -0
- package/dist/href-context.d.ts.map +1 -0
- package/dist/href-context.js +21 -0
- package/dist/href-context.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/index.rsc.d.ts +32 -0
- package/dist/index.rsc.d.ts.map +1 -0
- package/dist/index.rsc.js +40 -0
- package/dist/index.rsc.js.map +1 -0
- package/dist/internal-debug.d.ts +2 -0
- package/dist/internal-debug.d.ts.map +1 -0
- package/dist/internal-debug.js +5 -0
- package/dist/internal-debug.js.map +1 -0
- package/dist/loader.d.ts +14 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +20 -0
- package/dist/loader.js.map +1 -0
- package/dist/loader.rsc.d.ts +19 -0
- package/dist/loader.rsc.d.ts.map +1 -0
- package/dist/loader.rsc.js +99 -0
- package/dist/loader.rsc.js.map +1 -0
- package/dist/network-error-thrower.d.ts +17 -0
- package/dist/network-error-thrower.d.ts.map +1 -0
- package/dist/network-error-thrower.js +14 -0
- package/dist/network-error-thrower.js.map +1 -0
- package/dist/outlet-context.d.ts +13 -0
- package/dist/outlet-context.d.ts.map +1 -0
- package/dist/outlet-context.js +3 -0
- package/dist/outlet-context.js.map +1 -0
- package/dist/prerender/__tests__/param-hash.test.d.ts +2 -0
- package/dist/prerender/__tests__/param-hash.test.d.ts.map +1 -0
- package/dist/prerender/__tests__/param-hash.test.js +148 -0
- package/dist/prerender/__tests__/param-hash.test.js.map +1 -0
- package/dist/prerender/param-hash.d.ts +16 -0
- package/dist/prerender/param-hash.d.ts.map +1 -0
- package/dist/prerender/param-hash.js +36 -0
- package/dist/prerender/param-hash.js.map +1 -0
- package/dist/prerender/store.d.ts +38 -0
- package/dist/prerender/store.d.ts.map +1 -0
- package/dist/prerender/store.js +61 -0
- package/dist/prerender/store.js.map +1 -0
- package/dist/prerender.d.ts +66 -0
- package/dist/prerender.d.ts.map +1 -0
- package/dist/prerender.js +57 -0
- package/dist/prerender.js.map +1 -0
- package/dist/reverse.d.ts +196 -0
- package/dist/reverse.d.ts.map +1 -0
- package/dist/reverse.js +78 -0
- package/dist/reverse.js.map +1 -0
- package/dist/root-error-boundary.d.ts +33 -0
- package/dist/root-error-boundary.d.ts.map +1 -0
- package/dist/root-error-boundary.js +165 -0
- package/dist/root-error-boundary.js.map +1 -0
- package/dist/route-content-wrapper.d.ts +46 -0
- package/dist/route-content-wrapper.d.ts.map +1 -0
- package/dist/route-content-wrapper.js +77 -0
- package/dist/route-content-wrapper.js.map +1 -0
- package/dist/route-definition.d.ts +421 -0
- package/dist/route-definition.d.ts.map +1 -0
- package/dist/route-definition.js +868 -0
- package/dist/route-definition.js.map +1 -0
- package/dist/route-map-builder.d.ts +155 -0
- package/dist/route-map-builder.d.ts.map +1 -0
- package/dist/route-map-builder.js +237 -0
- package/dist/route-map-builder.js.map +1 -0
- package/dist/route-types.d.ts +165 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +7 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router/__tests__/handler-context.test.d.ts +2 -0
- package/dist/router/__tests__/handler-context.test.d.ts.map +1 -0
- package/dist/router/__tests__/handler-context.test.js +65 -0
- package/dist/router/__tests__/handler-context.test.js.map +1 -0
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts +2 -0
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +1 -0
- package/dist/router/__tests__/loader-cycle-detection.test.js +221 -0
- package/dist/router/__tests__/loader-cycle-detection.test.js.map +1 -0
- package/dist/router/__tests__/match-context.test.d.ts +2 -0
- package/dist/router/__tests__/match-context.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-context.test.js +92 -0
- package/dist/router/__tests__/match-context.test.js.map +1 -0
- package/dist/router/__tests__/match-pipelines.test.d.ts +2 -0
- package/dist/router/__tests__/match-pipelines.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-pipelines.test.js +417 -0
- package/dist/router/__tests__/match-pipelines.test.js.map +1 -0
- package/dist/router/__tests__/match-result.test.d.ts +2 -0
- package/dist/router/__tests__/match-result.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-result.test.js +457 -0
- package/dist/router/__tests__/match-result.test.js.map +1 -0
- package/dist/router/__tests__/on-error.test.d.ts +2 -0
- package/dist/router/__tests__/on-error.test.d.ts.map +1 -0
- package/dist/router/__tests__/on-error.test.js +678 -0
- package/dist/router/__tests__/on-error.test.js.map +1 -0
- package/dist/router/__tests__/pattern-matching.test.d.ts +2 -0
- package/dist/router/__tests__/pattern-matching.test.d.ts.map +1 -0
- package/dist/router/__tests__/pattern-matching.test.js +629 -0
- package/dist/router/__tests__/pattern-matching.test.js.map +1 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +2 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +1 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +155 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +1 -0
- package/dist/router/error-handling.d.ts +77 -0
- package/dist/router/error-handling.d.ts.map +1 -0
- package/dist/router/error-handling.js +202 -0
- package/dist/router/error-handling.js.map +1 -0
- package/dist/router/handler-context.d.ts +20 -0
- package/dist/router/handler-context.d.ts.map +1 -0
- package/dist/router/handler-context.js +198 -0
- package/dist/router/handler-context.js.map +1 -0
- package/dist/router/intercept-resolution.d.ts +66 -0
- package/dist/router/intercept-resolution.d.ts.map +1 -0
- package/dist/router/intercept-resolution.js +246 -0
- package/dist/router/intercept-resolution.js.map +1 -0
- package/dist/router/loader-resolution.d.ts +64 -0
- package/dist/router/loader-resolution.d.ts.map +1 -0
- package/dist/router/loader-resolution.js +284 -0
- package/dist/router/loader-resolution.js.map +1 -0
- package/dist/router/logging.d.ts +15 -0
- package/dist/router/logging.d.ts.map +1 -0
- package/dist/router/logging.js +99 -0
- package/dist/router/logging.js.map +1 -0
- package/dist/router/manifest.d.ts +22 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/manifest.js +181 -0
- package/dist/router/manifest.js.map +1 -0
- package/dist/router/match-api.d.ts +35 -0
- package/dist/router/match-api.d.ts.map +1 -0
- package/dist/router/match-api.js +406 -0
- package/dist/router/match-api.js.map +1 -0
- package/dist/router/match-context.d.ts +206 -0
- package/dist/router/match-context.d.ts.map +1 -0
- package/dist/router/match-context.js +17 -0
- package/dist/router/match-context.js.map +1 -0
- package/dist/router/match-middleware/background-revalidation.d.ts +127 -0
- package/dist/router/match-middleware/background-revalidation.d.ts.map +1 -0
- package/dist/router/match-middleware/background-revalidation.js +75 -0
- package/dist/router/match-middleware/background-revalidation.js.map +1 -0
- package/dist/router/match-middleware/cache-lookup.d.ts +112 -0
- package/dist/router/match-middleware/cache-lookup.d.ts.map +1 -0
- package/dist/router/match-middleware/cache-lookup.js +257 -0
- package/dist/router/match-middleware/cache-lookup.js.map +1 -0
- package/dist/router/match-middleware/cache-store.d.ts +113 -0
- package/dist/router/match-middleware/cache-store.d.ts.map +1 -0
- package/dist/router/match-middleware/cache-store.js +108 -0
- package/dist/router/match-middleware/cache-store.js.map +1 -0
- package/dist/router/match-middleware/index.d.ts +81 -0
- package/dist/router/match-middleware/index.d.ts.map +1 -0
- package/dist/router/match-middleware/index.js +80 -0
- package/dist/router/match-middleware/index.js.map +1 -0
- package/dist/router/match-middleware/intercept-resolution.d.ts +117 -0
- package/dist/router/match-middleware/intercept-resolution.d.ts.map +1 -0
- package/dist/router/match-middleware/intercept-resolution.js +134 -0
- package/dist/router/match-middleware/intercept-resolution.js.map +1 -0
- package/dist/router/match-middleware/segment-resolution.d.ts +99 -0
- package/dist/router/match-middleware/segment-resolution.d.ts.map +1 -0
- package/dist/router/match-middleware/segment-resolution.js +53 -0
- package/dist/router/match-middleware/segment-resolution.js.map +1 -0
- package/dist/router/match-pipelines.d.ts +147 -0
- package/dist/router/match-pipelines.d.ts.map +1 -0
- package/dist/router/match-pipelines.js +82 -0
- package/dist/router/match-pipelines.js.map +1 -0
- package/dist/router/match-result.d.ts +126 -0
- package/dist/router/match-result.d.ts.map +1 -0
- package/dist/router/match-result.js +93 -0
- package/dist/router/match-result.js.map +1 -0
- package/dist/router/metrics.d.ts +20 -0
- package/dist/router/metrics.d.ts.map +1 -0
- package/dist/router/metrics.js +47 -0
- package/dist/router/metrics.js.map +1 -0
- package/dist/router/middleware.d.ts +249 -0
- package/dist/router/middleware.d.ts.map +1 -0
- package/dist/router/middleware.js +434 -0
- package/dist/router/middleware.js.map +1 -0
- package/dist/router/middleware.test.d.ts +2 -0
- package/dist/router/middleware.test.d.ts.map +1 -0
- package/dist/router/middleware.test.js +816 -0
- package/dist/router/middleware.test.js.map +1 -0
- package/dist/router/pattern-matching.d.ts +149 -0
- package/dist/router/pattern-matching.d.ts.map +1 -0
- package/dist/router/pattern-matching.js +349 -0
- package/dist/router/pattern-matching.js.map +1 -0
- package/dist/router/revalidation.d.ts +44 -0
- package/dist/router/revalidation.d.ts.map +1 -0
- package/dist/router/revalidation.js +147 -0
- package/dist/router/revalidation.js.map +1 -0
- package/dist/router/router-context.d.ts +135 -0
- package/dist/router/router-context.d.ts.map +1 -0
- package/dist/router/router-context.js +36 -0
- package/dist/router/router-context.js.map +1 -0
- package/dist/router/segment-resolution.d.ts +127 -0
- package/dist/router/segment-resolution.d.ts.map +1 -0
- package/dist/router/segment-resolution.js +919 -0
- package/dist/router/segment-resolution.js.map +1 -0
- package/dist/router/trie-matching.d.ts +40 -0
- package/dist/router/trie-matching.d.ts.map +1 -0
- package/dist/router/trie-matching.js +127 -0
- package/dist/router/trie-matching.js.map +1 -0
- package/dist/router/types.d.ts +136 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +7 -0
- package/dist/router/types.js.map +1 -0
- package/dist/router.d.ts +753 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.gen.d.ts +6 -0
- package/dist/router.gen.d.ts.map +1 -0
- package/dist/router.gen.js +6 -0
- package/dist/router.gen.js.map +1 -0
- package/dist/router.js +1304 -0
- package/dist/router.js.map +1 -0
- package/dist/rsc/__tests__/helpers.test.d.ts +2 -0
- package/dist/rsc/__tests__/helpers.test.d.ts.map +1 -0
- package/dist/rsc/__tests__/helpers.test.js +140 -0
- package/dist/rsc/__tests__/helpers.test.js.map +1 -0
- package/dist/rsc/handler.d.ts +45 -0
- package/dist/rsc/handler.d.ts.map +1 -0
- package/dist/rsc/handler.js +1172 -0
- package/dist/rsc/handler.js.map +1 -0
- package/dist/rsc/helpers.d.ts +16 -0
- package/dist/rsc/helpers.d.ts.map +1 -0
- package/dist/rsc/helpers.js +55 -0
- package/dist/rsc/helpers.js.map +1 -0
- package/dist/rsc/index.d.ts +22 -0
- package/dist/rsc/index.d.ts.map +1 -0
- package/dist/rsc/index.js +23 -0
- package/dist/rsc/index.js.map +1 -0
- package/dist/rsc/nonce.d.ts +9 -0
- package/dist/rsc/nonce.d.ts.map +1 -0
- package/dist/rsc/nonce.js +18 -0
- package/dist/rsc/nonce.js.map +1 -0
- package/dist/rsc/types.d.ts +206 -0
- package/dist/rsc/types.d.ts.map +1 -0
- package/dist/rsc/types.js +8 -0
- package/dist/rsc/types.js.map +1 -0
- package/dist/search-params.d.ts +103 -0
- package/dist/search-params.d.ts.map +1 -0
- package/dist/search-params.js +74 -0
- package/dist/search-params.js.map +1 -0
- package/dist/segment-system.d.ts +75 -0
- package/dist/segment-system.d.ts.map +1 -0
- package/dist/segment-system.js +336 -0
- package/dist/segment-system.js.map +1 -0
- package/dist/server/context.d.ts +245 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +197 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/fetchable-loader-store.d.ts +18 -0
- package/dist/server/fetchable-loader-store.d.ts.map +1 -0
- package/dist/server/fetchable-loader-store.js +18 -0
- package/dist/server/fetchable-loader-store.js.map +1 -0
- package/dist/server/handle-store.d.ts +85 -0
- package/dist/server/handle-store.d.ts.map +1 -0
- package/dist/server/handle-store.js +142 -0
- package/dist/server/handle-store.js.map +1 -0
- package/dist/server/loader-registry.d.ts +55 -0
- package/dist/server/loader-registry.d.ts.map +1 -0
- package/dist/server/loader-registry.js +132 -0
- package/dist/server/loader-registry.js.map +1 -0
- package/dist/server/request-context.d.ts +226 -0
- package/dist/server/request-context.d.ts.map +1 -0
- package/dist/server/request-context.js +290 -0
- package/dist/server/request-context.js.map +1 -0
- package/dist/server/root-layout.d.ts +4 -0
- package/dist/server/root-layout.d.ts.map +1 -0
- package/dist/server/root-layout.js +5 -0
- package/dist/server/root-layout.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +20 -0
- package/dist/server.js.map +1 -0
- package/dist/ssr/__tests__/ssr-handler.test.d.ts +2 -0
- package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +1 -0
- package/dist/ssr/__tests__/ssr-handler.test.js +132 -0
- package/dist/ssr/__tests__/ssr-handler.test.js.map +1 -0
- package/dist/ssr/index.d.ts +98 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +158 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/static-handler.d.ts +50 -0
- package/dist/static-handler.d.ts.map +1 -0
- package/dist/static-handler.gen.d.ts +5 -0
- package/dist/static-handler.gen.d.ts.map +1 -0
- package/dist/static-handler.gen.js +5 -0
- package/dist/static-handler.gen.js.map +1 -0
- package/dist/static-handler.js +29 -0
- package/dist/static-handler.js.map +1 -0
- package/dist/theme/ThemeProvider.d.ts +20 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +240 -0
- package/dist/theme/ThemeProvider.js.map +1 -0
- package/dist/theme/ThemeScript.d.ts +48 -0
- package/dist/theme/ThemeScript.d.ts.map +1 -0
- package/dist/theme/ThemeScript.js +13 -0
- package/dist/theme/ThemeScript.js.map +1 -0
- package/dist/theme/__tests__/theme.test.d.ts +2 -0
- package/dist/theme/__tests__/theme.test.d.ts.map +1 -0
- package/dist/theme/__tests__/theme.test.js +103 -0
- package/dist/theme/__tests__/theme.test.js.map +1 -0
- package/dist/theme/constants.d.ts +29 -0
- package/dist/theme/constants.d.ts.map +1 -0
- package/dist/theme/constants.js +48 -0
- package/dist/theme/constants.js.map +1 -0
- package/dist/theme/index.d.ts +31 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +36 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/theme-context.d.ts +40 -0
- package/dist/theme/theme-context.d.ts.map +1 -0
- package/dist/theme/theme-context.js +60 -0
- package/dist/theme/theme-context.js.map +1 -0
- package/dist/theme/theme-script.d.ts +27 -0
- package/dist/theme/theme-script.d.ts.map +1 -0
- package/dist/theme/theme-script.js +147 -0
- package/dist/theme/theme-script.js.map +1 -0
- package/dist/theme/types.d.ts +163 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +11 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/theme/use-theme.d.ts +12 -0
- package/dist/theme/use-theme.d.ts.map +1 -0
- package/dist/theme/use-theme.js +40 -0
- package/dist/theme/use-theme.js.map +1 -0
- package/dist/types.d.ts +1479 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/urls.d.ts +441 -0
- package/dist/urls.d.ts.map +1 -0
- package/dist/urls.gen.d.ts +8 -0
- package/dist/urls.gen.d.ts.map +1 -0
- package/dist/urls.gen.js +8 -0
- package/dist/urls.gen.js.map +1 -0
- package/dist/urls.js +443 -0
- package/dist/urls.js.map +1 -0
- package/dist/use-loader.d.ts +127 -0
- package/dist/use-loader.d.ts.map +1 -0
- package/dist/use-loader.js +237 -0
- package/dist/use-loader.js.map +1 -0
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts +2 -0
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +1 -0
- package/dist/vite/__tests__/ast-handler-extract.test.js +294 -0
- package/dist/vite/__tests__/ast-handler-extract.test.js.map +1 -0
- package/dist/vite/__tests__/expose-id-utils.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-id-utils.test.js +224 -0
- package/dist/vite/__tests__/expose-id-utils.test.js.map +1 -0
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-internal-ids.test.js +647 -0
- package/dist/vite/__tests__/expose-internal-ids.test.js.map +1 -0
- package/dist/vite/__tests__/expose-router-id.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-router-id.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-router-id.test.js +39 -0
- package/dist/vite/__tests__/expose-router-id.test.js.map +1 -0
- package/dist/vite/ast-handler-extract.d.ts +49 -0
- package/dist/vite/ast-handler-extract.d.ts.map +1 -0
- package/dist/vite/ast-handler-extract.js +249 -0
- package/dist/vite/ast-handler-extract.js.map +1 -0
- package/dist/vite/expose-action-id.d.ts +19 -0
- package/dist/vite/expose-action-id.d.ts.map +1 -0
- package/dist/vite/expose-action-id.js +250 -0
- package/dist/vite/expose-action-id.js.map +1 -0
- package/dist/vite/expose-id-utils.d.ts +69 -0
- package/dist/vite/expose-id-utils.d.ts.map +1 -0
- package/dist/vite/expose-id-utils.js +289 -0
- package/dist/vite/expose-id-utils.js.map +1 -0
- package/dist/vite/expose-internal-ids.d.ts +22 -0
- package/dist/vite/expose-internal-ids.d.ts.map +1 -0
- package/dist/vite/expose-internal-ids.js +886 -0
- package/dist/vite/expose-internal-ids.js.map +1 -0
- package/dist/vite/index.d.ts +149 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +307 -26
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/index.named-routes.gen.ts +103 -0
- package/dist/vite/package-resolution.d.ts +43 -0
- package/dist/vite/package-resolution.d.ts.map +1 -0
- package/dist/vite/package-resolution.js +112 -0
- package/dist/vite/package-resolution.js.map +1 -0
- package/dist/vite/virtual-entries.d.ts +25 -0
- package/dist/vite/virtual-entries.d.ts.map +1 -0
- package/dist/vite/virtual-entries.js +110 -0
- package/dist/vite/virtual-entries.js.map +1 -0
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/browser/partial-update.ts +22 -1
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/rsc-router.tsx +8 -1
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -4
- package/src/browser/validate-redirect-origin.ts +43 -15
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +43 -11
- package/src/cache/cache-runtime.ts +21 -0
- package/src/cache/document-cache.ts +10 -0
- package/src/cache/profile-registry.ts +6 -34
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +5 -0
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/index.rsc.ts +7 -0
- package/src/index.ts +7 -0
- package/src/redirect-origin.ts +100 -0
- package/src/route-definition/redirect.ts +32 -8
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/router/find-match.ts +0 -1
- package/src/router/loader-resolution.ts +22 -21
- package/src/router/match-middleware/cache-lookup.ts +95 -125
- package/src/router/match-middleware/segment-resolution.ts +0 -1
- package/src/router/middleware-types.ts +0 -1
- package/src/router/middleware.ts +71 -31
- package/src/router/pattern-matching.ts +4 -23
- package/src/router/prerender-match.ts +27 -6
- package/src/router/revalidation.ts +9 -4
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -1
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/loader-cache.ts +25 -10
- package/src/router/segment-resolution/revalidation.ts +185 -243
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/trie-matching.ts +3 -5
- package/src/router.ts +4 -9
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +9 -2
- package/src/rsc/helpers.ts +56 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +10 -4
- package/src/rsc/progressive-enhancement.ts +8 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/server-action.ts +4 -1
- package/src/rsc/types.ts +6 -2
- package/src/server/request-context.ts +22 -13
- package/src/testing/cache-status.ts +44 -1
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.ts +48 -2
- package/src/testing/index.ts +1 -0
- package/src/testing/render-handler.ts +31 -12
- package/src/testing/render-route.tsx +75 -13
- package/src/types/handler-context.ts +28 -1
- package/src/vite/discovery/discover-routers.ts +22 -11
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/plugin-types.ts +32 -8
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -10
- package/src/vite/rango.ts +1 -0
- package/src/vite/router-discovery.ts +7 -0
- package/src/router/middleware-cookies.ts +0 -42
|
@@ -81,6 +81,66 @@ path("/product/:id", async (ctx) => {
|
|
|
81
81
|
Async content is a `Promise<ReactNode>`. Resolve it in your component
|
|
82
82
|
with React's `use()` hook wrapped in `<Suspense>`.
|
|
83
83
|
|
|
84
|
+
### Deferred content (decide now, resolve from a deep component)
|
|
85
|
+
|
|
86
|
+
When the handler should DECIDE to push a crumb (it holds `ctx`, so the decision
|
|
87
|
+
must land before the handles stream seals) but the value is produced far away — by
|
|
88
|
+
a deep async component, not the handler — call `.defer()` on the push function.
|
|
89
|
+
`ctx.use(Handle)` returns the push function; `.defer(options)` reserves the crumb's
|
|
90
|
+
slot synchronously and returns a **resolver that is push-equal** — you call it
|
|
91
|
+
later, anywhere in the render, with the same argument you'd have passed to the
|
|
92
|
+
push (a value, a `Promise`, or a thunk). The only added behavior is a timeout, so a
|
|
93
|
+
forgotten resolve can't hold the Flight stream (and the HTTP response) open forever.
|
|
94
|
+
|
|
95
|
+
Reserve the slot in the handler, then resolve it from a nested async component
|
|
96
|
+
that closes over the resolver — no extra wiring (the resolver is a plain closure,
|
|
97
|
+
not outlet context):
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
101
|
+
import { Outlet } from "@rangojs/router/client";
|
|
102
|
+
import { Suspense } from "react";
|
|
103
|
+
|
|
104
|
+
function DocsLayout(ctx) {
|
|
105
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
106
|
+
// Decide now (the slot is reserved before the stream seals); resolve later.
|
|
107
|
+
const resolveCrumb = breadcrumb.defer({ timeoutMs: 5000, else: null });
|
|
108
|
+
|
|
109
|
+
// Deep, async, far from the handler — closes over the resolver, never touches ctx.
|
|
110
|
+
// Same call shape as breadcrumb({ ... }), just deferred:
|
|
111
|
+
async function LiveCrumb() {
|
|
112
|
+
const n = await countOpenIssues();
|
|
113
|
+
resolveCrumb({ label: "Docs", href: "/docs", content: <span>{n}</span> });
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<Suspense>
|
|
120
|
+
<LiveCrumb />
|
|
121
|
+
</Suspense>
|
|
122
|
+
<Outlet />
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If the resolver is never called, the slot auto-resolves to `else` after
|
|
129
|
+
`timeoutMs` (default 10s) and warns in dev — graceful degradation instead of a
|
|
130
|
+
hung request. `timeoutMs: 0` or `Infinity` disable the timeout intentionally; any
|
|
131
|
+
other non-finite or negative value falls back to the default rather than silently
|
|
132
|
+
disabling the safety net.
|
|
133
|
+
|
|
134
|
+
**Consumer note:** because `.defer()` reserves the slot for the WHOLE item, a
|
|
135
|
+
client reading the handle (`useHandle(Breadcrumbs)`) sees that entry as a
|
|
136
|
+
`Promise` until it resolves. Type such reads with the exported
|
|
137
|
+
`DeferredHandleEntry<BreadcrumbItem>` (from `@rangojs/router/client`); a
|
|
138
|
+
deferred-aware consumer should `use()` thenable entries inside `<Suspense>`, while
|
|
139
|
+
a simple one can skip them (`typeof entry.then === "function"`). Use `.defer()`
|
|
140
|
+
only when even `label`/`href` are unknown at handler time — if you know them and
|
|
141
|
+
only the `content` is async, push a concrete item with a `Promise` `content` field
|
|
142
|
+
instead (no `.defer()` needed).
|
|
143
|
+
|
|
84
144
|
## Consuming Breadcrumbs (Client)
|
|
85
145
|
|
|
86
146
|
Use `useHandle(Breadcrumbs)` in a client component to read the accumulated items:
|
|
@@ -465,7 +465,10 @@ See `/middleware` for the full cross-segment revalidation contract.
|
|
|
465
465
|
|
|
466
466
|
`redirect()` works inside actions. Both `return redirect(...)` and
|
|
467
467
|
`throw redirect(...)` are supported and behave the same way for the
|
|
468
|
-
client. Throwing is clearer when the redirect is conditional
|
|
468
|
+
client. Throwing is clearer when the redirect is conditional, and it keeps
|
|
469
|
+
the action's return type narrow (e.g. `Promise<void>`) — `redirect()` returns
|
|
470
|
+
a `Response`, so the `return` form needs `Promise<Response>` in the signature.
|
|
471
|
+
Prefer `throw redirect(...)`; never cast with `as any`.
|
|
469
472
|
|
|
470
473
|
```typescript
|
|
471
474
|
"use server";
|
|
@@ -490,6 +493,27 @@ Redirects from actions render the **target** route tree's matched segments
|
|
|
490
493
|
source page's — the target is what the user sees next. See `/hooks
|
|
491
494
|
useLocationState` for reading flash state on the target page.
|
|
492
495
|
|
|
496
|
+
### Same-origin by default (open-redirect protection)
|
|
497
|
+
|
|
498
|
+
`redirect()` is same-origin by default on every path — JS, no-JS PE, and
|
|
499
|
+
full-page. A cross-origin target (e.g. from unvalidated user input) is blocked
|
|
500
|
+
and the user is sent to the app root instead, so `redirect(userInput)` can never
|
|
501
|
+
become an open redirect. To intentionally redirect off-host (an OAuth provider,
|
|
502
|
+
say), opt in explicitly:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
throw redirect("https://accounts.google.com/o/oauth2/v2/auth?...", {
|
|
506
|
+
external: true,
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
`{ external: true }` is the audit point: passing user input with it re-opens the
|
|
511
|
+
cross-origin risk and is your responsibility (the Rails `allow_other_host: true`
|
|
512
|
+
model). Omit it and off-host targets stay blocked. `external` only waives the
|
|
513
|
+
**same-origin** rule, not scheme safety: the target must be `http(s)` — a
|
|
514
|
+
`javascript:` or `data:` URL is still neutralized, so a forged or mistaken
|
|
515
|
+
`external` target can never become a scriptable navigation.
|
|
516
|
+
|
|
493
517
|
## Error Handling
|
|
494
518
|
|
|
495
519
|
### Validation errors — return them as state
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -81,23 +81,23 @@ The single rule that drives everything:
|
|
|
81
81
|
|
|
82
82
|
Each primitive links to its sub-file (API + recipe + caveats).
|
|
83
83
|
|
|
84
|
-
| The behavior is…
|
|
85
|
-
|
|
|
86
|
-
| a pure function / `reverse` / `href` / a predicate (`revalidate`, `isAction`)
|
|
87
|
-
| one loader's data logic
|
|
88
|
-
| a loader's cookie / header / redirect output (auth-loader pattern)
|
|
89
|
-
| one middleware's ordering / short-circuit / cookie+header merge
|
|
90
|
-
| a `"use server"` action's cookie / header / flash output (even on `throw redirect()`)
|
|
91
|
-
| a handle's `collect`/accumulator, or a seeded handle read
|
|
92
|
-
| a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`)
|
|
93
|
-
| a redirect / status / headers / cookies / **response route** (json/text/html/xml/md), no Flight
|
|
94
|
-
| a real async **Server Component**
|
|
95
|
-
|
|
|
96
|
-
| a real route **handler** `(ctx) => rsc` (params/loaders/vars -> rendered RSC + effects)
|
|
97
|
-
| navigation, hydration, PE parity, view transitions, real SSR
|
|
98
|
-
| cache hit/miss/stale, prerender (= a cache hit by design)
|
|
99
|
-
| generated route map drift vs runtime
|
|
100
|
-
| a platform binding (`env.DB` / Durable Object / `env.R2`)
|
|
84
|
+
| The behavior is… | Layer | Primitive | Import root |
|
|
85
|
+
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
86
|
+
| a pure function / `reverse` / `href` / a predicate (`revalidate`, `isAction`) | unit + types | [`reverse`/`@ts-expect-error`](./reverse-and-types.md) | `@rangojs/router/testing` |
|
|
87
|
+
| one loader's data logic | unit (node) | [`runLoader`](./loader.md) | `@rangojs/router/testing` |
|
|
88
|
+
| a loader's cookie / header / redirect output (auth-loader pattern) | unit (node) | [`runLoaderResult`](./loader.md) | `@rangojs/router/testing` |
|
|
89
|
+
| one middleware's ordering / short-circuit / cookie+header merge | unit (node) | [`runMiddleware`](./middleware.md) | `@rangojs/router/testing` |
|
|
90
|
+
| a `"use server"` action's cookie / header / flash output (even on `throw redirect()`) | unit (node) | [`runInRequestContext`](./server-actions.md) | `@rangojs/router/testing` |
|
|
91
|
+
| a handle's `collect`/accumulator, or a seeded handle read | unit | [`collectHandle` / seeded `handles`](./handles.md) | `@rangojs/router/testing[/dom]` |
|
|
92
|
+
| a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`) | unit (DOM) | [`renderRoute`](./client-components.md) | `@rangojs/router/testing/dom` |
|
|
93
|
+
| a redirect / status / headers / cookies / **response route** (json/text/html/xml/md), no Flight | integration | [`dispatch`](./response-routes.md) | `@rangojs/router/testing` |
|
|
94
|
+
| a real async **Server Component** (assert what it rendered: typed boundary props, server-rendered host content, inlined-vs-island) | RSC unit | [`renderServerTree` + `findClientBoundaries`/`findElements`](./server-tree.md) | `@rangojs/router/testing/flight` |
|
|
95
|
+
| the exact Flight **wire payload** shape (a drift snapshot) | RSC unit | [`renderToFlightString` + `toMatchFlightSnapshot`](./flight.md) | `@rangojs/router/testing/flight` |
|
|
96
|
+
| a real route **handler** `(ctx) => rsc` (params/loaders/vars -> rendered RSC + effects) | RSC unit | [`renderHandler`](./render-handler.md) | `@rangojs/router/testing/flight` |
|
|
97
|
+
| navigation, hydration, PE parity, view transitions, real SSR | e2e | [`createRangoE2E` -> `parityDescribe`/`expectParity`](./e2e-parity.md) | `@rangojs/router/testing/e2e` |
|
|
98
|
+
| cache hit/miss/stale, prerender (= a cache hit by design) | e2e + signal | [`assertCacheStatus` (header) / `assertCacheDecision` (telemetry sink)](./cache-prerender.md) | `@rangojs/router/testing[/e2e]` |
|
|
99
|
+
| generated route map drift vs runtime | unit (node) | [`assertGeneratedRoutesMatch`](./reverse-and-types.md) | `@rangojs/router/testing` |
|
|
100
|
+
| a platform binding (`env.DB` / Durable Object / `env.R2`) | unit/integr. | [your own double via `env`](./bindings.md) | (any primitive's `env` option) |
|
|
101
101
|
|
|
102
102
|
Cross-references to the DSL skills: `/loader`, `/middleware`, `/server-actions`,
|
|
103
103
|
`/handler-use`, `/hooks`, `/response-routes`, `/route`, `/caching`, `/prerender`,
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
# Testing cache / SWR / prerender — assertCacheStatus
|
|
2
2
|
|
|
3
|
-
**Layer:** e2e + signal · **Import:** the cache-status helpers (`assertCacheStatus`/`parseCacheHeader`/`createCacheSink`/`filterCacheDecisions`) are re-exported from BOTH entries — use `@rangojs/router/testing` from a Vitest unit/integration test, and `@rangojs/router/testing/e2e` from a plain Playwright runner (the e2e barrel avoids the Vite-only virtuals the main barrel pulls in). · **DSL it tests:** `cache()` / `"use cache"` / loader cache / `Prerender(...)` (see `/caching`, `/prerender`, `/use-cache`)
|
|
3
|
+
**Layer:** e2e + signal · **Import:** the cache-status helpers (`assertCacheStatus`/`parseCacheHeader`/`createCacheSink`/`assertCacheDecision`/`filterCacheDecisions`) are re-exported from BOTH entries — use `@rangojs/router/testing` from a Vitest unit/integration test, and `@rangojs/router/testing/e2e` from a plain Playwright runner (the e2e barrel avoids the Vite-only virtuals the main barrel pulls in). · **DSL it tests:** `cache()` / `"use cache"` / loader cache / `Prerender(...)` (see `/caching`, `/prerender`, `/use-cache`)
|
|
4
4
|
|
|
5
5
|
The router's REAL cache pipeline runs (runtime cache, SWR revalidation, prerender lookup); you SEED nothing — you drive a request through the real fetch path and read the resulting cache decision. The decision surfaces two ways: the `X-Rango-Cache` response header (a debug gate) or a captured `cache.decision` telemetry event.
|
|
6
6
|
|
|
7
|
+
## Which path to use
|
|
8
|
+
|
|
9
|
+
Both report the SAME coarse route-level signal (keyed by the route NAME). Pick by **transport**, not by meaning:
|
|
10
|
+
|
|
11
|
+
| Path | Helper | Transport | Needs the debug gate? | Production surface | Per-segment `shouldRevalidate`? |
|
|
12
|
+
| ------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------- | --------------------------------- | ------------------------------- |
|
|
13
|
+
| **Header** | `assertCacheStatus(res, routeKey, expected)` / `parseCacheHeader` | the `X-Rango-Cache` response header — the ONLY signal a black-box Playwright `Response` carries | Yes (`debugCacheSignal` / `RANGO_TEST_SIGNALS=1`) | the header (gated off by default) | no |
|
|
14
|
+
| **Telemetry** | `assertCacheDecision(events, routeKey, expected)` / `filterCacheDecisions` | a captured `cache.decision` event off a `createCacheSink()` sink | No | zero | yes (the only path exposing it) |
|
|
15
|
+
|
|
16
|
+
Use the header path when all you have is a black-box `Response` (a Playwright `APIResponse`); use the telemetry path when you can wire `createRouter({ telemetry: sink })` and want zero production surface or per-segment `shouldRevalidate`. `assertCacheDecision` is the one-call counterpart of `assertCacheStatus` (parallel `(…, routeKey, expected)` shape — captured `events` in place of a `Response`); reach for raw `filterCacheDecisions` only when you need the per-segment event fields directly.
|
|
17
|
+
|
|
7
18
|
## API
|
|
8
19
|
|
|
9
20
|
### Options — `assertCacheStatus(target, segment, expected)`
|
|
@@ -38,7 +49,11 @@ parseCacheHeader(headerValue: string | null | undefined): Record<string, string>
|
|
|
38
49
|
// createCacheSink -> a sink to wire via createRouter({ telemetry: sink }), plus the array it records into.
|
|
39
50
|
createCacheSink(): { sink: TelemetrySink; events: TelemetryEvent[] }
|
|
40
51
|
|
|
41
|
-
//
|
|
52
|
+
// assertCacheDecision -> the one-call telemetry assert (counterpart of assertCacheStatus).
|
|
53
|
+
// Throws on mismatch / no matching segment / unknown routeKey; returns void.
|
|
54
|
+
assertCacheDecision(events: readonly TelemetryEvent[], routeKey: string, expected: ExpectedCacheStatus): void
|
|
55
|
+
|
|
56
|
+
// filterCacheDecisions -> narrow captured events to cache.decision events (raw form).
|
|
42
57
|
filterCacheDecisions(events: readonly TelemetryEvent[]): CacheDecisionEvent[]
|
|
43
58
|
```
|
|
44
59
|
|
|
@@ -74,16 +89,27 @@ parityDescribe("product page caches", (f) => {
|
|
|
74
89
|
Zero-prod-surface alternative — the telemetry sink. No header at all; you inspect captured `cache.decision` events:
|
|
75
90
|
|
|
76
91
|
```ts
|
|
77
|
-
import {
|
|
92
|
+
import {
|
|
93
|
+
createCacheSink,
|
|
94
|
+
assertCacheDecision,
|
|
95
|
+
filterCacheDecisions,
|
|
96
|
+
} from "@rangojs/router/testing";
|
|
78
97
|
|
|
79
98
|
const { sink, events } = createCacheSink();
|
|
80
99
|
const router = createRouter({ telemetry: sink }).routes(urlpatterns);
|
|
81
100
|
// ...drive a request through the router's RSC fetch path...
|
|
101
|
+
|
|
102
|
+
// One-call assert (counterpart of assertCacheStatus), keyed by the route NAME:
|
|
103
|
+
assertCacheDecision(events, "product.detail", "stale");
|
|
104
|
+
|
|
105
|
+
// Or read the raw event when you need per-segment fields (shouldRevalidate):
|
|
82
106
|
const decision = filterCacheDecisions(events)[0];
|
|
83
107
|
expect(decision.segments?.[0].cacheStatus).toBe("stale");
|
|
84
108
|
expect(decision.segments?.[0].shouldRevalidate).toBe(true);
|
|
85
109
|
```
|
|
86
110
|
|
|
111
|
+
`events` accumulates across requests, so the FIRST matching segment for a `routeKey` wins — slice or recreate the sink between requests for the same route.
|
|
112
|
+
|
|
87
113
|
## Caveats
|
|
88
114
|
|
|
89
115
|
- The `X-Rango-Cache` header is emitted ONLY when the gate is on: `createRouter({ debugCacheSignal: true })` or `process.env.RANGO_TEST_SIGNALS === "1"`. Off by default — zero production surface. With the gate off, `assertCacheStatus` throws a clear "header missing" error.
|
package/skills/testing/flight.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Pinning the Flight wire payload — renderToFlightString
|
|
2
2
|
|
|
3
3
|
**Layer:** RSC unit (react-server project) · **Import:** `@rangojs/router/testing/flight` + `@rangojs/router/testing/flight-matchers` · **DSL it tests:** an async Server Component / Flight output (see `/route`)
|
|
4
4
|
|
|
5
|
+
> **Prefer `renderServerTree` (see [`./server-tree.md`](./server-tree.md)) for assertions on a Flight render** — it deserializes to a traversable tree with TYPED boundary props (a `Date` is a `Date`, not the opaque `$D...` encoding). Reach for `renderToFlightString` + the wire matchers (`toMatchFlight`/`toMatchFlightSnapshot`) only to pin the raw wire payload SHAPE — a `toMatchFlightSnapshot` drift snapshot. That is the niche/escape-hatch case; for "testing an async Server Component (assert what it rendered)" start at `./server-tree.md`.
|
|
6
|
+
|
|
5
7
|
`renderToFlightString` runs the REAL react-server-dom serializer the router uses at runtime — your async Server Component genuinely renders to its Flight wire string in plain node, with a request context active for the render. What you SEED is the request, headers, env, params, routeName, and vars that context exposes.
|
|
6
8
|
|
|
7
9
|
## API
|
|
8
10
|
|
|
9
11
|
### Options — `RenderToFlightStringOptions`
|
|
10
12
|
|
|
11
|
-
| Field | Type | Meaning
|
|
12
|
-
| ----------- | ------------------------ |
|
|
13
|
-
| `request` | `Request \| string` | The request the render runs under: a `Request`, or a URL string (absolute or path). Defaults to `http://localhost/`. A component reading `getRequestContext()` sees this request's url/cookies. When a `Request` is passed, its headers are used and `headers` is ignored.
|
|
14
|
-
| `headers` | `HeadersInit` | Request headers (e.g. Cookie) visible to the server tree, used only when `request` is a string.
|
|
15
|
-
| `env` | `unknown` | Env / bindings exposed as `ctx.env`. Defaults to `{}`.
|
|
16
|
-
| `params` | `Record<string, string>` | Route params exposed via `ctx.params` and loader contexts.
|
|
17
|
-
| `routeName` | `string` | Matched route name (drives `ctx.routeName` and scoped reverse).
|
|
18
|
-
| `
|
|
13
|
+
| Field | Type | Meaning |
|
|
14
|
+
| ----------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `request` | `Request \| string` | The request the render runs under: a `Request`, or a URL string (absolute or path). Defaults to `http://localhost/`. A component reading `getRequestContext()` sees this request's url/cookies. When a `Request` is passed, its headers are used and `headers` is ignored. |
|
|
16
|
+
| `headers` | `HeadersInit` | Request headers (e.g. Cookie) visible to the server tree, used only when `request` is a string. |
|
|
17
|
+
| `env` | `unknown` | Env / bindings exposed as `ctx.env`. Defaults to `{}`. |
|
|
18
|
+
| `params` | `Record<string, string>` | Route params exposed via `ctx.params` and loader contexts. |
|
|
19
|
+
| `routeName` | `string` | Matched route name (drives `ctx.routeName` and scoped reverse). |
|
|
20
|
+
| `routeMap` | `Record<string, string>` | Route name -> pattern map scoping `ctx.reverse()` (like `renderHandler`). Without it, a component that reverses resolves against the GLOBAL route map and is order-dependent on whatever router registered last. Pass the router-under-test's map for deterministic reverse. |
|
|
21
|
+
| `vars` | `VarsInit` | Variables a prior middleware would have set, visible via `ctx.get(...)`. Object form (`{ user }`) or `[key, value]` tuples (`[[userVar, u]]`). |
|
|
19
22
|
|
|
20
23
|
### Context — `RequestContext` (what your component receives)
|
|
21
24
|
|
|
@@ -78,7 +81,7 @@ it("snapshots the normalized payload", async () => {
|
|
|
78
81
|
|
|
79
82
|
- Leaf / server-only: a client island in the tree emits an un-hydratable `I[...]` import row against the empty client manifest. Keep Flight tests to leaf server components; test full pages at e2e.
|
|
80
83
|
- Requires the react-server vitest project (see `./setup.md`): `resolve.conditions` includes `react-server`, the `@rangojs/router -> index.rsc.ts` alias, `NODE_ENV=production`, and the worker `execArgv`. Name files `*.rsc-test.{ts,tsx}` and run `pnpm test:unit:rsc`. The main vitest project must NOT set `react-server` (it would flip React to the no-hooks server build).
|
|
81
|
-
- A component that imports a server API (`getRequestContext`, `cookies`) from the bare `@rangojs/router` barrel works ONLY with the `index.rsc.ts` alias wired (see `./setup.md`); without it the bare import resolves to the throwing out-of-react-server stub. Pure-leaf components that take all data as props need no barrel import and are the simplest case.
|
|
84
|
+
- A component that imports a server API (`getRequestContext`, `cookies`) from the bare `@rangojs/router` barrel works ONLY with the `index.rsc.ts` alias wired (see `./setup.md`); without it the bare import resolves to the throwing out-of-react-server stub. `renderToFlightString` / `renderServerTree` self-diagnose this exact misconfiguration — they reject with an actionable message naming `rangoTestAliases`, rather than surfacing the opaque stub error. Pure-leaf components that take all data as props need no barrel import and are the simplest case.
|
|
82
85
|
- `toMatchFlight` is containment (substring), not equality — the row framing (prefixes/quoting) is an internal serializer detail, so pin the rendered text/shape, not the framing. `toMatchFlightSnapshot()` snapshots the normalized payload; run under `NODE_ENV=production` for the cleanest, most stable bytes.
|
|
83
86
|
- No hydration / no interaction here — that is the e2e tier. For typed assertions on a client boundary's props (a `Date` back as a `Date`), or to confirm an island actually crossed the boundary, use `renderServerTree` (see `./server-tree.md`).
|
|
84
87
|
|
|
@@ -20,6 +20,8 @@ A Rango route handler is a pure function `(ctx) => rsc` — the function you pas
|
|
|
20
20
|
| `loaders` | `ReadonlyArray<readonly [LoaderDefinition, unknown]>` | Seed the data `ctx.use(SomeLoader)` returns. Matched by loader reference; NO real loader runs. |
|
|
21
21
|
| `clientComponents` | `Record<string, unknown>` | `"use client"` components in the handler's RSC, so they serialize as real boundaries when `rangoUseClientTransform()` is not wired. Keyed by name. |
|
|
22
22
|
| `stateCookie` | `StateCookieSeed` (`{ prefix?, routerId?, version? }`) | Customize the rango state cookie a handler calling `invalidateClientCache()` rotates. The name is ALWAYS seeded (default `rango-state_router_0`) so the rotation `Set-Cookie` fires like production rather than no-opping; override `prefix`/`routerId` to match your `createRouter({ stateCookiePrefix, id })`, or `version` (the value is `{version}:{timestamp}`, default `"0"`). |
|
|
23
|
+
| `cacheStore` | `SegmentCacheStore` | Segment cache store backing a `"use cache"` function the handler invokes (e.g. `new MemorySegmentCacheStore()`). WITHOUT it, `registerCachedFunction` takes the uncached bypass and the cached path is NOT exercised (the runtime emits a one-time warning under the test runner). Pair with `cacheProfiles`. |
|
|
24
|
+
| `cacheProfiles` | `Record<string, CacheProfile>` | Cache profiles in the `createRouter({ cacheProfiles })` shape, required for `"use cache: profileName"` resolution once a `cacheStore` is wired. |
|
|
23
25
|
|
|
24
26
|
### Context — `HandlerContext` (what your handler receives)
|
|
25
27
|
|
|
@@ -110,6 +112,7 @@ it("asserts the client-cache directives", async () => {
|
|
|
110
112
|
- A `throw redirect()` is captured on `thrown` (with `tree` undefined, since it produced a `Response`) — assert on `thrown`/`response`, no try/catch needed.
|
|
111
113
|
- No hydration and no interaction — for clicks, forms, and navigation use e2e.
|
|
112
114
|
- `renderHandler` runs a handler FUNCTION `(ctx) => rsc`; for a plain ELEMENT `<Page/>` use `renderServerTree` (see [`./server-tree.md`](./server-tree.md)).
|
|
115
|
+
- A handler that calls a `"use cache"` function runs UNCACHED unless you seed `cacheStore` (and `cacheProfiles` for a named profile). With nothing seeded the runtime bypasses to the live body and warns once under the test runner — assert real cache behavior by passing `{ cacheStore: new MemorySegmentCacheStore(), cacheProfiles: { default: { ttl: 60 } } }`.
|
|
113
116
|
|
|
114
117
|
## See also
|
|
115
118
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Layer:** RSC unit (react-server project) · **Import:** `@rangojs/router/testing/flight` · **DSL it tests:** client islands across the boundary + server-rendered host content (see `/route`)
|
|
4
4
|
|
|
5
|
-
`renderServerTree` serializes the real Flight (identical bytes to `renderToFlightString`) and then deserializes it back to an inspectable React element tree you traverse — that serialize/deserialize round-trip is REAL; what you SEED is the element you render plus the request context (`request`/`headers`/`params`/`vars`/`env`). The win over the wire string: a client boundary's props come back as real JS values (a `Date` is a `Date`, not the opaque `$D...` encoding) and you can confirm a `"use client"` component actually crossed the boundary (an `I` row) instead of being inlined. There is NO hydration and NO interaction — boundaries are inert placeholders carrying props.
|
|
5
|
+
`renderServerTree` is the DEFAULT way to assert on a Flight render; `renderToFlightString` (see [`./flight.md`](./flight.md)) is the escape hatch for pinning the raw wire bytes. It serializes the real Flight (identical bytes to `renderToFlightString`) and then deserializes it back to an inspectable React element tree you traverse — that serialize/deserialize round-trip is REAL; what you SEED is the element you render plus the request context (`request`/`headers`/`params`/`vars`/`env`). The win over the wire string: a client boundary's props come back as real JS values (a `Date` is a `Date`, not the opaque `$D...` encoding) and you can confirm a `"use client"` component actually crossed the boundary (an `I` row) instead of being inlined. There is NO hydration and NO interaction — boundaries are inert placeholders carrying props.
|
|
6
6
|
|
|
7
7
|
## API
|
|
8
8
|
|
package/skills/testing/setup.md
CHANGED
|
@@ -106,7 +106,7 @@ Scripts:
|
|
|
106
106
|
|
|
107
107
|
- Node >= 23 requires `rangoTestConfig()`, not bare `rangoTestAliases()`. `@rangojs/router` is consumed as SOURCE (exports -> `./src/*.ts`), and Node >= 23 refuses to type-strip `.ts` under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). `rangoTestConfig` ships as compiled JS AND adds `server.deps.inline: [/@rangojs[/\\]router/]` so Vite (not Node) transpiles rango source. With bare `rangoTestAliases` you must wire `deps.inline` yourself.
|
|
108
108
|
- Two separate projects. The node/DOM project keeps React on its CLIENT build; the Flight project uses the `react-server` condition in a separate `vitest.rsc.config.ts`. The main project must NOT set `react-server` — it flips React to the no-hooks server build and breaks every `renderRoute` / client test.
|
|
109
|
-
- The rsc project needs BOTH `resolve.conditions: ["react-server"]` AND the bare `@rangojs/router` -> `index.rsc.ts` alias from `rangoTestAliases({ preset })`. `resolve.conditions` alone is not reliably applied to bare-package export resolution; without the alias a handler/component reading `getRequestContext()` / `cookies()` resolves the throwing out-of-react-server stub (symptom: `renderHandler` returns `tree: undefined`).
|
|
109
|
+
- The rsc project needs BOTH `resolve.conditions: ["react-server"]` AND the bare `@rangojs/router` -> `index.rsc.ts` alias from `rangoTestAliases({ preset })`. `resolve.conditions` alone is not reliably applied to bare-package export resolution; without the alias a handler/component reading `getRequestContext()` / `cookies()` resolves the throwing out-of-react-server stub (symptom: `renderHandler` returns `tree: undefined`). `renderToFlightString` / `renderServerTree` now self-diagnose this exact misconfiguration — they reject with an actionable message naming `rangoTestAliases`, rather than surfacing the opaque stub error.
|
|
110
110
|
- `NODE_ENV` must be `"production"` in the rsc project. Dev `NODE_ENV` crashes the bare worker (jsxDEV owner-stack machinery uninitialized) and emits volatile debug rows that defeat stable Flight snapshots.
|
|
111
111
|
- The forked rsc worker (`pool: "forks"`) must force the condition via `execArgv: ["--conditions=react-server"]`, or React throws "the react-server condition must be enabled".
|
|
112
112
|
- The `@rangojs/router:version` and `@vitejs/plugin-rsc/rsc` virtuals must be stubbed; the preset does it. A bare router import without stubbing throws.
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
import type { BoundTransaction } from "./navigation-transaction.js";
|
|
22
22
|
import { ServerRedirect } from "../errors.js";
|
|
23
23
|
import { debugLog } from "./logging.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
validateRedirectOrigin,
|
|
26
|
+
validateExternalRedirect,
|
|
27
|
+
} from "./validate-redirect-origin.js";
|
|
25
28
|
import type { NavigationUpdate } from "./types.js";
|
|
26
29
|
|
|
27
30
|
function toScrollPayload(
|
|
@@ -211,6 +214,24 @@ export function createPartialUpdater(
|
|
|
211
214
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
212
215
|
return;
|
|
213
216
|
}
|
|
217
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
218
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
219
|
+
// waives the same-origin check the app opted out of, NOT scheme safety, so
|
|
220
|
+
// a forged payload carrying a javascript:/data: URL cannot script via
|
|
221
|
+
// location.assign.
|
|
222
|
+
if (payload.metadata.redirect.external) {
|
|
223
|
+
const externalUrl = validateExternalRedirect(
|
|
224
|
+
payload.metadata.redirect.url,
|
|
225
|
+
window.location.origin,
|
|
226
|
+
);
|
|
227
|
+
if (!externalUrl) {
|
|
228
|
+
debugLog("[Browser] Ignoring blocked external redirect payload");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
debugLog("[Browser] External redirect (hard navigation)");
|
|
232
|
+
window.location.assign(externalUrl);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
214
235
|
const redirectUrl = validateRedirectOrigin(
|
|
215
236
|
payload.metadata.redirect.url,
|
|
216
237
|
window.location.origin,
|
|
@@ -60,7 +60,7 @@ export function useRouter(): RouterInstance {
|
|
|
60
60
|
return ctx.refresh();
|
|
61
61
|
},
|
|
62
62
|
|
|
63
|
-
prefetch(url: string): void {
|
|
63
|
+
prefetch(url: string, options?: { key?: ":source" }): void {
|
|
64
64
|
const segmentState = ctx.store?.getSegmentState();
|
|
65
65
|
if (segmentState) {
|
|
66
66
|
prefetchDirect(
|
|
@@ -68,6 +68,7 @@ export function useRouter(): RouterInstance {
|
|
|
68
68
|
segmentState.currentSegmentIds,
|
|
69
69
|
getAppVersion(),
|
|
70
70
|
ctx.store?.getRouterId?.(),
|
|
71
|
+
options?.key,
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
},
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
generateHistoryKey,
|
|
9
9
|
} from "./navigation-store.js";
|
|
10
10
|
import { createEventController } from "./event-controller.js";
|
|
11
|
+
import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
11
12
|
import { createNavigationClient } from "./navigation-client.js";
|
|
12
13
|
import { createServerActionBridge } from "./server-action-bridge.js";
|
|
13
14
|
import { createNavigationBridge } from "./navigation-bridge.js";
|
|
@@ -280,7 +281,13 @@ export async function initBrowserApp(
|
|
|
280
281
|
renderSegments,
|
|
281
282
|
onNavigate: (url, options) => {
|
|
282
283
|
if (!navigateFn) {
|
|
283
|
-
|
|
284
|
+
// Navigation bridge not wired yet: hard-navigate, but re-validate
|
|
285
|
+
// same-origin defensively so this init-window fallback cannot become an
|
|
286
|
+
// open redirect (the normal path validates inside the navigation bridge).
|
|
287
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
288
|
+
if (safe) {
|
|
289
|
+
window.location.href = safe;
|
|
290
|
+
}
|
|
284
291
|
return Promise.resolve();
|
|
285
292
|
}
|
|
286
293
|
return navigateFn(url, options);
|
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
isBrowserDebugEnabled,
|
|
24
24
|
startBrowserTransaction,
|
|
25
25
|
} from "./logging.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
validateRedirectOrigin,
|
|
28
|
+
validateExternalRedirect,
|
|
29
|
+
} from "./validate-redirect-origin.js";
|
|
27
30
|
import {
|
|
28
31
|
extractRscHeaderUrl,
|
|
29
32
|
emptyResponse,
|
|
@@ -83,6 +86,9 @@ export function createServerActionBridge(
|
|
|
83
86
|
|
|
84
87
|
// SPA-navigate when onNavigate is set, else hard-reload. state is omitted (not
|
|
85
88
|
// passed as undefined) to match the header path's prior call shape.
|
|
89
|
+
// Callers pass an already same-origin-validated url; the hard-reload fallback
|
|
90
|
+
// re-validates defensively so this leaf cannot become an open redirect if a
|
|
91
|
+
// future caller forgets (the SPA path validates inside the navigation bridge).
|
|
86
92
|
async function dispatchRedirect(url: string, state?: unknown): Promise<void> {
|
|
87
93
|
if (onNavigate) {
|
|
88
94
|
await onNavigate(url, {
|
|
@@ -91,7 +97,10 @@ export function createServerActionBridge(
|
|
|
91
97
|
_skipCache: true,
|
|
92
98
|
});
|
|
93
99
|
} else {
|
|
94
|
-
window.location.
|
|
100
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
101
|
+
if (safe) {
|
|
102
|
+
window.location.href = safe;
|
|
103
|
+
}
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
|
|
@@ -161,6 +170,15 @@ export function createServerActionBridge(
|
|
|
161
170
|
// Whether the action's response carried the keepClientCache() directive.
|
|
162
171
|
// Set when the response arrives; gates the deferred invalidation below.
|
|
163
172
|
let keepCache = false;
|
|
173
|
+
// Whether a Response actually settled from the network (the server saw the
|
|
174
|
+
// request). Set true as the first statement in the fetch .then() below.
|
|
175
|
+
// Gates the automatic invalidation: a pre-dispatch failure (encodeReply
|
|
176
|
+
// throw or a fetch rejection — server unreachable/DNS/connection refused)
|
|
177
|
+
// leaves this false, so finalizeAction() must NOT invalidate or broadcast —
|
|
178
|
+
// nothing reached the server, so nothing could have mutated. A failed Flight
|
|
179
|
+
// DECODE after the response arrived keeps it true (the mutation may have
|
|
180
|
+
// committed, so invalidating the now-possibly-stale client cache is correct).
|
|
181
|
+
let responseReceived = false;
|
|
164
182
|
// Single deferred invalidation + fence release, run exactly ONCE however the
|
|
165
183
|
// action terminates (normal, redirect, error, abort, intercept, concurrent).
|
|
166
184
|
// This replaces main's eager clear at action start: every directive-free
|
|
@@ -176,7 +194,10 @@ export function createServerActionBridge(
|
|
|
176
194
|
actionFinalized = true;
|
|
177
195
|
// finally so a throw in invalidation cannot leak the fence (latch is set).
|
|
178
196
|
try {
|
|
179
|
-
|
|
197
|
+
// responseReceived gates the automatic invalidation: a pre-dispatch
|
|
198
|
+
// failure (serialize throw / fetch reject) never reached the server, so
|
|
199
|
+
// marking the cache stale + broadcasting cross-tab would be spurious.
|
|
200
|
+
if (responseReceived && !keepCache && !skipInvalidation) {
|
|
180
201
|
store.markCacheAsStaleAndBroadcast();
|
|
181
202
|
}
|
|
182
203
|
} finally {
|
|
@@ -263,6 +284,12 @@ export function createServerActionBridge(
|
|
|
263
284
|
body: encodedBody,
|
|
264
285
|
signal: fetchAbort.signal,
|
|
265
286
|
}).then(async (response) => {
|
|
287
|
+
// A settled fetch promise means the request reached the server and a
|
|
288
|
+
// Response came back (true for 2xx, 4xx, AND 5xx — fetch only rejects
|
|
289
|
+
// on network-layer failure, never on HTTP status). Record it as the
|
|
290
|
+
// first statement so every downstream terminal can invalidate; a
|
|
291
|
+
// pre-dispatch failure never gets here and stays gated out.
|
|
292
|
+
responseReceived = true;
|
|
266
293
|
// Response arrived — disconnect fetch abort from handle abort so
|
|
267
294
|
// abortAllActions() doesn't disrupt the in-progress Flight stream.
|
|
268
295
|
handle.signal.removeEventListener("abort", onHandleAbort);
|
|
@@ -399,6 +426,27 @@ export function createServerActionBridge(
|
|
|
399
426
|
// Check handle.signal.aborted to avoid redirecting from a stale action
|
|
400
427
|
// when the user has already navigated away.
|
|
401
428
|
if (metadata?.redirect && !handle.signal.aborted) {
|
|
429
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
430
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
431
|
+
// waives the same-origin check, NOT scheme safety, so a forged payload
|
|
432
|
+
// carrying a javascript:/data: URL cannot script via location.assign.
|
|
433
|
+
if (metadata.redirect.external) {
|
|
434
|
+
const externalUrl = validateExternalRedirect(
|
|
435
|
+
metadata.redirect.url,
|
|
436
|
+
window.location.origin,
|
|
437
|
+
);
|
|
438
|
+
if (!externalUrl) {
|
|
439
|
+
log("blocked external action redirect payload", {
|
|
440
|
+
url: metadata.redirect.url,
|
|
441
|
+
});
|
|
442
|
+
handle.complete(returnValue?.data);
|
|
443
|
+
return returnValue?.data;
|
|
444
|
+
}
|
|
445
|
+
log("external action redirect", { url: externalUrl });
|
|
446
|
+
handle.complete(returnValue?.data);
|
|
447
|
+
window.location.assign(externalUrl);
|
|
448
|
+
return returnValue?.data;
|
|
449
|
+
}
|
|
402
450
|
const redirectUrl = validateRedirectOrigin(
|
|
403
451
|
metadata.redirect.url,
|
|
404
452
|
window.location.origin,
|
package/src/browser/types.ts
CHANGED
|
@@ -90,8 +90,12 @@ export interface RscMetadata {
|
|
|
90
90
|
basename?: string;
|
|
91
91
|
/** Whether connection warmup is enabled */
|
|
92
92
|
warmupEnabled?: boolean;
|
|
93
|
-
/**
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Server-side redirect with optional state (for partial requests).
|
|
95
|
+
* `external: true` (from redirect(url, { external: true })) tells the client
|
|
96
|
+
* to hard-navigate to an off-host target instead of validating same-origin.
|
|
97
|
+
*/
|
|
98
|
+
redirect?: { url: string; external?: boolean };
|
|
95
99
|
/** Server-set location state to include in history.pushState */
|
|
96
100
|
locationState?: Record<string, unknown>;
|
|
97
101
|
}
|
|
@@ -192,6 +196,15 @@ export interface TrackedActionState {
|
|
|
192
196
|
result: unknown | null;
|
|
193
197
|
}
|
|
194
198
|
|
|
199
|
+
/**
|
|
200
|
+
* The value returned by {@link useAction} when called without a selector.
|
|
201
|
+
*
|
|
202
|
+
* This is the stable, public name for the action-state shape; consumers can
|
|
203
|
+
* name it in their own signatures (e.g. a wrapper hook). It aliases the
|
|
204
|
+
* internal {@link TrackedActionState}.
|
|
205
|
+
*/
|
|
206
|
+
export type ActionState = TrackedActionState;
|
|
207
|
+
|
|
195
208
|
/**
|
|
196
209
|
* Listener for action state changes
|
|
197
210
|
*
|
|
@@ -329,8 +342,14 @@ export interface RouterInstance {
|
|
|
329
342
|
replace(url: string, options?: RouterNavigateOptions): Promise<void>;
|
|
330
343
|
/** Refresh the current route (re-fetch server data, preserve client state) */
|
|
331
344
|
refresh(): Promise<void>;
|
|
332
|
-
/**
|
|
333
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Prefetch a URL for faster client-side transition.
|
|
347
|
+
*
|
|
348
|
+
* Pass `{ key: ":source" }` to source-scope the prefetch cache entry (parity
|
|
349
|
+
* with `<Link prefetchKey=":source">`) when the target's response can differ
|
|
350
|
+
* by source page.
|
|
351
|
+
*/
|
|
352
|
+
prefetch(url: string, options?: { key?: ":source" }): void;
|
|
334
353
|
/** Go back in browser history */
|
|
335
354
|
back(): void;
|
|
336
355
|
/** Go forward in browser history */
|
|
@@ -1,28 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveSameOriginRedirect,
|
|
3
|
+
resolveExternalRedirect,
|
|
4
|
+
} from "../redirect-origin.js";
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Validate that a client-consumed redirect URL (from headers or Flight payload)
|
|
3
8
|
* targets the same origin as the current page. Prevents open-redirect attacks
|
|
4
9
|
* via crafted responses.
|
|
5
10
|
*
|
|
11
|
+
* Thin wrapper over the shared {@link resolveSameOriginRedirect} rule (also used
|
|
12
|
+
* by the server guard in `rsc/redirect-guard.ts`) so client and server enforce
|
|
13
|
+
* the identical same-origin contract. Adds the client-side `console.error` on a
|
|
14
|
+
* block; the resolver itself stays pure.
|
|
15
|
+
*
|
|
6
16
|
* @returns The canonical (normalized) URL string on success, or null if blocked.
|
|
7
17
|
*/
|
|
8
18
|
export function validateRedirectOrigin(
|
|
9
19
|
url: string,
|
|
10
20
|
currentOrigin: string,
|
|
11
21
|
): string | null {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const resolved = resolveSameOriginRedirect(url, currentOrigin);
|
|
23
|
+
if (resolved === null) {
|
|
24
|
+
console.error(
|
|
25
|
+
`[rango] Redirect blocked: cross-origin or invalid target "${url}"`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate an explicit off-origin redirect (`redirect(url, { external: true })`)
|
|
33
|
+
* the client is about to hard-navigate to via `window.location.assign()`.
|
|
34
|
+
*
|
|
35
|
+
* Thin wrapper over the shared {@link resolveExternalRedirect} rule (also used by
|
|
36
|
+
* the server guard in `rsc/redirect-guard.ts`) so client and server enforce the
|
|
37
|
+
* identical contract: `external` allows an off-origin target but only an
|
|
38
|
+
* http(s) scheme. This stops a forged or mistaken external payload carrying a
|
|
39
|
+
* `javascript:`/`data:` URL from turning `location.assign` into a scriptable
|
|
40
|
+
* navigation. Adds the client-side `console.error` on a block; the resolver
|
|
41
|
+
* itself stays pure.
|
|
42
|
+
*
|
|
43
|
+
* @returns The normalized URL string on success, or null if blocked.
|
|
44
|
+
*/
|
|
45
|
+
export function validateExternalRedirect(
|
|
46
|
+
url: string,
|
|
47
|
+
currentOrigin: string,
|
|
48
|
+
): string | null {
|
|
49
|
+
const resolved = resolveExternalRedirect(url, currentOrigin);
|
|
50
|
+
if (resolved === null) {
|
|
51
|
+
console.error(
|
|
52
|
+
`[rango] External redirect blocked: non-http(s) target "${url}"`,
|
|
53
|
+
);
|
|
27
54
|
}
|
|
55
|
+
return resolved;
|
|
28
56
|
}
|