@benjavicente/router-core 1.168.9
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/LICENSE +21 -0
- package/README.md +5 -0
- package/bin/intent.js +25 -0
- package/dist/cjs/Matches.cjs +17 -0
- package/dist/cjs/Matches.cjs.map +1 -0
- package/dist/cjs/Matches.d.cts +139 -0
- package/dist/cjs/RouterProvider.d.cts +27 -0
- package/dist/cjs/config.cjs +11 -0
- package/dist/cjs/config.cjs.map +1 -0
- package/dist/cjs/config.d.cts +17 -0
- package/dist/cjs/defer.cjs +41 -0
- package/dist/cjs/defer.cjs.map +1 -0
- package/dist/cjs/defer.d.cts +37 -0
- package/dist/cjs/fileRoute.d.cts +24 -0
- package/dist/cjs/global.d.cts +7 -0
- package/dist/cjs/hash-scroll.cjs +20 -0
- package/dist/cjs/hash-scroll.cjs.map +1 -0
- package/dist/cjs/hash-scroll.d.cts +7 -0
- package/dist/cjs/history.d.cts +8 -0
- package/dist/cjs/index.cjs +96 -0
- package/dist/cjs/index.d.cts +53 -0
- package/dist/cjs/invariant.cjs +8 -0
- package/dist/cjs/invariant.cjs.map +1 -0
- package/dist/cjs/invariant.d.cts +1 -0
- package/dist/cjs/isServer/client.cjs +7 -0
- package/dist/cjs/isServer/client.cjs.map +1 -0
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +7 -0
- package/dist/cjs/isServer/development.cjs.map +1 -0
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +7 -0
- package/dist/cjs/isServer/server.cjs.map +1 -0
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/link.cjs +6 -0
- package/dist/cjs/link.cjs.map +1 -0
- package/dist/cjs/link.d.cts +221 -0
- package/dist/cjs/load-matches.cjs +659 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +18 -0
- package/dist/cjs/location.d.cts +50 -0
- package/dist/cjs/lru-cache.cjs +70 -0
- package/dist/cjs/lru-cache.cjs.map +1 -0
- package/dist/cjs/lru-cache.d.cts +6 -0
- package/dist/cjs/manifest.cjs +18 -0
- package/dist/cjs/manifest.cjs.map +1 -0
- package/dist/cjs/manifest.d.cts +35 -0
- package/dist/cjs/new-process-route-tree.cjs +754 -0
- package/dist/cjs/new-process-route-tree.cjs.map +1 -0
- package/dist/cjs/new-process-route-tree.d.cts +236 -0
- package/dist/cjs/not-found.cjs +26 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +32 -0
- package/dist/cjs/path.cjs +252 -0
- package/dist/cjs/path.cjs.map +1 -0
- package/dist/cjs/path.d.cts +56 -0
- package/dist/cjs/qss.cjs +70 -0
- package/dist/cjs/qss.cjs.map +1 -0
- package/dist/cjs/qss.d.cts +33 -0
- package/dist/cjs/redirect.cjs +56 -0
- package/dist/cjs/redirect.cjs.map +1 -0
- package/dist/cjs/redirect.d.cts +77 -0
- package/dist/cjs/rewrite.cjs +68 -0
- package/dist/cjs/rewrite.cjs.map +1 -0
- package/dist/cjs/rewrite.d.cts +30 -0
- package/dist/cjs/root.cjs +7 -0
- package/dist/cjs/root.cjs.map +1 -0
- package/dist/cjs/root.d.cts +3 -0
- package/dist/cjs/route.cjs +100 -0
- package/dist/cjs/route.cjs.map +1 -0
- package/dist/cjs/route.d.cts +552 -0
- package/dist/cjs/routeInfo.d.cts +54 -0
- package/dist/cjs/router.cjs +1173 -0
- package/dist/cjs/router.cjs.map +1 -0
- package/dist/cjs/router.d.cts +734 -0
- package/dist/cjs/scroll-restoration-inline.cjs +6 -0
- package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
- package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
- package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
- package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
- package/dist/cjs/scroll-restoration.cjs +191 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -0
- package/dist/cjs/scroll-restoration.d.cts +38 -0
- package/dist/cjs/searchMiddleware.cjs +55 -0
- package/dist/cjs/searchMiddleware.cjs.map +1 -0
- package/dist/cjs/searchMiddleware.d.cts +25 -0
- package/dist/cjs/searchParams.cjs +65 -0
- package/dist/cjs/searchParams.cjs.map +1 -0
- package/dist/cjs/searchParams.d.cts +31 -0
- package/dist/cjs/ssr/client.cjs +7 -0
- package/dist/cjs/ssr/client.d.cts +6 -0
- package/dist/cjs/ssr/constants.cjs +8 -0
- package/dist/cjs/ssr/constants.cjs.map +1 -0
- package/dist/cjs/ssr/constants.d.cts +3 -0
- package/dist/cjs/ssr/createRequestHandler.cjs +44 -0
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
- package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
- package/dist/cjs/ssr/handlerCallback.cjs +8 -0
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
- package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
- package/dist/cjs/ssr/headers.cjs +21 -0
- package/dist/cjs/ssr/headers.cjs.map +1 -0
- package/dist/cjs/ssr/headers.d.cts +3 -0
- package/dist/cjs/ssr/json.cjs +11 -0
- package/dist/cjs/ssr/json.cjs.map +1 -0
- package/dist/cjs/ssr/json.d.cts +10 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs +287 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/RawStream.d.cts +64 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +32 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +9 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +13 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
- package/dist/cjs/ssr/serializer/transformer.cjs +53 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +91 -0
- package/dist/cjs/ssr/server.cjs +13 -0
- package/dist/cjs/ssr/server.d.cts +6 -0
- package/dist/cjs/ssr/ssr-client.cjs +183 -0
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-client.d.cts +10 -0
- package/dist/cjs/ssr/ssr-match-id.cjs +12 -0
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-match-id.d.cts +2 -0
- package/dist/cjs/ssr/ssr-server.cjs +279 -0
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-server.d.cts +42 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +327 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +11 -0
- package/dist/cjs/ssr/tsrScript.cjs +6 -0
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
- package/dist/cjs/ssr/tsrScript.d.cts +1 -0
- package/dist/cjs/ssr/types.d.cts +30 -0
- package/dist/cjs/stores.cjs +148 -0
- package/dist/cjs/stores.cjs.map +1 -0
- package/dist/cjs/stores.d.cts +70 -0
- package/dist/cjs/structuralSharing.d.cts +4 -0
- package/dist/cjs/typePrimitives.d.cts +65 -0
- package/dist/cjs/useLoaderData.d.cts +5 -0
- package/dist/cjs/useLoaderDeps.d.cts +5 -0
- package/dist/cjs/useNavigate.d.cts +3 -0
- package/dist/cjs/useParams.d.cts +5 -0
- package/dist/cjs/useRouteContext.d.cts +9 -0
- package/dist/cjs/useSearch.d.cts +5 -0
- package/dist/cjs/utils.cjs +339 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +178 -0
- package/dist/cjs/validators.d.cts +51 -0
- package/dist/esm/Matches.d.ts +139 -0
- package/dist/esm/Matches.js +17 -0
- package/dist/esm/Matches.js.map +1 -0
- package/dist/esm/RouterProvider.d.ts +27 -0
- package/dist/esm/config.d.ts +17 -0
- package/dist/esm/config.js +11 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/defer.d.ts +37 -0
- package/dist/esm/defer.js +40 -0
- package/dist/esm/defer.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +24 -0
- package/dist/esm/global.d.ts +7 -0
- package/dist/esm/hash-scroll.d.ts +7 -0
- package/dist/esm/hash-scroll.js +20 -0
- package/dist/esm/hash-scroll.js.map +1 -0
- package/dist/esm/history.d.ts +8 -0
- package/dist/esm/index.d.ts +53 -0
- package/dist/esm/index.js +24 -0
- package/dist/esm/invariant.d.ts +1 -0
- package/dist/esm/invariant.js +8 -0
- package/dist/esm/invariant.js.map +1 -0
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +6 -0
- package/dist/esm/isServer/client.js.map +1 -0
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +6 -0
- package/dist/esm/isServer/development.js.map +1 -0
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +6 -0
- package/dist/esm/isServer/server.js.map +1 -0
- package/dist/esm/link.d.ts +221 -0
- package/dist/esm/link.js +6 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/load-matches.d.ts +18 -0
- package/dist/esm/load-matches.js +657 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/location.d.ts +50 -0
- package/dist/esm/lru-cache.d.ts +6 -0
- package/dist/esm/lru-cache.js +70 -0
- package/dist/esm/lru-cache.js.map +1 -0
- package/dist/esm/manifest.d.ts +35 -0
- package/dist/esm/manifest.js +17 -0
- package/dist/esm/manifest.js.map +1 -0
- package/dist/esm/new-process-route-tree.d.ts +236 -0
- package/dist/esm/new-process-route-tree.js +749 -0
- package/dist/esm/new-process-route-tree.js.map +1 -0
- package/dist/esm/not-found.d.ts +32 -0
- package/dist/esm/not-found.js +25 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/path.d.ts +56 -0
- package/dist/esm/path.js +243 -0
- package/dist/esm/path.js.map +1 -0
- package/dist/esm/qss.d.ts +33 -0
- package/dist/esm/qss.js +69 -0
- package/dist/esm/qss.js.map +1 -0
- package/dist/esm/redirect.d.ts +77 -0
- package/dist/esm/redirect.js +53 -0
- package/dist/esm/redirect.js.map +1 -0
- package/dist/esm/rewrite.d.ts +30 -0
- package/dist/esm/rewrite.js +65 -0
- package/dist/esm/rewrite.js.map +1 -0
- package/dist/esm/root.d.ts +3 -0
- package/dist/esm/root.js +7 -0
- package/dist/esm/root.js.map +1 -0
- package/dist/esm/route.d.ts +552 -0
- package/dist/esm/route.js +98 -0
- package/dist/esm/route.js.map +1 -0
- package/dist/esm/routeInfo.d.ts +54 -0
- package/dist/esm/router.d.ts +734 -0
- package/dist/esm/router.js +1165 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/scroll-restoration-inline.d.ts +6 -0
- package/dist/esm/scroll-restoration-inline.js +6 -0
- package/dist/esm/scroll-restoration-inline.js.map +1 -0
- package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/client.js +8 -0
- package/dist/esm/scroll-restoration-script/client.js.map +1 -0
- package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/server.js +29 -0
- package/dist/esm/scroll-restoration-script/server.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +38 -0
- package/dist/esm/scroll-restoration.js +187 -0
- package/dist/esm/scroll-restoration.js.map +1 -0
- package/dist/esm/searchMiddleware.d.ts +25 -0
- package/dist/esm/searchMiddleware.js +54 -0
- package/dist/esm/searchMiddleware.js.map +1 -0
- package/dist/esm/searchParams.d.ts +31 -0
- package/dist/esm/searchParams.js +62 -0
- package/dist/esm/searchParams.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +6 -0
- package/dist/esm/ssr/client.js +4 -0
- package/dist/esm/ssr/constants.d.ts +3 -0
- package/dist/esm/ssr/constants.js +7 -0
- package/dist/esm/ssr/constants.js.map +1 -0
- package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
- package/dist/esm/ssr/createRequestHandler.js +44 -0
- package/dist/esm/ssr/createRequestHandler.js.map +1 -0
- package/dist/esm/ssr/handlerCallback.d.ts +9 -0
- package/dist/esm/ssr/handlerCallback.js +8 -0
- package/dist/esm/ssr/handlerCallback.js.map +1 -0
- package/dist/esm/ssr/headers.d.ts +3 -0
- package/dist/esm/ssr/headers.js +21 -0
- package/dist/esm/ssr/headers.js.map +1 -0
- package/dist/esm/ssr/json.d.ts +10 -0
- package/dist/esm/ssr/json.js +11 -0
- package/dist/esm/ssr/json.js.map +1 -0
- package/dist/esm/ssr/serializer/RawStream.d.ts +64 -0
- package/dist/esm/ssr/serializer/RawStream.js +282 -0
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +9 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +33 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js +13 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
- package/dist/esm/ssr/serializer/transformer.d.ts +91 -0
- package/dist/esm/ssr/serializer/transformer.js +51 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +5 -0
- package/dist/esm/ssr/ssr-client.d.ts +10 -0
- package/dist/esm/ssr/ssr-client.js +183 -0
- package/dist/esm/ssr/ssr-client.js.map +1 -0
- package/dist/esm/ssr/ssr-match-id.d.ts +2 -0
- package/dist/esm/ssr/ssr-match-id.js +11 -0
- package/dist/esm/ssr/ssr-match-id.js.map +1 -0
- package/dist/esm/ssr/ssr-server.d.ts +42 -0
- package/dist/esm/ssr/ssr-server.js +277 -0
- package/dist/esm/ssr/ssr-server.js.map +1 -0
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +11 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +325 -0
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
- package/dist/esm/ssr/tsrScript.d.ts +0 -0
- package/dist/esm/ssr/tsrScript.js +6 -0
- package/dist/esm/ssr/tsrScript.js.map +1 -0
- package/dist/esm/ssr/types.d.ts +30 -0
- package/dist/esm/stores.d.ts +70 -0
- package/dist/esm/stores.js +146 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/structuralSharing.d.ts +4 -0
- package/dist/esm/typePrimitives.d.ts +65 -0
- package/dist/esm/useLoaderData.d.ts +5 -0
- package/dist/esm/useLoaderDeps.d.ts +5 -0
- package/dist/esm/useNavigate.d.ts +3 -0
- package/dist/esm/useParams.d.ts +5 -0
- package/dist/esm/useRouteContext.d.ts +9 -0
- package/dist/esm/useSearch.d.ts +5 -0
- package/dist/esm/utils.d.ts +178 -0
- package/dist/esm/utils.js +322 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validators.d.ts +51 -0
- package/package.json +200 -0
- package/skills/router-core/SKILL.md +139 -0
- package/skills/router-core/auth-and-guards/SKILL.md +458 -0
- package/skills/router-core/code-splitting/SKILL.md +322 -0
- package/skills/router-core/data-loading/SKILL.md +485 -0
- package/skills/router-core/navigation/SKILL.md +448 -0
- package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
- package/skills/router-core/path-params/SKILL.md +382 -0
- package/skills/router-core/search-params/SKILL.md +349 -0
- package/skills/router-core/search-params/references/validation-patterns.md +379 -0
- package/skills/router-core/ssr/SKILL.md +437 -0
- package/skills/router-core/type-safety/SKILL.md +497 -0
- package/src/Matches.ts +291 -0
- package/src/RouterProvider.ts +47 -0
- package/src/config.ts +42 -0
- package/src/defer.ts +69 -0
- package/src/fileRoute.ts +164 -0
- package/src/global.ts +9 -0
- package/src/hash-scroll.ts +21 -0
- package/src/history.ts +9 -0
- package/src/index.ts +471 -0
- package/src/invariant.ts +3 -0
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +2 -0
- package/src/isServer/server.ts +1 -0
- package/src/link.ts +704 -0
- package/src/load-matches.ts +1281 -0
- package/src/location.ts +51 -0
- package/src/lru-cache.ts +74 -0
- package/src/manifest.ts +68 -0
- package/src/new-process-route-tree.ts +1387 -0
- package/src/not-found.ts +41 -0
- package/src/path.ts +436 -0
- package/src/qss.ts +81 -0
- package/src/redirect.ts +179 -0
- package/src/rewrite.ts +93 -0
- package/src/root.ts +3 -0
- package/src/route.ts +2235 -0
- package/src/routeInfo.ts +235 -0
- package/src/router.ts +3207 -0
- package/src/scroll-restoration-inline.ts +81 -0
- package/src/scroll-restoration-script/client.ts +5 -0
- package/src/scroll-restoration-script/server.ts +64 -0
- package/src/scroll-restoration.ts +357 -0
- package/src/searchMiddleware.ts +76 -0
- package/src/searchParams.ts +90 -0
- package/src/ssr/client.ts +6 -0
- package/src/ssr/constants.ts +3 -0
- package/src/ssr/createRequestHandler.ts +98 -0
- package/src/ssr/handlerCallback.ts +15 -0
- package/src/ssr/headers.ts +40 -0
- package/src/ssr/json.ts +16 -0
- package/src/ssr/serializer/RawStream.ts +464 -0
- package/src/ssr/serializer/ShallowErrorPlugin.ts +43 -0
- package/src/ssr/serializer/seroval-plugins.ts +12 -0
- package/src/ssr/serializer/transformer.ts +312 -0
- package/src/ssr/server.ts +14 -0
- package/src/ssr/ssr-client.ts +313 -0
- package/src/ssr/ssr-match-id.ts +7 -0
- package/src/ssr/ssr-server.ts +425 -0
- package/src/ssr/transformStreamWithRouter.ts +493 -0
- package/src/ssr/tsrScript.ts +20 -0
- package/src/ssr/types.ts +41 -0
- package/src/stores.ts +342 -0
- package/src/structuralSharing.ts +7 -0
- package/src/typePrimitives.ts +181 -0
- package/src/useLoaderData.ts +20 -0
- package/src/useLoaderDeps.ts +13 -0
- package/src/useNavigate.ts +13 -0
- package/src/useParams.ts +20 -0
- package/src/useRouteContext.ts +39 -0
- package/src/useSearch.ts +20 -0
- package/src/utils.ts +708 -0
- package/src/validators.ts +121 -0
- package/src/vite-env.d.ts +4 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
import { isServer } from '@benjavicente/router-core/isServer'
|
|
2
|
+
import type { RouteIds } from './routeInfo'
|
|
3
|
+
import type { AnyRouter } from './router'
|
|
4
|
+
|
|
5
|
+
export type Awaitable<T> = T | Promise<T>
|
|
6
|
+
export type NoInfer<T> = [T][T extends any ? 0 : never]
|
|
7
|
+
export type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue
|
|
8
|
+
? TYesResult
|
|
9
|
+
: TNoResult
|
|
10
|
+
|
|
11
|
+
export type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<
|
|
12
|
+
TValue,
|
|
13
|
+
TKey
|
|
14
|
+
> &
|
|
15
|
+
Required<Pick<TValue, TKey>>
|
|
16
|
+
|
|
17
|
+
export type PickRequired<T> = {
|
|
18
|
+
[K in keyof T as undefined extends T[K] ? never : K]: T[K]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PickOptional<T> = {
|
|
22
|
+
[K in keyof T as undefined extends T[K] ? K : never]: T[K]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// from https://stackoverflow.com/a/76458160
|
|
26
|
+
export type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never
|
|
27
|
+
|
|
28
|
+
export type Expand<T> = T extends object
|
|
29
|
+
? T extends infer O
|
|
30
|
+
? O extends Function
|
|
31
|
+
? O
|
|
32
|
+
: { [K in keyof O]: O[K] }
|
|
33
|
+
: never
|
|
34
|
+
: T
|
|
35
|
+
|
|
36
|
+
export type DeepPartial<T> = T extends object
|
|
37
|
+
? {
|
|
38
|
+
[P in keyof T]?: DeepPartial<T[P]>
|
|
39
|
+
}
|
|
40
|
+
: T
|
|
41
|
+
|
|
42
|
+
export type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &
|
|
43
|
+
keyof TRight extends never
|
|
44
|
+
? TRight
|
|
45
|
+
: Omit<TRight, keyof TLeft & keyof TRight> & {
|
|
46
|
+
[K in keyof TLeft & keyof TRight]?: TRight[K]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// from https://stackoverflow.com/a/53955431
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
51
|
+
export type IsUnion<T, U extends T = T> = (
|
|
52
|
+
T extends any ? (U extends T ? false : true) : never
|
|
53
|
+
) extends false
|
|
54
|
+
? false
|
|
55
|
+
: true
|
|
56
|
+
|
|
57
|
+
export type IsNonEmptyObject<T> = T extends object
|
|
58
|
+
? keyof T extends never
|
|
59
|
+
? false
|
|
60
|
+
: true
|
|
61
|
+
: false
|
|
62
|
+
|
|
63
|
+
export type Assign<TLeft, TRight> = TLeft extends any
|
|
64
|
+
? TRight extends any
|
|
65
|
+
? IsNonEmptyObject<TLeft> extends false
|
|
66
|
+
? TRight
|
|
67
|
+
: IsNonEmptyObject<TRight> extends false
|
|
68
|
+
? TLeft
|
|
69
|
+
: keyof TLeft & keyof TRight extends never
|
|
70
|
+
? TLeft & TRight
|
|
71
|
+
: Omit<TLeft, keyof TRight> & TRight
|
|
72
|
+
: never
|
|
73
|
+
: never
|
|
74
|
+
|
|
75
|
+
export type IntersectAssign<TLeft, TRight> = TLeft extends any
|
|
76
|
+
? TRight extends any
|
|
77
|
+
? IsNonEmptyObject<TLeft> extends false
|
|
78
|
+
? TRight
|
|
79
|
+
: IsNonEmptyObject<TRight> extends false
|
|
80
|
+
? TLeft
|
|
81
|
+
: TRight & TLeft
|
|
82
|
+
: never
|
|
83
|
+
: never
|
|
84
|
+
|
|
85
|
+
export type Timeout = ReturnType<typeof setTimeout>
|
|
86
|
+
|
|
87
|
+
export type Updater<TPrevious, TResult = TPrevious> =
|
|
88
|
+
| TResult
|
|
89
|
+
| ((prev?: TPrevious) => TResult)
|
|
90
|
+
|
|
91
|
+
export type NonNullableUpdater<TPrevious, TResult = TPrevious> =
|
|
92
|
+
| TResult
|
|
93
|
+
| ((prev: TPrevious) => TResult)
|
|
94
|
+
|
|
95
|
+
export type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive
|
|
96
|
+
? never
|
|
97
|
+
: TUnion
|
|
98
|
+
|
|
99
|
+
export type PartialMergeAllObject<TUnion> =
|
|
100
|
+
ExtractObjects<TUnion> extends infer TObj
|
|
101
|
+
? [TObj] extends [never]
|
|
102
|
+
? never
|
|
103
|
+
: {
|
|
104
|
+
[TKey in TObj extends any ? keyof TObj : never]?: TObj extends any
|
|
105
|
+
? TKey extends keyof TObj
|
|
106
|
+
? TObj[TKey]
|
|
107
|
+
: never
|
|
108
|
+
: never
|
|
109
|
+
}
|
|
110
|
+
: never
|
|
111
|
+
|
|
112
|
+
export type MergeAllPrimitive =
|
|
113
|
+
| ReadonlyArray<any>
|
|
114
|
+
| number
|
|
115
|
+
| string
|
|
116
|
+
| bigint
|
|
117
|
+
| boolean
|
|
118
|
+
| symbol
|
|
119
|
+
| undefined
|
|
120
|
+
| null
|
|
121
|
+
|
|
122
|
+
export type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive
|
|
123
|
+
? TUnion
|
|
124
|
+
: TUnion extends object
|
|
125
|
+
? never
|
|
126
|
+
: TUnion
|
|
127
|
+
|
|
128
|
+
export type PartialMergeAll<TUnion> =
|
|
129
|
+
| ExtractPrimitives<TUnion>
|
|
130
|
+
| PartialMergeAllObject<TUnion>
|
|
131
|
+
|
|
132
|
+
export type Constrain<T, TConstraint, TDefault = TConstraint> =
|
|
133
|
+
| (T extends TConstraint ? T : never)
|
|
134
|
+
| TDefault
|
|
135
|
+
|
|
136
|
+
export type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =
|
|
137
|
+
| (T & TConstraint)
|
|
138
|
+
| TDefault
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* To be added to router types
|
|
142
|
+
*/
|
|
143
|
+
export type UnionToIntersection<T> = (
|
|
144
|
+
T extends any ? (arg: T) => any : never
|
|
145
|
+
) extends (arg: infer T) => any
|
|
146
|
+
? T
|
|
147
|
+
: never
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Merges everything in a union into one object.
|
|
151
|
+
* This mapped type is homomorphic which means it preserves stuff! :)
|
|
152
|
+
*/
|
|
153
|
+
export type MergeAllObjects<
|
|
154
|
+
TUnion,
|
|
155
|
+
TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,
|
|
156
|
+
> = [keyof TIntersected] extends [never]
|
|
157
|
+
? never
|
|
158
|
+
: {
|
|
159
|
+
[TKey in keyof TIntersected]: TUnion extends any
|
|
160
|
+
? TUnion[TKey & keyof TUnion]
|
|
161
|
+
: never
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type MergeAll<TUnion> =
|
|
165
|
+
| MergeAllObjects<TUnion>
|
|
166
|
+
| ExtractPrimitives<TUnion>
|
|
167
|
+
|
|
168
|
+
export type ValidateJSON<T> = ((...args: Array<any>) => any) extends T
|
|
169
|
+
? unknown extends T
|
|
170
|
+
? never
|
|
171
|
+
: 'Function is not serializable'
|
|
172
|
+
: { [K in keyof T]: ValidateJSON<T[K]> }
|
|
173
|
+
|
|
174
|
+
export type LooseReturnType<T> = T extends (
|
|
175
|
+
...args: Array<any>
|
|
176
|
+
) => infer TReturn
|
|
177
|
+
? TReturn
|
|
178
|
+
: never
|
|
179
|
+
|
|
180
|
+
export type LooseAsyncReturnType<T> = T extends (
|
|
181
|
+
...args: Array<any>
|
|
182
|
+
) => infer TReturn
|
|
183
|
+
? TReturn extends Promise<infer TReturn>
|
|
184
|
+
? TReturn
|
|
185
|
+
: TReturn
|
|
186
|
+
: never
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Return the last element of an array.
|
|
190
|
+
* Intended for non-empty arrays used within router internals.
|
|
191
|
+
*/
|
|
192
|
+
export function last<T>(arr: ReadonlyArray<T>) {
|
|
193
|
+
return arr[arr.length - 1]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isFunction(d: any): d is Function {
|
|
197
|
+
return typeof d === 'function'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Apply a value-or-updater to a previous value.
|
|
202
|
+
* Accepts either a literal value or a function of the previous value.
|
|
203
|
+
*/
|
|
204
|
+
export function functionalUpdate<TPrevious, TResult = TPrevious>(
|
|
205
|
+
updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,
|
|
206
|
+
previous: TPrevious,
|
|
207
|
+
): TResult {
|
|
208
|
+
if (isFunction(updater)) {
|
|
209
|
+
return updater(previous)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return updater
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const hasOwn = Object.prototype.hasOwnProperty
|
|
216
|
+
const isEnumerable = Object.prototype.propertyIsEnumerable
|
|
217
|
+
|
|
218
|
+
const createNull = () => Object.create(null)
|
|
219
|
+
export const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>
|
|
220
|
+
replaceEqualDeep(prev, next, createNull)
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* This function returns `prev` if `_next` is deeply equal.
|
|
224
|
+
* If not, it will replace any deeply equal children of `b` with those of `a`.
|
|
225
|
+
* This can be used for structural sharing between immutable JSON values for example.
|
|
226
|
+
* Do not use this with signals
|
|
227
|
+
*/
|
|
228
|
+
export function replaceEqualDeep<T>(
|
|
229
|
+
prev: any,
|
|
230
|
+
_next: T,
|
|
231
|
+
_makeObj = () => ({}),
|
|
232
|
+
_depth = 0,
|
|
233
|
+
): T {
|
|
234
|
+
if (isServer) {
|
|
235
|
+
return _next
|
|
236
|
+
}
|
|
237
|
+
if (prev === _next) {
|
|
238
|
+
return prev
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (_depth > 500) return _next
|
|
242
|
+
|
|
243
|
+
const next = _next as any
|
|
244
|
+
|
|
245
|
+
const array = isPlainArray(prev) && isPlainArray(next)
|
|
246
|
+
|
|
247
|
+
if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next
|
|
248
|
+
|
|
249
|
+
const prevItems = array ? prev : getEnumerableOwnKeys(prev)
|
|
250
|
+
if (!prevItems) return next
|
|
251
|
+
const nextItems = array ? next : getEnumerableOwnKeys(next)
|
|
252
|
+
if (!nextItems) return next
|
|
253
|
+
const prevSize = prevItems.length
|
|
254
|
+
const nextSize = nextItems.length
|
|
255
|
+
const copy: any = array ? new Array(nextSize) : _makeObj()
|
|
256
|
+
|
|
257
|
+
let equalItems = 0
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < nextSize; i++) {
|
|
260
|
+
const key = array ? i : (nextItems[i] as any)
|
|
261
|
+
const p = prev[key]
|
|
262
|
+
const n = next[key]
|
|
263
|
+
|
|
264
|
+
if (p === n) {
|
|
265
|
+
copy[key] = p
|
|
266
|
+
if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
p === null ||
|
|
272
|
+
n === null ||
|
|
273
|
+
typeof p !== 'object' ||
|
|
274
|
+
typeof n !== 'object'
|
|
275
|
+
) {
|
|
276
|
+
copy[key] = n
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)
|
|
281
|
+
copy[key] = v
|
|
282
|
+
if (v === p) equalItems++
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return prevSize === nextSize && equalItems === prevSize ? prev : copy
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Equivalent to `Reflect.ownKeys`, but ensures that objects are "clone-friendly":
|
|
290
|
+
* will return false if object has any non-enumerable properties.
|
|
291
|
+
*
|
|
292
|
+
* Optimized for the common case where objects have no symbol properties.
|
|
293
|
+
*/
|
|
294
|
+
function getEnumerableOwnKeys(o: object) {
|
|
295
|
+
const names = Object.getOwnPropertyNames(o)
|
|
296
|
+
|
|
297
|
+
// Fast path: check all string property names are enumerable
|
|
298
|
+
for (const name of names) {
|
|
299
|
+
if (!isEnumerable.call(o, name)) return false
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Only check symbols if the object has any (most plain objects don't)
|
|
303
|
+
const symbols = Object.getOwnPropertySymbols(o)
|
|
304
|
+
|
|
305
|
+
// Fast path: no symbols, return names directly (avoids array allocation/concat)
|
|
306
|
+
if (symbols.length === 0) return names
|
|
307
|
+
|
|
308
|
+
// Slow path: has symbols, need to check and merge
|
|
309
|
+
const keys: Array<string | symbol> = names
|
|
310
|
+
for (const symbol of symbols) {
|
|
311
|
+
if (!isEnumerable.call(o, symbol)) return false
|
|
312
|
+
keys.push(symbol)
|
|
313
|
+
}
|
|
314
|
+
return keys
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Copied from: https://github.com/jonschlinkert/is-plain-object
|
|
318
|
+
export function isPlainObject(o: any) {
|
|
319
|
+
if (!hasObjectPrototype(o)) {
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// If has modified constructor
|
|
324
|
+
const ctor = o.constructor
|
|
325
|
+
if (typeof ctor === 'undefined') {
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If has modified prototype
|
|
330
|
+
const prot = ctor.prototype
|
|
331
|
+
if (!hasObjectPrototype(prot)) {
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// If constructor does not have an Object-specific method
|
|
336
|
+
if (!prot.hasOwnProperty('isPrototypeOf')) {
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Most likely a plain Object
|
|
341
|
+
return true
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function hasObjectPrototype(o: any) {
|
|
345
|
+
return Object.prototype.toString.call(o) === '[object Object]'
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Check if a value is a "plain" array (no extra enumerable keys).
|
|
350
|
+
*/
|
|
351
|
+
export function isPlainArray(value: unknown): value is Array<unknown> {
|
|
352
|
+
return Array.isArray(value) && value.length === Object.keys(value).length
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Perform a deep equality check with options for partial comparison and
|
|
357
|
+
* ignoring `undefined` values. Optimized for router state comparisons.
|
|
358
|
+
*/
|
|
359
|
+
export function deepEqual(
|
|
360
|
+
a: any,
|
|
361
|
+
b: any,
|
|
362
|
+
opts?: { partial?: boolean; ignoreUndefined?: boolean },
|
|
363
|
+
): boolean {
|
|
364
|
+
if (a === b) {
|
|
365
|
+
return true
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (typeof a !== typeof b) {
|
|
369
|
+
return false
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
373
|
+
if (a.length !== b.length) return false
|
|
374
|
+
for (let i = 0, l = a.length; i < l; i++) {
|
|
375
|
+
if (!deepEqual(a[i], b[i], opts)) return false
|
|
376
|
+
}
|
|
377
|
+
return true
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
381
|
+
const ignoreUndefined = opts?.ignoreUndefined ?? true
|
|
382
|
+
|
|
383
|
+
if (opts?.partial) {
|
|
384
|
+
for (const k in b) {
|
|
385
|
+
if (!ignoreUndefined || b[k] !== undefined) {
|
|
386
|
+
if (!deepEqual(a[k], b[k], opts)) return false
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return true
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let aCount = 0
|
|
393
|
+
if (!ignoreUndefined) {
|
|
394
|
+
aCount = Object.keys(a).length
|
|
395
|
+
} else {
|
|
396
|
+
for (const k in a) {
|
|
397
|
+
if (a[k] !== undefined) aCount++
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let bCount = 0
|
|
402
|
+
for (const k in b) {
|
|
403
|
+
if (!ignoreUndefined || b[k] !== undefined) {
|
|
404
|
+
bCount++
|
|
405
|
+
if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return aCount === bCount
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export type StringLiteral<T> = T extends string
|
|
416
|
+
? string extends T
|
|
417
|
+
? string
|
|
418
|
+
: T
|
|
419
|
+
: never
|
|
420
|
+
|
|
421
|
+
export type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true
|
|
422
|
+
? T
|
|
423
|
+
: T | undefined
|
|
424
|
+
|
|
425
|
+
export type StrictOrFrom<
|
|
426
|
+
TRouter extends AnyRouter,
|
|
427
|
+
TFrom,
|
|
428
|
+
TStrict extends boolean = true,
|
|
429
|
+
> = TStrict extends false
|
|
430
|
+
? {
|
|
431
|
+
from?: never
|
|
432
|
+
strict: TStrict
|
|
433
|
+
}
|
|
434
|
+
: {
|
|
435
|
+
from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>
|
|
436
|
+
strict?: TStrict
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export type ThrowConstraint<
|
|
440
|
+
TStrict extends boolean,
|
|
441
|
+
TThrow extends boolean,
|
|
442
|
+
> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow
|
|
443
|
+
|
|
444
|
+
export type ControlledPromise<T> = Promise<T> & {
|
|
445
|
+
resolve: (value: T) => void
|
|
446
|
+
reject: (value: any) => void
|
|
447
|
+
status: 'pending' | 'resolved' | 'rejected'
|
|
448
|
+
value?: T
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create a promise with exposed resolve/reject and status fields.
|
|
453
|
+
* Useful for coordinating async router lifecycle operations.
|
|
454
|
+
*/
|
|
455
|
+
export function createControlledPromise<T>(onResolve?: (value: T) => void) {
|
|
456
|
+
let resolveLoadPromise!: (value: T) => void
|
|
457
|
+
let rejectLoadPromise!: (value: any) => void
|
|
458
|
+
|
|
459
|
+
const controlledPromise = new Promise<T>((resolve, reject) => {
|
|
460
|
+
resolveLoadPromise = resolve
|
|
461
|
+
rejectLoadPromise = reject
|
|
462
|
+
}) as ControlledPromise<T>
|
|
463
|
+
|
|
464
|
+
controlledPromise.status = 'pending'
|
|
465
|
+
|
|
466
|
+
controlledPromise.resolve = (value: T) => {
|
|
467
|
+
controlledPromise.status = 'resolved'
|
|
468
|
+
controlledPromise.value = value
|
|
469
|
+
resolveLoadPromise(value)
|
|
470
|
+
onResolve?.(value)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
controlledPromise.reject = (e) => {
|
|
474
|
+
controlledPromise.status = 'rejected'
|
|
475
|
+
rejectLoadPromise(e)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return controlledPromise
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Heuristically detect dynamic import "module not found" errors
|
|
483
|
+
* across major browsers for lazy route component handling.
|
|
484
|
+
*/
|
|
485
|
+
export function isModuleNotFoundError(error: any): boolean {
|
|
486
|
+
// chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
|
|
487
|
+
// firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
|
|
488
|
+
// safari: "Importing a module script failed."
|
|
489
|
+
if (typeof error?.message !== 'string') return false
|
|
490
|
+
return (
|
|
491
|
+
error.message.startsWith('Failed to fetch dynamically imported module') ||
|
|
492
|
+
error.message.startsWith('error loading dynamically imported module') ||
|
|
493
|
+
error.message.startsWith('Importing a module script failed')
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function isPromise<T>(
|
|
498
|
+
value: Promise<Awaited<T>> | T,
|
|
499
|
+
): value is Promise<Awaited<T>> {
|
|
500
|
+
return Boolean(
|
|
501
|
+
value &&
|
|
502
|
+
typeof value === 'object' &&
|
|
503
|
+
typeof (value as Promise<T>).then === 'function',
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export function findLast<T>(
|
|
508
|
+
array: ReadonlyArray<T>,
|
|
509
|
+
predicate: (item: T) => boolean,
|
|
510
|
+
): T | undefined {
|
|
511
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
512
|
+
const item = array[i]!
|
|
513
|
+
if (predicate(item)) return item
|
|
514
|
+
}
|
|
515
|
+
return undefined
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Remove control characters that can cause open redirect vulnerabilities.
|
|
520
|
+
* Characters like \r (CR) and \n (LF) can trick URL parsers into interpreting
|
|
521
|
+
* paths like "/\r/evil.com" as "http://evil.com".
|
|
522
|
+
*/
|
|
523
|
+
function sanitizePathSegment(segment: string): string {
|
|
524
|
+
// Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)
|
|
525
|
+
// These include CR (\r = 0x0D), LF (\n = 0x0A), and other potentially dangerous characters
|
|
526
|
+
// eslint-disable-next-line no-control-regex
|
|
527
|
+
return segment.replace(/[\x00-\x1f\x7f]/g, '')
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function decodeSegment(segment: string): string {
|
|
531
|
+
let decoded: string
|
|
532
|
+
try {
|
|
533
|
+
decoded = decodeURI(segment)
|
|
534
|
+
} catch {
|
|
535
|
+
// if the decoding fails, try to decode the various parts leaving the malformed tags in place
|
|
536
|
+
decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
|
|
537
|
+
try {
|
|
538
|
+
return decodeURI(match)
|
|
539
|
+
} catch {
|
|
540
|
+
return match
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
return sanitizePathSegment(decoded)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Default list of URL protocols to allow in links, redirects, and navigation.
|
|
549
|
+
* Any absolute URL protocol not in this list is treated as dangerous by default.
|
|
550
|
+
*/
|
|
551
|
+
export const DEFAULT_PROTOCOL_ALLOWLIST = [
|
|
552
|
+
// Standard web navigation
|
|
553
|
+
'http:',
|
|
554
|
+
'https:',
|
|
555
|
+
|
|
556
|
+
// Common browser-safe actions
|
|
557
|
+
'mailto:',
|
|
558
|
+
'tel:',
|
|
559
|
+
]
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Check if a URL string uses a protocol that is not in the allowlist.
|
|
563
|
+
* Returns true for blocked protocols like javascript:, blob:, data:, etc.
|
|
564
|
+
*
|
|
565
|
+
* The URL constructor correctly normalizes:
|
|
566
|
+
* - Mixed case (JavaScript: → javascript:)
|
|
567
|
+
* - Whitespace/control characters (java\nscript: → javascript:)
|
|
568
|
+
* - Leading whitespace
|
|
569
|
+
*
|
|
570
|
+
* For relative URLs (no protocol), returns false (safe).
|
|
571
|
+
*
|
|
572
|
+
* @param url - The URL string to check
|
|
573
|
+
* @param allowlist - Set of protocols to allow
|
|
574
|
+
* @returns true if the URL uses a protocol that is not allowed
|
|
575
|
+
*/
|
|
576
|
+
export function isDangerousProtocol(
|
|
577
|
+
url: string,
|
|
578
|
+
allowlist: Set<string>,
|
|
579
|
+
): boolean {
|
|
580
|
+
if (!url) return false
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
// Use the URL constructor - it correctly normalizes protocols
|
|
584
|
+
// per WHATWG URL spec, handling all bypass attempts automatically
|
|
585
|
+
const parsed = new URL(url)
|
|
586
|
+
return !allowlist.has(parsed.protocol)
|
|
587
|
+
} catch {
|
|
588
|
+
// URL constructor throws for relative URLs (no protocol)
|
|
589
|
+
// These are safe - they can't execute scripts
|
|
590
|
+
return false
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// This utility is based on https://github.com/zertosh/htmlescape
|
|
595
|
+
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
|
|
596
|
+
const HTML_ESCAPE_LOOKUP: { [match: string]: string } = {
|
|
597
|
+
'&': '\\u0026',
|
|
598
|
+
'>': '\\u003e',
|
|
599
|
+
'<': '\\u003c',
|
|
600
|
+
'\u2028': '\\u2028',
|
|
601
|
+
'\u2029': '\\u2029',
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const HTML_ESCAPE_REGEX = /[&><\u2028\u2029]/g
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Escape HTML special characters in a string to prevent XSS attacks
|
|
608
|
+
* when embedding strings in script tags during SSR.
|
|
609
|
+
*
|
|
610
|
+
* This is essential for preventing XSS vulnerabilities when user-controlled
|
|
611
|
+
* content is embedded in inline scripts.
|
|
612
|
+
*/
|
|
613
|
+
export function escapeHtml(str: string): string {
|
|
614
|
+
return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function decodePath(path: string) {
|
|
618
|
+
if (!path) return { path, handledProtocolRelativeURL: false }
|
|
619
|
+
|
|
620
|
+
// Fast path: most paths are already decoded and safe.
|
|
621
|
+
// Only fall back to the slower scan/regex path when we see a '%' (encoded),
|
|
622
|
+
// a backslash (explicitly handled), a control character, or a protocol-relative
|
|
623
|
+
// prefix which needs collapsing.
|
|
624
|
+
// eslint-disable-next-line no-control-regex
|
|
625
|
+
if (!/[%\\\x00-\x1f\x7f]/.test(path) && !path.startsWith('//')) {
|
|
626
|
+
return { path, handledProtocolRelativeURL: false }
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const re = /%25|%5C/gi
|
|
630
|
+
let cursor = 0
|
|
631
|
+
let result = ''
|
|
632
|
+
let match
|
|
633
|
+
while (null !== (match = re.exec(path))) {
|
|
634
|
+
result += decodeSegment(path.slice(cursor, match.index)) + match[0]
|
|
635
|
+
cursor = re.lastIndex
|
|
636
|
+
}
|
|
637
|
+
result = result + decodeSegment(cursor ? path.slice(cursor) : path)
|
|
638
|
+
|
|
639
|
+
// Prevent open redirect via protocol-relative URLs (e.g. "//evil.com")
|
|
640
|
+
// After sanitizing control characters, paths like "/\r/evil.com" become "//evil.com"
|
|
641
|
+
// Collapse leading double slashes to a single slash
|
|
642
|
+
let handledProtocolRelativeURL = false
|
|
643
|
+
if (result.startsWith('//')) {
|
|
644
|
+
handledProtocolRelativeURL = true
|
|
645
|
+
result = '/' + result.replace(/^\/+/, '')
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return { path: result, handledProtocolRelativeURL }
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.
|
|
653
|
+
*
|
|
654
|
+
* This function encodes:
|
|
655
|
+
* - Whitespace characters (spaces → %20, tabs → %09, etc.)
|
|
656
|
+
* - Non-ASCII/Unicode characters (emojis, accented characters, etc.)
|
|
657
|
+
*
|
|
658
|
+
* It preserves:
|
|
659
|
+
* - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)
|
|
660
|
+
* - ASCII special characters valid in URL paths (@, $, &, +, etc.)
|
|
661
|
+
* - Forward slashes as path separators
|
|
662
|
+
*
|
|
663
|
+
* Used to generate proper href values for SSR without constructing URL objects.
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'
|
|
667
|
+
* encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'
|
|
668
|
+
* encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)
|
|
669
|
+
*/
|
|
670
|
+
export function encodePathLikeUrl(path: string): string {
|
|
671
|
+
// Encode whitespace and non-ASCII characters that browsers encode in URLs
|
|
672
|
+
|
|
673
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check
|
|
674
|
+
// eslint-disable-next-line no-control-regex
|
|
675
|
+
if (!/\s|[^\u0000-\u007F]/.test(path)) return path
|
|
676
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check
|
|
677
|
+
// eslint-disable-next-line no-control-regex
|
|
678
|
+
return path.replace(/\s|[^\u0000-\u007F]/gu, encodeURIComponent)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Builds the dev-mode CSS styles URL for route-scoped CSS collection.
|
|
683
|
+
* Used by HeadContent components in all framework implementations to construct
|
|
684
|
+
* the URL for the `/@tanstack-start/styles.css` endpoint.
|
|
685
|
+
*
|
|
686
|
+
* @param basepath - The router's basepath (may or may not have leading slash)
|
|
687
|
+
* @param routeIds - Array of matched route IDs to include in the CSS collection
|
|
688
|
+
* @returns The full URL path for the dev styles CSS endpoint
|
|
689
|
+
*/
|
|
690
|
+
export function buildDevStylesUrl(
|
|
691
|
+
basepath: string,
|
|
692
|
+
routeIds: Array<string>,
|
|
693
|
+
): string {
|
|
694
|
+
// Trim all leading and trailing slashes from basepath
|
|
695
|
+
const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, '')
|
|
696
|
+
// Build normalized basepath: empty string for root, or '/path' for non-root
|
|
697
|
+
const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`
|
|
698
|
+
return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export function arraysEqual<T>(a: Array<T>, b: Array<T>) {
|
|
702
|
+
if (a === b) return true
|
|
703
|
+
if (a.length !== b.length) return false
|
|
704
|
+
for (let i = 0; i < a.length; i++) {
|
|
705
|
+
if (a[i] !== b[i]) return false
|
|
706
|
+
}
|
|
707
|
+
return true
|
|
708
|
+
}
|