diff --git a/examples/react/router-monorepo-react-query/packages/router/tsconfig.json b/examples/react/router-monorepo-react-query/packages/router/tsconfig.json index ce3a7d2339..0d04c7789d 100644 --- a/examples/react/router-monorepo-react-query/packages/router/tsconfig.json +++ b/examples/react/router-monorepo-react-query/packages/router/tsconfig.json @@ -8,5 +8,6 @@ "module": "ESNext", "lib": ["DOM", "DOM.Iterable", "ES2022"], "skipLibCheck": true - } + }, + "include": ["src"] } diff --git a/examples/react/router-monorepo-simple/packages/router/tsconfig.json b/examples/react/router-monorepo-simple/packages/router/tsconfig.json index ce3a7d2339..0d04c7789d 100644 --- a/examples/react/router-monorepo-simple/packages/router/tsconfig.json +++ b/examples/react/router-monorepo-simple/packages/router/tsconfig.json @@ -8,5 +8,6 @@ "module": "ESNext", "lib": ["DOM", "DOM.Iterable", "ES2022"], "skipLibCheck": true - } + }, + "include": ["src"] } diff --git a/package.json b/package.json index 93fab65511..5e37a09a72 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@tanstack/react-query": "5.64.2", "use-sync-external-store": "1.2.2", "@tanstack/history": "workspace:*", + "@tanstack/router-core": "workspace:*", "@tanstack/react-cross-context": "workspace:*", "@tanstack/react-router": "workspace:*", "@tanstack/router-cli": "workspace:*", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 29886faeb6..f384765635 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -68,6 +68,7 @@ "dependencies": { "@tanstack/history": "workspace:*", "@tanstack/react-store": "^0.7.0", + "@tanstack/router-core": "workspace:^", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" diff --git a/packages/react-router/src/Match.tsx b/packages/react-router/src/Match.tsx index dd5e4f0ddd..5240072ad1 100644 --- a/packages/react-router/src/Match.tsx +++ b/packages/react-router/src/Match.tsx @@ -3,16 +3,19 @@ import * as React from 'react' import invariant from 'tiny-invariant' import warning from 'tiny-warning' +import { + createControlledPromise, + pick, + rootRouteId, +} from '@tanstack/router-core' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' -import { createControlledPromise, pick } from './utils' import { CatchNotFound, isNotFound } from './not-found' import { isRedirect } from './redirects' import { matchContext } from './matchContext' import { SafeFragment } from './SafeFragment' import { renderRouteNotFound } from './renderRouteNotFound' -import { rootRouteId } from './root' import type { AnyRoute } from './route' export const Match = React.memo(function MatchImpl({ diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx index a70ed9e9a8..40ffc3951e 100644 --- a/packages/react-router/src/Matches.tsx +++ b/packages/react-router/src/Matches.tsx @@ -11,13 +11,19 @@ import type { StructuralSharingOption, ValidateSelected, } from './structuralSharing' -import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route' +import type { AnyRoute, ReactNode } from './route' +import type { + ControlledPromise, + DeepPartial, + NoInfer, + ResolveRelativePath, + StaticDataRouteOption, +} from '@tanstack/router-core' import type { AnyRouter, RegisteredRouter, RouterState } from './router' import type { MakeOptionalPathParams, MakeOptionalSearchParams, MaskOptions, - ResolveRelativePath, ResolveRoute, ToSubOptionsProps, } from './link' @@ -31,105 +37,6 @@ import type { RouteByPath, RouteIds, } from './routeInfo' -import type { - Constrain, - ControlledPromise, - DeepPartial, - NoInfer, -} from './utils' - -export type AnyMatchAndValue = { match: any; value: any } - -export type FindValueByIndex< - TKey, - TValue extends ReadonlyArray, -> = TKey extends `${infer TIndex extends number}` ? TValue[TIndex] : never - -export type FindValueByKey = - TValue extends ReadonlyArray - ? FindValueByIndex - : TValue[TKey & keyof TValue] - -export type CreateMatchAndValue = TValue extends any - ? { - match: TMatch - value: TValue - } - : never - -export type NextMatchAndValue< - TKey, - TMatchAndValue extends AnyMatchAndValue, -> = TMatchAndValue extends any - ? CreateMatchAndValue< - TMatchAndValue['match'], - FindValueByKey - > - : never - -export type IsMatchKeyOf = - TValue extends ReadonlyArray - ? number extends TValue['length'] - ? `${number}` - : keyof TValue & `${number}` - : TValue extends object - ? keyof TValue & string - : never - -export type IsMatchPath< - TParentPath extends string, - TMatchAndValue extends AnyMatchAndValue, -> = `${TParentPath}${IsMatchKeyOf}` - -export type IsMatchResult< - TKey, - TMatchAndValue extends AnyMatchAndValue, -> = TMatchAndValue extends any - ? TKey extends keyof TMatchAndValue['value'] - ? TMatchAndValue['match'] - : never - : never - -export type IsMatchParse< - TPath, - TMatchAndValue extends AnyMatchAndValue, - TParentPath extends string = '', -> = TPath extends `${string}.${string}` - ? TPath extends `${infer TFirst}.${infer TRest}` - ? IsMatchParse< - TRest, - NextMatchAndValue, - `${TParentPath}${TFirst}.` - > - : never - : { - path: IsMatchPath - result: IsMatchResult - } - -export type IsMatch = IsMatchParse< - TPath, - TMatch extends any ? { match: TMatch; value: TMatch } : never -> - -/** - * Narrows matches based on a path - * @experimental - */ -export const isMatch = ( - match: TMatch, - path: Constrain['path']>, -): match is IsMatch['result'] => { - const parts = (path as string).split('.') - let part - let value: any = match - - while ((part = parts.shift()) != null && value != null) { - value = value[part] - } - - return value != null -} export type MakeRouteMatchFromRoute = RouteMatch< TRoute['types']['id'], diff --git a/packages/react-router/src/RouterProvider.tsx b/packages/react-router/src/RouterProvider.tsx index 59f8e53347..ad499eaa89 100644 --- a/packages/react-router/src/RouterProvider.tsx +++ b/packages/react-router/src/RouterProvider.tsx @@ -2,14 +2,16 @@ import * as React from 'react' import { Matches } from './Matches' import { getRouterContext } from './routerContext' import type { NavigateOptions, ToOptions } from './link' -import type { ParsedLocation } from './location' +import type { + ParsedLocation, + ViewTransitionOptions, +} from '@tanstack/router-core' import type { RoutePaths } from './routeInfo' import type { AnyRouter, RegisteredRouter, Router, RouterOptions, - ViewTransitionOptions, } from './router' export interface CommitLocationOptions { diff --git a/packages/react-router/src/Transitioner.tsx b/packages/react-router/src/Transitioner.tsx index 693f2fd070..7d25ecdc41 100644 --- a/packages/react-router/src/Transitioner.tsx +++ b/packages/react-router/src/Transitioner.tsx @@ -1,8 +1,8 @@ import * as React from 'react' +import { trimPathRight } from '@tanstack/router-core' import { useLayoutEffect, usePrevious } from './utils' import { useRouter } from './useRouter' import { useRouterState } from './useRouterState' -import { trimPathRight } from './path' export function Transitioner() { const router = useRouter() diff --git a/packages/react-router/src/awaited.tsx b/packages/react-router/src/awaited.tsx index 1cba67a490..f695b33e8c 100644 --- a/packages/react-router/src/awaited.tsx +++ b/packages/react-router/src/awaited.tsx @@ -1,7 +1,7 @@ import * as React from 'react' -import { TSR_DEFERRED_PROMISE, defer } from './defer' -import type { DeferredPromise } from './defer' +import { TSR_DEFERRED_PROMISE, defer } from '@tanstack/router-core' +import type { DeferredPromise } from '@tanstack/router-core' export type AwaitOptions = { promise: Promise diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts index ed2374a85a..242ea27662 100644 --- a/packages/react-router/src/fileRoute.ts +++ b/packages/react-router/src/fileRoute.ts @@ -10,13 +10,17 @@ import { useNavigate } from './useNavigate' import type { UseParamsRoute } from './useParams' import type { UseMatchRoute } from './useMatch' import type { UseSearchRoute } from './useSearch' -import type { Constrain } from './utils' import type { AnyContext, AnyPathParams, + AnyValidator, + Constrain, + ResolveParams, +} from '@tanstack/router-core' + +import type { AnyRoute, FileBaseRouteOptions, - ResolveParams, RootRoute, Route, RouteConstraints, @@ -25,7 +29,6 @@ import type { } from './route' import type { RegisteredRouter } from './router' import type { RouteById, RouteIds } from './routeInfo' -import type { AnyValidator } from './validators' import type { UseLoaderDepsRoute } from './useLoaderDeps' import type { UseLoaderDataRoute } from './useLoaderData' import type { UseRouteContextRoute } from './useRouteContext' diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx index 34ee024a57..654a71c8fe 100644 --- a/packages/react-router/src/index.tsx +++ b/packages/react-router/src/index.tsx @@ -1,9 +1,134 @@ +export { default as invariant } from 'tiny-invariant' +export { default as warning } from 'tiny-warning' + +export { + defer, + TSR_DEFERRED_PROMISE, + isMatch, + joinPaths, + cleanPath, + trimPathLeft, + trimPathRight, + trimPath, + resolvePath, + parsePathname, + interpolatePath, + matchPathname, + removeBasepath, + matchByPath, + encode, + decode, + rootRouteId, + defaultSerializeError, + defaultParseSearch, + defaultStringifySearch, + parseSearchWith, + stringifySearchWith, + escapeJSON, // SSR + pick, + functionalUpdate, + replaceEqualDeep, + isPlainObject, + isPlainArray, + deepEqual, + shallow, + createControlledPromise, + retainSearchParams, + stripSearchParams, +} from '@tanstack/router-core' + +export type { + StartSerializer, + Serializable, + SerializerParse, + SerializerParseBy, + SerializerStringify, + SerializerStringifyBy, + DeferredPromiseState, + DeferredPromise, + ParsedLocation, + ParsePathParams, + RemoveTrailingSlashes, + RemoveLeadingSlashes, + ActiveOptions, + Segment, + ResolveRelativePath, + RootRouteId, + AnyPathParams, + ResolveParams, + SearchSchemaInput, + AnyContext, + RouteContext, + PreloadableObj, + RoutePathOptions, + StaticDataRouteOption, + RoutePathOptionsIntersection, + UpdatableStaticRouteOption, + MetaDescriptor, + RouteLinkEntry, + ParseParamsFn, + SearchFilter, + ResolveId, + InferFullSearchSchema, + InferFullSearchSchemaInput, + ErrorRouteProps, + ErrorComponentProps, + NotFoundRouteProps, + TrimPath, + TrimPathLeft, + TrimPathRight, + ParseSplatParams, + SplatParams, + StringifyParamsFn, + ParamsOptions, + InferAllParams, + InferAllContext, + LooseReturnType, + LooseAsyncReturnType, + ContextReturnType, + ContextAsyncReturnType, + ResolveLoaderData, + ResolveRouteContext, + SearchSerializer, + SearchParser, + TrailingSlashOption, + ExtractedEntry, + ExtractedStream, + ExtractedPromise, + StreamState, + Manifest, + RouterManagedTag, + ControlledPromise, + Constrain, + Expand, + MergeAll, + Assign, + IntersectAssign, + ResolveValidatorInput, + ResolveValidatorOutput, + AnyValidator, + DefaultValidator, + ValidatorFn, + AnySchema, + AnyValidatorAdapter, + AnyValidatorFn, + AnyValidatorObj, + ResolveValidatorInputFn, + ResolveValidatorOutputFn, + ResolveSearchValidatorInput, + ResolveSearchValidatorInputFn, + Validator, + ValidatorAdapter, + ValidatorObj, +} from '@tanstack/router-core' + export { createHistory, createBrowserHistory, createHashHistory, createMemoryHistory, } from '@tanstack/history' + export type { BlockerFn, HistoryLocation, @@ -11,15 +136,10 @@ export type { ParsedPath, HistoryState, } from '@tanstack/history' -export { default as invariant } from 'tiny-invariant' -export { default as warning } from 'tiny-warning' export { useAwaited, Await } from './awaited' export type { AwaitOptions } from './awaited' -export { defer, TSR_DEFERRED_PROMISE } from './defer' -export type { DeferredPromiseState, DeferredPromise } from './defer' - export { CatchBoundary, ErrorComponent } from './CatchBoundary' export { @@ -42,9 +162,6 @@ export { lazyRouteComponent } from './lazyRouteComponent' export { useLinkProps, createLink, Link, linkOptions } from './link' export type { - ParsePathParams, - RemoveTrailingSlashes, - RemoveLeadingSlashes, InferDescendantToPaths, RelativeToPath, RelativeToParentPath, @@ -59,9 +176,7 @@ export type { SearchParamOptions, PathParamOptions, ToPathOption, - ActiveOptions, LinkOptions, - ResolveRelativePath, UseLinkPropsOptions, ActiveLinkOptions, LinkProps, @@ -71,8 +186,6 @@ export type { MakeOptionalPathParams, } from './link' -export type { ParsedLocation } from './location' - export { Matches, useMatchRoute, @@ -80,8 +193,8 @@ export { useMatches, useParentMatches, useChildMatches, - isMatch, } from './Matches' + export type { RouteMatch, AnyRouteMatch, @@ -94,33 +207,14 @@ export type { export { matchContext } from './matchContext' export { Match, Outlet } from './Match' + export { useMatch } from './useMatch' export { useLoaderDeps } from './useLoaderDeps' export { useLoaderData } from './useLoaderData' -export { - joinPaths, - cleanPath, - trimPathLeft, - trimPathRight, - trimPath, - resolvePath, - parsePathname, - interpolatePath, - matchPathname, - removeBasepath, - matchByPath, -} from './path' -export type { Segment } from './path' - -export { encode, decode } from './qss' - export { redirect, isRedirect } from './redirects' export type { AnyRedirect, Redirect, ResolvedRedirect } from './redirects' -export { rootRouteId } from './root' -export type { RootRouteId } from './root' - export { RouteApi, getRouteApi, @@ -134,29 +228,12 @@ export { NotFoundRoute, } from './route' export type { - AnyPathParams, - ResolveParams, - SearchSchemaInput, - AnyContext, - RouteContext, - PreloadableObj, - RoutePathOptions, - StaticDataRouteOption, - RoutePathOptionsIntersection, RouteOptions, FileBaseRouteOptions, BaseRouteOptions, UpdatableRouteOptions, - UpdatableStaticRouteOption, - MetaDescriptor, - RouteLinkEntry, - ParseParamsFn, RouteLoaderFn, LoaderFnContext, - SearchFilter, - ResolveId, - InferFullSearchSchema, - InferFullSearchSchemaInput, ResolveFullSearchSchema, ResolveFullSearchSchemaInput, AnyRoute, @@ -164,42 +241,24 @@ export type { AnyRootRoute, ResolveFullPath, RouteMask, - ErrorRouteProps, - ErrorComponentProps, - NotFoundRouteProps, ReactNode, SyncRouteComponent, AsyncRouteComponent, RouteComponent, ErrorRouteComponent, NotFoundRouteComponent, - TrimPath, - TrimPathLeft, - TrimPathRight, RootRouteOptions, AnyRouteWithContext, - ParseSplatParams, - SplatParams, - StringifyParamsFn, - ParamsOptions, FullSearchSchemaOption, RouteContextFn, RouteContextOptions, BeforeLoadFn, BeforeLoadContextOptions, ContextOptions, - InferAllParams, - InferAllContext, - LooseReturnType, - LooseAsyncReturnType, - ContextReturnType, - ContextAsyncReturnType, RouteContextParameter, BeforeLoadContextParameter, ResolveAllContext, - ResolveLoaderData, ResolveAllParamsFromParent, - ResolveRouteContext, } from './route' export type { @@ -225,14 +284,13 @@ export { SearchParamError, PathParamError, getInitialRouterState, - defaultSerializeError, } from './router' + export type { Register, AnyRouter, RegisteredRouter, RouterContextOptions, - TrailingSlashOption, RouterOptions, RouterErrorSerializer, RouterState, @@ -247,15 +305,6 @@ export type { InjectedHtmlEntry, } from './router' -export type { - StartSerializer, - Serializable, - SerializerParse, - SerializerParseBy, - SerializerStringify, - SerializerStringifyBy, -} from './serializer' - export { RouterProvider, RouterContextProvider } from './RouterProvider' export type { RouterProps, @@ -272,14 +321,6 @@ export { } from './scroll-restoration' export type { ScrollRestorationOptions } from './scroll-restoration' -export { - defaultParseSearch, - defaultStringifySearch, - parseSearchWith, - stringifySearchWith, -} from './searchParams' -export type { SearchSerializer, SearchParser } from './searchParams' - export type { UseBlockerOpts, ShouldBlockFn } from './useBlocker' export { useBlocker, Block } from './useBlocker' @@ -300,16 +341,8 @@ export { useLocation } from './useLocation' export { useCanGoBack } from './useCanGoBack' export { - escapeJSON, // SSR useLayoutEffect, // SSR - pick, - functionalUpdate, - replaceEqualDeep, - isPlainObject, - isPlainArray, - deepEqual, useStableCallback, - shallow, } from './utils' export { @@ -320,39 +353,6 @@ export { } from './not-found' export type { NotFoundError } from './not-found' -export type { Manifest, RouterManagedTag } from './manifest' - -export { createControlledPromise } from './utils' -export type { - ControlledPromise, - Constrain, - Expand, - MergeAll, - Assign, - IntersectAssign, -} from './utils' - -export type { - ResolveValidatorInput, - ResolveValidatorOutput, - AnyValidator, - DefaultValidator, - ValidatorFn, - AnySchema, - AnyValidatorAdapter, - AnyValidatorFn, - AnyValidatorObj, - ResolveValidatorInputFn, - ResolveValidatorOutputFn, - ResolveSearchValidatorInput, - ResolveSearchValidatorInputFn, - Validator, - ValidatorAdapter, - ValidatorObj, -} from './validators' - -export { retainSearchParams, stripSearchParams } from './searchMiddleware' - export * from './typePrimitives' export { ScriptOnce } from './ScriptOnce' diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx index ae2837a64b..394eb73668 100644 --- a/packages/react-router/src/link.tsx +++ b/packages/react-router/src/link.tsx @@ -2,18 +2,40 @@ import * as React from 'react' import { flushSync } from 'react-dom' -import { useRouterState } from './useRouterState' -import { useRouter } from './useRouter' import { deepEqual, + exactPathTest, functionalUpdate, + preloadWarning, + removeTrailingSlash, +} from '@tanstack/router-core' +import { useRouterState } from './useRouterState' +import { useRouter } from './useRouter' + +import { useForwardedRef, useIntersectionObserver, useLayoutEffect, } from './utils' -import { exactPathTest, removeTrailingSlash } from './path' + import { useMatch } from './useMatch' -import type { ParsedLocation } from './location' +import type { + Constrain, + ConstrainLiteral, + Expand, + IsRequiredParams, + LinkOptionsProps, + MakeDifferenceOptional, + NoInfer, + NonNullableUpdater, + ParsedLocation, + PickRequired, + RemoveTrailingSlashes, + ResolveRelativePath, + Updater, + ViewTransitionOptions, + WithoutEmpty, +} from '@tanstack/router-core' import type { HistoryState, ParsedHistoryState } from '@tanstack/history' import type { AllParams, @@ -28,57 +50,13 @@ import type { RouteToPath, ToPath, } from './routeInfo' -import type { - AnyRouter, - RegisteredRouter, - ViewTransitionOptions, -} from './router' -import type { - Constrain, - ConstrainLiteral, - Expand, - MakeDifferenceOptional, - NoInfer, - NonNullableUpdater, - PickRequired, - Updater, - WithoutEmpty, -} from './utils' +import type { AnyRouter, RegisteredRouter } from './router' import type { ReactNode } from 'react' import type { ValidateLinkOptions, ValidateLinkOptionsArray, } from './typePrimitives' -export type ParsePathParams = T & - `${string}$${string}` extends never - ? TAcc - : T extends `${string}$${infer TPossiblyParam}` - ? TPossiblyParam extends '' - ? TAcc - : TPossiblyParam & `${string}/${string}` extends never - ? TPossiblyParam | TAcc - : TPossiblyParam extends `${infer TParam}/${infer TRest}` - ? ParsePathParams - : never - : TAcc - -export type AddTrailingSlash = T extends `${string}/` ? T : `${T & string}/` - -export type RemoveTrailingSlashes = T extends `${string}/` - ? T extends `${infer R}/` - ? R - : T - : T - -export type AddLeadingSlash = T extends `/${string}` ? T : `/${T & string}` - -export type RemoveLeadingSlashes = T extends `/${string}` - ? T extends `/${infer R}` - ? R - : T - : T - export type FindDescendantToPaths< TRouter extends AnyRouter, TPrefix extends string, @@ -404,9 +382,6 @@ export interface MakeRequiredSearchParams< search: MakeRequiredParamsReducer & {} } -export type IsRequiredParams = - Record extends TParams ? never : true - export type IsRequired< TRouter extends AnyRouter, TParamVariant extends ParamVariant, @@ -451,13 +426,6 @@ export type FromPathOption = ConstrainLiteral< RoutePaths > -export interface ActiveOptions { - exact?: boolean - includeHash?: boolean - includeSearch?: boolean - explicitUndefined?: boolean -} - export type LinkOptions< TRouter extends AnyRouter = RegisteredRouter, TFrom extends string = string, @@ -466,99 +434,6 @@ export type LinkOptions< TMaskTo extends string = '.', > = NavigateOptions & LinkOptionsProps -export interface LinkOptionsProps { - /** - * The standard anchor tag target attribute - */ - target?: HTMLAnchorElement['target'] - /** - * Configurable options to determine if the link should be considered active or not - * @default {exact:true,includeHash:true} - */ - activeOptions?: ActiveOptions - /** - * The preloading strategy for this link - * - `false` - No preloading - * - `'intent'` - Preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there. - * - `'viewport'` - Preload the linked route when it enters the viewport - */ - preload?: false | 'intent' | 'viewport' | 'render' - /** - * When a preload strategy is set, this delays the preload by this many milliseconds. - * If the user exits the link before this delay, the preload will be cancelled. - */ - preloadDelay?: number - /** - * Control whether the link should be disabled or not - * If set to `true`, the link will be rendered without an `href` attribute - * @default false - */ - disabled?: boolean -} - -type JoinPath = TRight extends '' - ? TLeft - : TLeft extends '' - ? TRight - : `${RemoveTrailingSlashes}/${RemoveLeadingSlashes}` - -type RemoveLastSegment< - T extends string, - TAcc extends string = '', -> = T extends `${infer TSegment}/${infer TRest}` - ? TRest & `${string}/${string}` extends never - ? TRest extends '' - ? TAcc - : `${TAcc}${TSegment}` - : RemoveLastSegment - : TAcc - -export type ResolveCurrentPath< - TFrom extends string, - TTo extends string, -> = TTo extends '.' - ? TFrom - : TTo extends './' - ? AddTrailingSlash - : TTo & `./${string}` extends never - ? never - : TTo extends `./${infer TRest}` - ? AddLeadingSlash> - : never - -export type ResolveParentPath< - TFrom extends string, - TTo extends string, -> = TTo extends '../' | '..' - ? TFrom extends '' | '/' - ? never - : AddLeadingSlash> - : TTo & `../${string}` extends never - ? AddLeadingSlash> - : TFrom extends '' | '/' - ? never - : TTo extends `../${infer ToRest}` - ? ResolveParentPath, ToRest> - : AddLeadingSlash> - -export type ResolveRelativePath = string extends TFrom - ? TTo - : string extends TTo - ? TFrom - : undefined extends TTo - ? TFrom - : TTo extends string - ? TFrom extends string - ? TTo extends `/${string}` - ? TTo - : TTo extends `..${string}` - ? ResolveParentPath - : TTo extends `.${string}` - ? ResolveCurrentPath - : AddLeadingSlash> - : never - : never - // type Test1 = ResolveRelativePath<'/', '/posts'> // // ^? // type Test4 = ResolveRelativePath<'/posts/1/comments', '../..'> @@ -577,13 +452,10 @@ export type ResolveRelativePath = string extends TFrom // // ^? // type Test11 = ResolveRelativePath<'/posts/1/comments', './1/2'> // // ^? - type LinkCurrentTargetElement = { preloadTimeout?: null | ReturnType } -const preloadWarning = 'Error preloading route! ☝️' - export function useLinkProps< TRouter extends AnyRouter = RegisteredRouter, const TFrom extends string = string, diff --git a/packages/react-router/src/redirects.ts b/packages/react-router/src/redirects.ts index b81265d36d..9f90c2434c 100644 --- a/packages/react-router/src/redirects.ts +++ b/packages/react-router/src/redirects.ts @@ -1,7 +1,7 @@ import type { NavigateOptions } from './link' import type { RoutePaths } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' -import type { PickAsRequired } from './utils' +import type { PickAsRequired } from '@tanstack/router-core' export type AnyRedirect = Redirect diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts index 2c90f27698..7f8b22b4d2 100644 --- a/packages/react-router/src/route.ts +++ b/packages/react-router/src/route.ts @@ -1,20 +1,56 @@ import invariant from 'tiny-invariant' -import { joinPaths, trimPathLeft } from './path' +import { joinPaths, rootRouteId, trimPathLeft } from '@tanstack/router-core' import { useLoaderData } from './useLoaderData' import { useLoaderDeps } from './useLoaderDeps' import { useParams } from './useParams' import { useSearch } from './useSearch' import { notFound } from './not-found' import { useNavigate } from './useNavigate' -import { rootRouteId } from './root' import { useMatch } from './useMatch' +import type { + AnyContext, + AnyPathParams, + AnySchema, + AnyValidator, + Assign, + Constrain, + ConstrainLiteral, + ContextAsyncReturnType, + ContextReturnType, + DefaultValidator, + ErrorComponentProps, + Expand, + InferAllContext, + InferAllParams, + InferFullSearchSchema, + InferFullSearchSchemaInput, + IntersectAssign, + NoInfer, + NotFoundRouteProps, + ParamsOptions, + ParsedLocation, + ResolveId, + ResolveLoaderData, + ResolveParams, + ResolveRouteContext, + ResolveSearchValidatorInput, + ResolveValidatorOutput, + RootRouteId, + RouteContext, + RoutePathOptions, + RoutePathOptionsIntersection, + RoutePrefix, + SearchFilter, + SearchMiddleware, + TrimPathRight, + UpdatableStaticRouteOption, +} from '@tanstack/router-core' import type { UseLoaderDataRoute } from './useLoaderData' import type { UseMatchRoute } from './useMatch' import type { UseLoaderDepsRoute } from './useLoaderDeps' import type { UseParamsRoute } from './useParams' import type { UseSearchRoute } from './useSearch' import type * as React from 'react' -import type { RootRouteId } from './root' import type { UseNavigateResult } from './useNavigate' import type { AnyRouteMatch, @@ -22,63 +58,14 @@ import type { MakeRouteMatchUnion, RouteMatch, } from './Matches' -import type { NavigateOptions, ParsePathParams, ToMaskOptions } from './link' -import type { ParsedLocation } from './location' +import type { NavigateOptions, ToMaskOptions } from './link' import type { RouteById, RouteIds, RoutePaths } from './routeInfo' import type { AnyRouter, RegisteredRouter, Router } from './router' -import type { - Assign, - Constrain, - ConstrainLiteral, - Expand, - IntersectAssign, - NoInfer, -} from './utils' import type { BuildLocationFn, NavigateFn } from './RouterProvider' import type { NotFoundError } from './not-found' import type { LazyRoute } from './fileRoute' -import type { - AnySchema, - AnyStandardSchemaValidator, - AnyValidator, - AnyValidatorAdapter, - AnyValidatorObj, - DefaultValidator, - ResolveSearchValidatorInput, - ResolveValidatorOutput, - StandardSchemaValidator, - ValidatorAdapter, - ValidatorFn, - ValidatorObj, -} from './validators' -import type { UseRouteContextRoute } from './useRouteContext' - -export type AnyPathParams = {} - -export type SearchSchemaInput = { - __TSearchSchemaInput__: 'TSearchSchemaInput' -} - -export type AnyContext = {} - -export interface RouteContext {} - -export type PreloadableObj = { preload?: () => Promise } - -export type RoutePathOptions = - | { - path: TPath - } - | { - id: TCustomId - } -export interface StaticDataRouteOption {} - -export type RoutePathOptionsIntersection = { - path: TPath - id: TCustomId -} +import type { UseRouteContextRoute } from './useRouteContext' export type RouteOptions< TParentRoute extends AnyRoute = AnyRoute, @@ -119,49 +106,6 @@ export type RouteOptions< NoInfer > -export type ParseSplatParams = TPath & - `${string}$` extends never - ? TPath & `${string}$/${string}` extends never - ? never - : '_splat' - : '_splat' - -export interface SplatParams { - _splat?: string -} - -export type ResolveParams = - ParseSplatParams extends never - ? Record, string> - : Record, string> & SplatParams - -export type ParseParamsFn = ( - rawParams: ResolveParams, -) => TParams extends Record, any> - ? TParams - : Record, any> - -export type StringifyParamsFn = ( - params: TParams, -) => ResolveParams - -export type ParamsOptions = { - params?: { - parse?: ParseParamsFn - stringify?: StringifyParamsFn - } - - /** - @deprecated Use params.parse instead - */ - parseParams?: ParseParamsFn - - /** - @deprecated Use params.stringify instead - */ - stringifyParams?: StringifyParamsFn -} - export interface FullSearchSchemaOption< in out TParentRoute extends AnyRoute, in out TSearchValidator, @@ -487,51 +431,6 @@ export interface UpdatableRouteOptions< ssr?: boolean } -interface RequiredStaticDataRouteOption { - staticData: StaticDataRouteOption -} - -interface OptionalStaticDataRouteOption { - staticData?: StaticDataRouteOption -} - -export type UpdatableStaticRouteOption = {} extends StaticDataRouteOption - ? OptionalStaticDataRouteOption - : RequiredStaticDataRouteOption - -export type MetaDescriptor = - | { charSet: 'utf-8' } - | { title: string } - | { name: string; content: string } - | { property: string; content: string } - | { httpEquiv: string; content: string } - | { 'script:ld+json': LdJsonObject } - | { tagName: 'meta' | 'link'; [name: string]: string } - | Record - -type LdJsonObject = { [Key in string]: LdJsonValue } & { - [Key in string]?: LdJsonValue | undefined -} -type LdJsonArray = Array | ReadonlyArray -type LdJsonPrimitive = string | number | boolean | null -type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray - -export type RouteLinkEntry = {} - -export type SearchValidator = - | ValidatorObj - | ValidatorFn - | ValidatorAdapter - | StandardSchemaValidator - | undefined - -export type AnySearchValidator = SearchValidator - -export type DefaultSearchValidator = SearchValidator< - Record, - AnySchema -> - export type RouteLoaderFn< in out TParentRoute extends AnyRoute = AnyRoute, in out TId extends string = string, @@ -586,92 +485,6 @@ export interface LoaderFnContext< route: Route } -export type SearchFilter = (prev: TInput) => TResult - -export type SearchMiddlewareContext = { - search: TSearchSchema - next: (newSearch: TSearchSchema) => TSearchSchema -} - -export type SearchMiddleware = ( - ctx: SearchMiddlewareContext, -) => TSearchSchema - -export type ResolveId< - TParentRoute, - TCustomId extends string, - TPath extends string, -> = TParentRoute extends { id: infer TParentId extends string } - ? RoutePrefix - : RootRouteId - -export type InferFullSearchSchema = TRoute extends { - types: { - fullSearchSchema: infer TFullSearchSchema - } -} - ? TFullSearchSchema - : {} - -export type InferFullSearchSchemaInput = TRoute extends { - types: { - fullSearchSchemaInput: infer TFullSearchSchemaInput - } -} - ? TFullSearchSchemaInput - : {} - -export type InferAllParams = TRoute extends { - types: { - allParams: infer TAllParams - } -} - ? TAllParams - : {} - -export type InferAllContext = unknown extends TRoute - ? TRoute - : TRoute extends { - types: { - allContext: infer TAllContext - } - } - ? TAllContext - : {} - -export type ResolveSearchSchemaFnInput = - TSearchValidator extends (input: infer TSearchSchemaInput) => any - ? TSearchSchemaInput extends SearchSchemaInput - ? Omit - : ResolveSearchSchemaFn - : AnySchema - -export type ResolveSearchSchemaInput = - TSearchValidator extends AnyStandardSchemaValidator - ? NonNullable['input'] - : TSearchValidator extends AnyValidatorAdapter - ? TSearchValidator['types']['input'] - : TSearchValidator extends AnyValidatorObj - ? ResolveSearchSchemaFnInput - : ResolveSearchSchemaFnInput - -export type ResolveSearchSchemaFn = TSearchValidator extends ( - ...args: any -) => infer TSearchSchema - ? TSearchSchema - : AnySchema - -export type ResolveSearchSchema = - unknown extends TSearchValidator - ? TSearchValidator - : TSearchValidator extends AnyStandardSchemaValidator - ? NonNullable['output'] - : TSearchValidator extends AnyValidatorAdapter - ? TSearchValidator['types']['output'] - : TSearchValidator extends AnyValidatorObj - ? ResolveSearchSchemaFn - : ResolveSearchSchemaFn - export type ResolveFullSearchSchema< TParentRoute extends AnyRoute, TSearchValidator, @@ -690,32 +503,6 @@ export type ResolveFullSearchSchemaInput< ResolveSearchValidatorInput > -export type LooseReturnType = T extends ( - ...args: Array -) => infer TReturn - ? TReturn - : never - -export type LooseAsyncReturnType = T extends ( - ...args: Array -) => infer TReturn - ? TReturn extends Promise - ? TReturn - : TReturn - : never - -export type ContextReturnType = unknown extends TContextFn - ? TContextFn - : LooseReturnType extends never - ? AnyContext - : LooseReturnType - -export type ContextAsyncReturnType = unknown extends TContextFn - ? TContextFn - : LooseAsyncReturnType extends never - ? AnyContext - : LooseAsyncReturnType - export type RouteContextParameter< TParentRoute extends AnyRoute, TRouterContext, @@ -723,10 +510,6 @@ export type RouteContextParameter< ? TRouterContext : Assign> -export type ResolveRouteContext = Assign< - ContextReturnType, - ContextAsyncReturnType -> export type BeforeLoadContextParameter< TParentRoute extends AnyRoute, TRouterContext, @@ -746,12 +529,6 @@ export type ResolveAllContext< ContextAsyncReturnType > -export type ResolveLoaderData = unknown extends TLoaderFn - ? TLoaderFn - : LooseAsyncReturnType extends never - ? {} - : LooseAsyncReturnType - export interface AnyRoute extends Route< any, @@ -1530,37 +1307,6 @@ export type ResolveFullPath< TPrefixed = RoutePrefix, > = TPrefixed extends RootRouteId ? '/' : TPrefixed -type RoutePrefix< - TPrefix extends string, - TPath extends string, -> = string extends TPath - ? RootRouteId - : TPath extends string - ? TPrefix extends RootRouteId - ? TPath extends '/' - ? '/' - : `/${TrimPath}` - : `${TPrefix}/${TPath}` extends '/' - ? '/' - : `/${TrimPathLeft<`${TrimPathRight}/${TrimPath}`>}` - : never - -export type TrimPath = '' extends T - ? '' - : TrimPathRight> - -export type TrimPathLeft = - T extends `${RootRouteId}/${infer U}` - ? TrimPathLeft - : T extends `/${infer U}` - ? TrimPathLeft - : T -export type TrimPathRight = T extends '/' - ? '/' - : T extends `${infer U}/` - ? TrimPathRight - : T - export type RouteMask = { routeTree: TRouteTree from: RoutePaths @@ -1584,26 +1330,6 @@ export function createRouteMask< return opts as any } -/** - * @deprecated Use `ErrorComponentProps` instead. - */ -export type ErrorRouteProps = { - error: unknown - info?: { componentStack: string } - reset: () => void -} - -export type ErrorComponentProps = { - error: Error - info?: { componentStack: string } - reset: () => void -} -export type NotFoundRouteProps = { - // TODO: Make sure this is `| null | undefined` (this is for global not-founds) - data: unknown -} -// - export type ReactNode = any export type SyncRouteComponent = diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts index 6c9249e091..45bb5a30d3 100644 --- a/packages/react-router/src/routeInfo.ts +++ b/packages/react-router/src/routeInfo.ts @@ -1,8 +1,12 @@ import type { InferFileRouteTypes } from './fileRoute' -import type { AddTrailingSlash, RemoveTrailingSlashes } from './link' +import type { + AddTrailingSlash, + PartialMergeAll, + RemoveTrailingSlashes, + TrailingSlashOption, +} from '@tanstack/router-core' import type { AnyRoute } from './route' -import type { AnyRouter, TrailingSlashOption } from './router' -import type { PartialMergeAll } from './utils' +import type { AnyRouter } from './router' export type ParseRoute = TRouteTree extends { types: { children: infer TChildren } diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts index 9b92d61c5d..6c1a4f5433 100644 --- a/packages/react-router/src/router.ts +++ b/packages/react-router/src/router.ts @@ -5,30 +5,28 @@ import { } from '@tanstack/history' import { Store, batch } from '@tanstack/react-store' import invariant from 'tiny-invariant' -import { rootRouteId } from './root' -import { defaultParseSearch, defaultStringifySearch } from './searchParams' import { + cleanPath, createControlledPromise, deepEqual, + defaultParseSearch, + defaultStringifySearch, functionalUpdate, - last, - pick, - replaceEqualDeep, -} from './utils' -import { - cleanPath, interpolatePath, joinPaths, + last, matchPathname, parsePathname, + pick, + replaceEqualDeep, resolvePath, + rootRouteId, trimPath, trimPathLeft, trimPathRight, -} from './path' +} from '@tanstack/router-core' import { isRedirect, isResolvedRedirect } from './redirects' import { isNotFound } from './not-found' -import type { StartSerializer } from './serializer' import type * as React from 'react' import type { HistoryLocation, @@ -37,9 +35,27 @@ import type { RouterHistory, } from '@tanstack/history' import type { NoInfer } from '@tanstack/react-store' -import type { Manifest } from './manifest' + import type { AnyContext, + AnySchema, + AnyValidator, + CommitLocationOptions, + ControlledPromise, + Manifest, + NonNullableUpdater, + ParsedLocation, + PickAsRequired, + ResolveRelativePath, + SearchMiddleware, + SearchParser, + SearchSerializer, + StartSerializer, + TrailingSlashOption, + Updater, + ViewTransitionOptions, +} from '@tanstack/router-core' +import type { AnyRoute, AnyRouteWithContext, BeforeLoadContextOptions, @@ -50,8 +66,8 @@ import type { RouteComponent, RouteContextOptions, RouteMask, - SearchMiddleware, } from './route' + import type { FullSearchSchema, RouteById, @@ -59,29 +75,18 @@ import type { RoutesById, RoutesByPath, } from './routeInfo' -import type { - ControlledPromise, - NonNullableUpdater, - PickAsRequired, - Updater, -} from './utils' import type { AnyRouteMatch, MakeRouteMatch, MakeRouteMatchUnion, MatchRouteOptions, } from './Matches' -import type { ParsedLocation } from './location' -import type { SearchParser, SearchSerializer } from './searchParams' -import type { - BuildLocationFn, - CommitLocationOptions, - NavigateFn, -} from './RouterProvider' + +import type { BuildLocationFn, NavigateFn } from './RouterProvider' + import type { AnyRedirect, ResolvedRedirect } from './redirects' import type { NotFoundError } from './not-found' -import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link' -import type { AnySchema, AnyValidator } from './validators' +import type { NavigateOptions, ToOptions } from './link' declare global { interface Window { @@ -137,8 +142,6 @@ export type RouterContextOptions = context: InferRouterContext } -export type TrailingSlashOption = 'always' | 'never' | 'preserve' - export type InjectedHtmlEntry = Promise export interface RouterOptions< @@ -498,10 +501,6 @@ export interface MatchedRoutesResult { routeParams: Record } -export interface ViewTransitionOptions { - types: Array -} - export type RouterConstructorOptions< TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, @@ -3029,22 +3028,3 @@ export function getInitialRouterState( statusCode: 200, } } - -export function defaultSerializeError(err: unknown) { - if (err instanceof Error) { - const obj = { - name: err.name, - message: err.message, - } - - if (process.env.NODE_ENV === 'development') { - ;(obj as any).stack = err.stack - } - - return obj - } - - return { - data: err, - } -} diff --git a/packages/react-router/src/scroll-restoration.tsx b/packages/react-router/src/scroll-restoration.tsx index 497a1d2c0b..fce713fcc6 100644 --- a/packages/react-router/src/scroll-restoration.tsx +++ b/packages/react-router/src/scroll-restoration.tsx @@ -1,8 +1,7 @@ import * as React from 'react' +import { functionalUpdate } from '@tanstack/router-core' import { useRouter } from './useRouter' -import { functionalUpdate } from './utils' -import type { ParsedLocation } from './location' -import type { NonNullableUpdater } from './utils' +import type { NonNullableUpdater, ParsedLocation } from '@tanstack/router-core' const useLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect diff --git a/packages/react-router/src/structuralSharing.ts b/packages/react-router/src/structuralSharing.ts index 9921e9c862..e9ab55d8b8 100644 --- a/packages/react-router/src/structuralSharing.ts +++ b/packages/react-router/src/structuralSharing.ts @@ -1,4 +1,8 @@ -import type { Constrain, ValidateJSON } from './utils' +import type { + Constrain, + OptionalStructuralSharing, + ValidateJSON, +} from '@tanstack/router-core' import type { AnyRouter } from './router' export type DefaultStructuralSharingEnabled = @@ -8,12 +12,6 @@ export type DefaultStructuralSharingEnabled = false : NonNullable -export interface OptionalStructuralSharing { - readonly structuralSharing?: - | Constrain - | undefined -} - export interface RequiredStructuralSharing { readonly structuralSharing: Constrain } diff --git a/packages/react-router/src/typePrimitives.ts b/packages/react-router/src/typePrimitives.ts index 108bfa9fd7..c71e06991d 100644 --- a/packages/react-router/src/typePrimitives.ts +++ b/packages/react-router/src/typePrimitives.ts @@ -10,7 +10,7 @@ import type { RouteIds } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' import type { UseParamsOptions, UseParamsResult } from './useParams' import type { UseSearchOptions, UseSearchResult } from './useSearch' -import type { Constrain, ConstrainLiteral } from './utils' +import type { Constrain, ConstrainLiteral } from '@tanstack/router-core' export type ValidateFromPath< TFrom, diff --git a/packages/react-router/src/useLoaderData.tsx b/packages/react-router/src/useLoaderData.tsx index 6128e5e32f..538a3578af 100644 --- a/packages/react-router/src/useLoaderData.tsx +++ b/packages/react-router/src/useLoaderData.tsx @@ -5,7 +5,8 @@ import type { } from './structuralSharing' import type { AnyRouter, RegisteredRouter } from './router' import type { AllLoaderData, RouteById } from './routeInfo' -import type { Expand, StrictOrFrom } from './utils' +import type { StrictOrFrom } from './utils' +import type { Expand } from '@tanstack/router-core' export interface UseLoaderDataBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/useLoaderDeps.tsx b/packages/react-router/src/useLoaderDeps.tsx index d9c9fc5b37..bb176cb2f8 100644 --- a/packages/react-router/src/useLoaderDeps.tsx +++ b/packages/react-router/src/useLoaderDeps.tsx @@ -5,7 +5,8 @@ import type { } from './structuralSharing' import type { AnyRouter, RegisteredRouter } from './router' import type { RouteById } from './routeInfo' -import type { Expand, StrictOrFrom } from './utils' +import type { StrictOrFrom } from './utils' +import type { Expand } from '@tanstack/router-core' export interface UseLoaderDepsBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/useMatch.tsx b/packages/react-router/src/useMatch.tsx index 367ae4f46d..51cf0bf7d1 100644 --- a/packages/react-router/src/useMatch.tsx +++ b/packages/react-router/src/useMatch.tsx @@ -8,7 +8,8 @@ import type { } from './structuralSharing' import type { AnyRouter, RegisteredRouter } from './router' import type { MakeRouteMatch, MakeRouteMatchUnion } from './Matches' -import type { StrictOrFrom, ThrowOrOptional } from './utils' +import type { StrictOrFrom } from './utils' +import type { ThrowOrOptional } from '@tanstack/router-core' export interface UseMatchBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx index 45ad4951b3..c096075cc6 100644 --- a/packages/react-router/src/useParams.tsx +++ b/packages/react-router/src/useParams.tsx @@ -5,7 +5,8 @@ import type { } from './structuralSharing' import type { AllParams, RouteById } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' -import type { Expand, StrictOrFrom } from './utils' +import type { StrictOrFrom } from './utils' +import type { Expand } from '@tanstack/router-core' export interface UseParamsBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/useRouteContext.ts b/packages/react-router/src/useRouteContext.ts index 451807d994..dd86b766a2 100644 --- a/packages/react-router/src/useRouteContext.ts +++ b/packages/react-router/src/useRouteContext.ts @@ -1,7 +1,8 @@ import { useMatch } from './useMatch' import type { AllContext, RouteById } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' -import type { Expand, StrictOrFrom } from './utils' +import type { StrictOrFrom } from './utils' +import type { Expand } from '@tanstack/router-core' export interface UseRouteContextBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/useRouterState.tsx b/packages/react-router/src/useRouterState.tsx index 5eb49abddf..9032d750ac 100644 --- a/packages/react-router/src/useRouterState.tsx +++ b/packages/react-router/src/useRouterState.tsx @@ -1,7 +1,7 @@ import { useStore } from '@tanstack/react-store' import { useRef } from 'react' +import { replaceEqualDeep } from '@tanstack/router-core' import { useRouter } from './useRouter' -import { replaceEqualDeep } from './utils' import type { StructuralSharingOption, ValidateSelected, diff --git a/packages/react-router/src/useSearch.tsx b/packages/react-router/src/useSearch.tsx index 9734dba4d4..bb92db1429 100644 --- a/packages/react-router/src/useSearch.tsx +++ b/packages/react-router/src/useSearch.tsx @@ -5,7 +5,8 @@ import type { } from './structuralSharing' import type { FullSearchSchema, RouteById } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' -import type { Expand, StrictOrFrom } from './utils' +import type { StrictOrFrom } from './utils' +import type { Expand } from '@tanstack/router-core' export interface UseSearchBaseOptions< TRouter extends AnyRouter, diff --git a/packages/react-router/src/utils.ts b/packages/react-router/src/utils.ts index 188428fa8b..491cb2d614 100644 --- a/packages/react-router/src/utils.ts +++ b/packages/react-router/src/utils.ts @@ -1,327 +1,7 @@ import * as React from 'react' import type { RouteIds } from './routeInfo' import type { AnyRouter } from './router' - -export type NoInfer = [T][T extends any ? 0 : never] -export type IsAny = 1 extends 0 & TValue - ? TYesResult - : TNoResult - -export type PickAsRequired = Omit< - TValue, - TKey -> & - Required> - -export type PickRequired = { - [K in keyof T as undefined extends T[K] ? never : K]: T[K] -} - -export type PickOptional = { - [K in keyof T as undefined extends T[K] ? K : never]: T[K] -} - -// from https://stackoverflow.com/a/76458160 -export type WithoutEmpty = T extends any ? ({} extends T ? never : T) : never - -// export type Expand = T -export type Expand = T extends object - ? T extends infer O - ? O extends Function - ? O - : { [K in keyof O]: O[K] } - : never - : T - -export type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial - } - : T - -export type MakeDifferenceOptional = Omit< - TRight, - keyof TLeft -> & { - [K in keyof TLeft & keyof TRight]?: TRight[K] -} - -// from https://stackoverflow.com/a/53955431 -// eslint-disable-next-line @typescript-eslint/naming-convention -export type IsUnion = ( - T extends any ? (U extends T ? false : true) : never -) extends false - ? false - : true - -export type IsNonEmptyObject = T extends object - ? keyof T extends never - ? false - : true - : false - -export type Assign = TLeft extends any - ? TRight extends any - ? IsNonEmptyObject extends false - ? TRight - : IsNonEmptyObject extends false - ? TLeft - : keyof TLeft & keyof TRight extends never - ? TLeft & TRight - : Omit & TRight - : never - : never - -export type IntersectAssign = TLeft extends any - ? TRight extends any - ? IsNonEmptyObject extends false - ? TRight - : IsNonEmptyObject extends false - ? TLeft - : TRight & TLeft - : never - : never - -export type Timeout = ReturnType - -export type Updater = - | TResult - | ((prev?: TPrevious) => TResult) - -export type NonNullableUpdater = - | TResult - | ((prev: TPrevious) => TResult) - -export type ExtractObjects = TUnion extends MergeAllPrimitive - ? never - : TUnion - -export type PartialMergeAllObject = - ExtractObjects extends infer TObj - ? { - [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any - ? TKey extends keyof TObj - ? TObj[TKey] - : never - : never - } - : never - -export type MergeAllPrimitive = - | ReadonlyArray - | number - | string - | bigint - | boolean - | symbol - | undefined - | null - -export type ExtractPrimitives = TUnion extends MergeAllPrimitive - ? TUnion - : TUnion extends object - ? never - : TUnion - -export type PartialMergeAll = - | ExtractPrimitives - | PartialMergeAllObject - -export type Constrain = - | (T extends TConstraint ? T : never) - | TDefault - -export type ConstrainLiteral = - | (T & TConstraint) - | TDefault - -/** - * To be added to router types - */ -export type UnionToIntersection = ( - T extends any ? (arg: T) => any : never -) extends (arg: infer T) => any - ? T - : never - -/** - * Merges everything in a union into one object. - * This mapped type is homomorphic which means it preserves stuff! :) - */ -export type MergeAllObjects< - TUnion, - TIntersected = UnionToIntersection>, -> = [keyof TIntersected] extends [never] - ? never - : { - [TKey in keyof TIntersected]: TUnion extends any - ? TUnion[TKey & keyof TUnion] - : never - } - -export type MergeAll = - | MergeAllObjects - | ExtractPrimitives - -export type ValidateJSON = ((...args: Array) => any) extends T - ? unknown extends T - ? never - : 'Function is not serializable' - : { [K in keyof T]: ValidateJSON } - -export function last(arr: Array) { - return arr[arr.length - 1] -} - -function isFunction(d: any): d is Function { - return typeof d === 'function' -} - -export function functionalUpdate( - updater: Updater | NonNullableUpdater, - previous: TPrevious, -): TResult { - if (isFunction(updater)) { - return updater(previous) - } - - return updater -} - -export function pick( - parent: TValue, - keys: Array, -): Pick { - return keys.reduce((obj: any, key: TKey) => { - obj[key] = parent[key] - return obj - }, {} as any) -} - -/** - * This function returns `prev` if `_next` is deeply equal. - * If not, it will replace any deeply equal children of `b` with those of `a`. - * This can be used for structural sharing between immutable JSON values for example. - * Do not use this with signals - */ -export function replaceEqualDeep(prev: any, _next: T): T { - if (prev === _next) { - return prev - } - - const next = _next as any - - const array = isPlainArray(prev) && isPlainArray(next) - - if (array || (isPlainObject(prev) && isPlainObject(next))) { - const prevItems = array ? prev : Object.keys(prev) - const prevSize = prevItems.length - const nextItems = array ? next : Object.keys(next) - const nextSize = nextItems.length - const copy: any = array ? [] : {} - - let equalItems = 0 - - for (let i = 0; i < nextSize; i++) { - const key = array ? i : (nextItems[i] as any) - if ( - ((!array && prevItems.includes(key)) || array) && - prev[key] === undefined && - next[key] === undefined - ) { - copy[key] = undefined - equalItems++ - } else { - copy[key] = replaceEqualDeep(prev[key], next[key]) - if (copy[key] === prev[key] && prev[key] !== undefined) { - equalItems++ - } - } - } - - return prevSize === nextSize && equalItems === prevSize ? prev : copy - } - - return next -} - -// Copied from: https://github.com/jonschlinkert/is-plain-object -export function isPlainObject(o: any) { - if (!hasObjectPrototype(o)) { - return false - } - - // If has modified constructor - const ctor = o.constructor - if (typeof ctor === 'undefined') { - return true - } - - // If has modified prototype - const prot = ctor.prototype - if (!hasObjectPrototype(prot)) { - return false - } - - // If constructor does not have an Object-specific method - if (!prot.hasOwnProperty('isPrototypeOf')) { - return false - } - - // Most likely a plain Object - return true -} - -function hasObjectPrototype(o: any) { - return Object.prototype.toString.call(o) === '[object Object]' -} - -export function isPlainArray(value: unknown): value is Array { - return Array.isArray(value) && value.length === Object.keys(value).length -} - -function getObjectKeys(obj: any, ignoreUndefined: boolean) { - let keys = Object.keys(obj) - if (ignoreUndefined) { - keys = keys.filter((key) => obj[key] !== undefined) - } - return keys -} - -export function deepEqual( - a: any, - b: any, - opts?: { partial?: boolean; ignoreUndefined?: boolean }, -): boolean { - if (a === b) { - return true - } - - if (typeof a !== typeof b) { - return false - } - - if (isPlainObject(a) && isPlainObject(b)) { - const ignoreUndefined = opts?.ignoreUndefined ?? true - const aKeys = getObjectKeys(a, ignoreUndefined) - const bKeys = getObjectKeys(b, ignoreUndefined) - - if (!opts?.partial && aKeys.length !== bKeys.length) { - return false - } - - return bKeys.every((key) => deepEqual(a[key], b[key], opts)) - } - - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false - } - return !a.some((item, index) => !deepEqual(item, b[index], opts)) - } - - return false -} +import type { ConstrainLiteral } from '@tanstack/router-core' export function useStableCallback) => any>( fn: T, @@ -333,42 +13,6 @@ export function useStableCallback) => any>( return ref.current as T } -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } - - for (const item of keysA) { - if ( - !Object.prototype.hasOwnProperty.call(objB, item) || - !Object.is(objA[item as keyof T], objB[item as keyof T]) - ) { - return false - } - } - return true -} - -export type StringLiteral = T extends string - ? string extends T - ? string - : T - : never - export type StrictOrFrom< TRouter extends AnyRouter, TFrom, @@ -383,57 +27,9 @@ export type StrictOrFrom< strict?: TStrict } -export type ThrowOrOptional = TThrow extends true - ? T - : T | undefined - export const useLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect -/** - * - * @deprecated use `jsesc` instead - */ -export function escapeJSON(jsonString: string) { - return jsonString - .replace(/\\/g, '\\\\') // Escape backslashes - .replace(/'/g, "\\'") // Escape single quotes - .replace(/"/g, '\\"') // Escape double quotes -} - -export type ControlledPromise = Promise & { - resolve: (value: T) => void - reject: (value: any) => void - status: 'pending' | 'resolved' | 'rejected' - value?: T -} - -export function createControlledPromise(onResolve?: (value: T) => void) { - let resolveLoadPromise!: (value: T) => void - let rejectLoadPromise!: (value: any) => void - - const controlledPromise = new Promise((resolve, reject) => { - resolveLoadPromise = resolve - rejectLoadPromise = reject - }) as ControlledPromise - - controlledPromise.status = 'pending' - - controlledPromise.resolve = (value: T) => { - controlledPromise.status = 'resolved' - controlledPromise.value = value - resolveLoadPromise(value) - onResolve?.(value) - } - - controlledPromise.reject = (e) => { - controlledPromise.status = 'rejected' - rejectLoadPromise(e) - } - - return controlledPromise -} - /** * Taken from https://www.developerway.com/posts/implementing-advanced-use-previous-hook#part3 */ diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index d6169164f5..7f6a10e052 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -19,7 +19,7 @@ import { createRouter, useNavigate, } from '../src' -import type { StandardSchemaValidator } from '../src/validators' +import type { StandardSchemaValidator } from '@tanstack/router-core' import type { AnyRoute, AnyRouter, diff --git a/packages/react-router/tests/searchMiddleware.test.tsx b/packages/react-router/tests/searchMiddleware.test.tsx index fb5189803e..36b4ebb58e 100644 --- a/packages/react-router/tests/searchMiddleware.test.tsx +++ b/packages/react-router/tests/searchMiddleware.test.tsx @@ -12,7 +12,7 @@ import { } from '../src' import { getSearchParamsFromURI } from './utils' import type { AnyRouter } from '../src' -import type { SearchMiddleware } from '../src/route' +import type { SearchMiddleware } from '@tanstack/router-core' afterEach(() => { vi.resetAllMocks() diff --git a/packages/router-core/README.md b/packages/router-core/README.md new file mode 100644 index 0000000000..b3a422c890 --- /dev/null +++ b/packages/router-core/README.md @@ -0,0 +1,5 @@ + + +# TanStack Router Core + +See [https://tanstack.com/router](https://tanstack.com/router) for documentation. diff --git a/packages/router-core/eslint.config.js b/packages/router-core/eslint.config.js new file mode 100644 index 0000000000..8ce6ad05fc --- /dev/null +++ b/packages/router-core/eslint.config.js @@ -0,0 +1,5 @@ +// @ts-check + +import rootConfig from '../../eslint.config.js' + +export default [...rootConfig] diff --git a/packages/router-core/package.json b/packages/router-core/package.json new file mode 100644 index 0000000000..09637a7e72 --- /dev/null +++ b/packages/router-core/package.json @@ -0,0 +1,65 @@ +{ + "name": "@tanstack/router-core", + "version": "1.90.0", + "description": "Modern and scalable routing for React applications", + "author": "Tanner Linsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/router.git", + "directory": "packages/router-core" + }, + "homepage": "https://tanstack.com/router", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "keywords": [ + "history", + "typescript" + ], + "scripts": { + "clean": "rimraf ./dist && rimraf ./coverage", + "test:eslint": "eslint ./src", + "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", + "test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js", + "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js", + "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js", + "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", + "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", + "test:types:ts57": "tsc", + "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", + "test:unit": "vitest", + "test:unit:dev": "pnpm run test:unit --watch", + "build": "vite build" + }, + "type": "module", + "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "engines": { + "node": ">=12" + }, + "dependencies": { + "@tanstack/history": "workspace:*", + "@tanstack/store": "^0.6.0" + } +} diff --git a/packages/router-core/src/Matches.ts b/packages/router-core/src/Matches.ts new file mode 100644 index 0000000000..6ff0f7390f --- /dev/null +++ b/packages/router-core/src/Matches.ts @@ -0,0 +1,94 @@ +import type { Constrain } from './utils' + +export type AnyMatchAndValue = { match: any; value: any } + +export type FindValueByIndex< + TKey, + TValue extends ReadonlyArray, +> = TKey extends `${infer TIndex extends number}` ? TValue[TIndex] : never + +export type FindValueByKey = + TValue extends ReadonlyArray + ? FindValueByIndex + : TValue[TKey & keyof TValue] + +export type CreateMatchAndValue = TValue extends any + ? { + match: TMatch + value: TValue + } + : never + +export type NextMatchAndValue< + TKey, + TMatchAndValue extends AnyMatchAndValue, +> = TMatchAndValue extends any + ? CreateMatchAndValue< + TMatchAndValue['match'], + FindValueByKey + > + : never + +export type IsMatchKeyOf = + TValue extends ReadonlyArray + ? number extends TValue['length'] + ? `${number}` + : keyof TValue & `${number}` + : TValue extends object + ? keyof TValue & string + : never + +export type IsMatchPath< + TParentPath extends string, + TMatchAndValue extends AnyMatchAndValue, +> = `${TParentPath}${IsMatchKeyOf}` + +export type IsMatchResult< + TKey, + TMatchAndValue extends AnyMatchAndValue, +> = TMatchAndValue extends any + ? TKey extends keyof TMatchAndValue['value'] + ? TMatchAndValue['match'] + : never + : never + +export type IsMatchParse< + TPath, + TMatchAndValue extends AnyMatchAndValue, + TParentPath extends string = '', +> = TPath extends `${string}.${string}` + ? TPath extends `${infer TFirst}.${infer TRest}` + ? IsMatchParse< + TRest, + NextMatchAndValue, + `${TParentPath}${TFirst}.` + > + : never + : { + path: IsMatchPath + result: IsMatchResult + } + +export type IsMatch = IsMatchParse< + TPath, + TMatch extends any ? { match: TMatch; value: TMatch } : never +> + +/** + * Narrows matches based on a path + * @experimental + */ +export const isMatch = ( + match: TMatch, + path: Constrain['path']>, +): match is IsMatch['result'] => { + const parts = (path as string).split('.') + let part + let value: any = match + + while ((part = parts.shift()) != null && value != null) { + value = value[part] + } + + return value != null +} diff --git a/packages/router-core/src/RouterProvider.ts b/packages/router-core/src/RouterProvider.ts new file mode 100644 index 0000000000..4e7e3d9f82 --- /dev/null +++ b/packages/router-core/src/RouterProvider.ts @@ -0,0 +1,20 @@ +import type { ViewTransitionOptions } from './router' + +export interface MatchLocation { + to?: string | number | null + fuzzy?: boolean + caseSensitive?: boolean + from?: string +} + +export interface CommitLocationOptions { + replace?: boolean + resetScroll?: boolean + hashScrollIntoView?: boolean | ScrollIntoViewOptions + viewTransition?: boolean | ViewTransitionOptions + /** + * @deprecated All navigations use React transitions under the hood now + **/ + startTransition?: boolean + ignoreBlocker?: boolean +} diff --git a/packages/react-router/src/defer.ts b/packages/router-core/src/defer.ts similarity index 100% rename from packages/react-router/src/defer.ts rename to packages/router-core/src/defer.ts diff --git a/packages/router-core/src/index.ts b/packages/router-core/src/index.ts new file mode 100644 index 0000000000..bce54a72f5 --- /dev/null +++ b/packages/router-core/src/index.ts @@ -0,0 +1,207 @@ +export { TSR_DEFERRED_PROMISE, defer } from './defer' +export type { DeferredPromiseState, DeferredPromise } from './defer' +export { preloadWarning } from './link' +export type { + IsRequiredParams, + ParsePathParams, + AddTrailingSlash, + RemoveTrailingSlashes, + AddLeadingSlash, + RemoveLeadingSlashes, + ActiveOptions, + LinkOptionsProps, + ResolveCurrentPath, + ResolveParentPath, + ResolveRelativePath, + LinkCurrentTargetElement, +} from './link' + +export type { + StartSerializer, + Serializable, + SerializerParse, + SerializerParseBy, + SerializerStringify, + SerializerStringifyBy, +} from './serializer' + +export type { ParsedLocation } from './location' +export type { Manifest, RouterManagedTag } from './manifest' +export { isMatch } from './Matches' +export type { + AnyMatchAndValue, + FindValueByIndex, + FindValueByKey, + CreateMatchAndValue, + NextMatchAndValue, + IsMatchKeyOf, + IsMatchPath, + IsMatchResult, + IsMatchParse, + IsMatch, +} from './Matches' +export { + joinPaths, + cleanPath, + trimPathLeft, + trimPathRight, + trimPath, + removeTrailingSlash, + exactPathTest, + resolvePath, + parsePathname, + interpolatePath, + matchPathname, + removeBasepath, + matchByPath, +} from './path' +export type { Segment } from './path' +export { encode, decode } from './qss' +export { rootRouteId } from './root' +export type { RootRouteId } from './root' + +export type { + AnyPathParams, + SearchSchemaInput, + AnyContext, + RouteContext, + PreloadableObj, + RoutePathOptions, + StaticDataRouteOption, + RoutePathOptionsIntersection, + SearchFilter, + SearchMiddlewareContext, + SearchMiddleware, + ResolveId, + InferFullSearchSchema, + InferFullSearchSchemaInput, + InferAllParams, + InferAllContext, + MetaDescriptor, + RouteLinkEntry, + SearchValidator, + AnySearchValidator, + DefaultSearchValidator, + ErrorRouteProps, + ErrorComponentProps, + NotFoundRouteProps, + ParseSplatParams, + SplatParams, + ResolveParams, + ParseParamsFn, + StringifyParamsFn, + ParamsOptions, + UpdatableStaticRouteOption, + LooseReturnType, + LooseAsyncReturnType, + ContextReturnType, + ContextAsyncReturnType, + ResolveRouteContext, + ResolveLoaderData, + RoutePrefix, + TrimPath, + TrimPathLeft, + TrimPathRight, + ResolveSearchSchemaFnInput, + ResolveSearchSchemaInput, + ResolveSearchSchemaFn, + ResolveSearchSchema, +} from './route' + +export { defaultSerializeError } from './router' +export type { + ViewTransitionOptions, + ExtractedBaseEntry, + ExtractedStream, + ExtractedPromise, + ExtractedEntry, + StreamState, + TrailingSlashOption, +} from './router' + +export type { MatchLocation, CommitLocationOptions } from './RouterProvider' + +export { retainSearchParams, stripSearchParams } from './searchMiddleware' + +export { + defaultParseSearch, + defaultStringifySearch, + parseSearchWith, + stringifySearchWith, +} from './searchParams' +export type { SearchSerializer, SearchParser } from './searchParams' + +export type { OptionalStructuralSharing } from './structuralSharing' + +export { + last, + functionalUpdate, + pick, + replaceEqualDeep, + isPlainObject, + isPlainArray, + deepEqual, + escapeJSON, + shallow, + createControlledPromise, +} from './utils' +export type { + NoInfer, + IsAny, + PickAsRequired, + PickRequired, + PickOptional, + WithoutEmpty, + Expand, + DeepPartial, + MakeDifferenceOptional, + IsUnion, + IsNonEmptyObject, + Assign, + IntersectAssign, + Timeout, + Updater, + NonNullableUpdater, + StringLiteral, + ThrowOrOptional, + ControlledPromise, + ExtractObjects, + PartialMergeAllObject, + MergeAllPrimitive, + ExtractPrimitives, + PartialMergeAll, + Constrain, + ConstrainLiteral, + UnionToIntersection, + MergeAllObjects, + MergeAll, + ValidateJSON, +} from './utils' + +export type { + StandardSchemaValidatorProps, + StandardSchemaValidator, + AnyStandardSchemaValidator, + StandardSchemaValidatorTypes, + AnyStandardSchemaValidateSuccess, + AnyStandardSchemaValidateFailure, + AnyStandardSchemaValidateIssue, + AnyStandardSchemaValidateInput, + AnyStandardSchemaValidate, + ValidatorObj, + AnyValidatorObj, + ValidatorAdapter, + AnyValidatorAdapter, + AnyValidatorFn, + ValidatorFn, + Validator, + AnyValidator, + AnySchema, + DefaultValidator, + ResolveSearchValidatorInputFn, + ResolveSearchValidatorInput, + ResolveValidatorInputFn, + ResolveValidatorInput, + ResolveValidatorOutputFn, + ResolveValidatorOutput, +} from './validators' diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts new file mode 100644 index 0000000000..d3d516dbe8 --- /dev/null +++ b/packages/router-core/src/link.ts @@ -0,0 +1,141 @@ +export type IsRequiredParams = + Record extends TParams ? never : true + +export type ParsePathParams = T & + `${string}$${string}` extends never + ? TAcc + : T extends `${string}$${infer TPossiblyParam}` + ? TPossiblyParam extends '' + ? TAcc + : TPossiblyParam & `${string}/${string}` extends never + ? TPossiblyParam | TAcc + : TPossiblyParam extends `${infer TParam}/${infer TRest}` + ? ParsePathParams + : never + : TAcc + +export type AddTrailingSlash = T & `${string}/` extends never + ? `${T & string}/` + : T + +export type RemoveTrailingSlashes = T & `${string}/` extends never + ? T + : T extends `${infer R}/` + ? R + : T + +export type AddLeadingSlash = T & `/${string}` extends never + ? `/${T & string}` + : T + +export type RemoveLeadingSlashes = T & `/${string}` extends never + ? T + : T extends `/${infer R}` + ? R + : T + +export interface ActiveOptions { + exact?: boolean + includeHash?: boolean + includeSearch?: boolean + explicitUndefined?: boolean +} + +export interface LinkOptionsProps { + /** + * The standard anchor tag target attribute + */ + target?: HTMLAnchorElement['target'] + /** + * Configurable options to determine if the link should be considered active or not + * @default {exact:true,includeHash:true} + */ + activeOptions?: ActiveOptions + /** + * The preloading strategy for this link + * - `false` - No preloading + * - `'intent'` - Preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there. + * - `'viewport'` - Preload the linked route when it enters the viewport + */ + preload?: false | 'intent' | 'viewport' | 'render' + /** + * When a preload strategy is set, this delays the preload by this many milliseconds. + * If the user exits the link before this delay, the preload will be cancelled. + */ + preloadDelay?: number + /** + * Control whether the link should be disabled or not + * If set to `true`, the link will be rendered without an `href` attribute + * @default false + */ + disabled?: boolean +} + +type JoinPath = TRight extends '' + ? TLeft + : TLeft extends '' + ? TRight + : `${RemoveTrailingSlashes}/${RemoveLeadingSlashes}` + +type RemoveLastSegment< + T extends string, + TAcc extends string = '', +> = T extends `${infer TSegment}/${infer TRest}` + ? TRest & `${string}/${string}` extends never + ? TRest extends '' + ? TAcc + : `${TAcc}${TSegment}` + : RemoveLastSegment + : TAcc + +export type ResolveCurrentPath< + TFrom extends string, + TTo extends string, +> = TTo extends '.' + ? TFrom + : TTo extends './' + ? AddTrailingSlash + : TTo & `./${string}` extends never + ? never + : TTo extends `./${infer TRest}` + ? AddLeadingSlash> + : never + +export type ResolveParentPath< + TFrom extends string, + TTo extends string, +> = TTo extends '../' | '..' + ? TFrom extends '' | '/' + ? never + : AddLeadingSlash> + : TTo & `../${string}` extends never + ? AddLeadingSlash> + : TFrom extends '' | '/' + ? never + : TTo extends `../${infer ToRest}` + ? ResolveParentPath, ToRest> + : AddLeadingSlash> + +export type ResolveRelativePath = string extends TFrom + ? TTo + : string extends TTo + ? TFrom + : undefined extends TTo + ? TFrom + : TTo extends string + ? TFrom extends string + ? TTo extends `/${string}` + ? TTo + : TTo extends `..${string}` + ? ResolveParentPath + : TTo extends `.${string}` + ? ResolveCurrentPath + : AddLeadingSlash> + : never + : never + +export type LinkCurrentTargetElement = { + preloadTimeout?: null | ReturnType +} + +export const preloadWarning = 'Error preloading route! ☝️' diff --git a/packages/react-router/src/location.ts b/packages/router-core/src/location.ts similarity index 100% rename from packages/react-router/src/location.ts rename to packages/router-core/src/location.ts diff --git a/packages/react-router/src/manifest.ts b/packages/router-core/src/manifest.ts similarity index 100% rename from packages/react-router/src/manifest.ts rename to packages/router-core/src/manifest.ts diff --git a/packages/react-router/src/path.ts b/packages/router-core/src/path.ts similarity index 100% rename from packages/react-router/src/path.ts rename to packages/router-core/src/path.ts diff --git a/packages/react-router/src/qss.ts b/packages/router-core/src/qss.ts similarity index 100% rename from packages/react-router/src/qss.ts rename to packages/router-core/src/qss.ts diff --git a/packages/react-router/src/root.ts b/packages/router-core/src/root.ts similarity index 100% rename from packages/react-router/src/root.ts rename to packages/router-core/src/root.ts diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts new file mode 100644 index 0000000000..e43d805194 --- /dev/null +++ b/packages/router-core/src/route.ts @@ -0,0 +1,303 @@ +import type { ParsePathParams } from './link' +import type { RootRouteId } from './root' +import type { Assign } from './utils' +import type { + AnySchema, + AnyStandardSchemaValidator, + AnyValidatorAdapter, + AnyValidatorObj, + StandardSchemaValidator, + ValidatorAdapter, + ValidatorFn, + ValidatorObj, +} from './validators' + +export type AnyPathParams = {} + +export type SearchSchemaInput = { + __TSearchSchemaInput__: 'TSearchSchemaInput' +} + +export type AnyContext = {} + +export interface RouteContext {} + +export type PreloadableObj = { preload?: () => Promise } + +export type RoutePathOptions = + | { + path: TPath + } + | { + id: TCustomId + } + +export interface StaticDataRouteOption {} + +export type RoutePathOptionsIntersection = { + path: TPath + id: TCustomId +} + +export type SearchFilter = (prev: TInput) => TResult + +export type SearchMiddlewareContext = { + search: TSearchSchema + next: (newSearch: TSearchSchema) => TSearchSchema +} + +export type SearchMiddleware = ( + ctx: SearchMiddlewareContext, +) => TSearchSchema + +export type ResolveId< + TParentRoute, + TCustomId extends string, + TPath extends string, +> = TParentRoute extends { id: infer TParentId extends string } + ? RoutePrefix + : RootRouteId + +export type InferFullSearchSchema = TRoute extends { + types: { + fullSearchSchema: infer TFullSearchSchema + } +} + ? TFullSearchSchema + : {} + +export type InferFullSearchSchemaInput = TRoute extends { + types: { + fullSearchSchemaInput: infer TFullSearchSchemaInput + } +} + ? TFullSearchSchemaInput + : {} + +export type InferAllParams = TRoute extends { + types: { + allParams: infer TAllParams + } +} + ? TAllParams + : {} + +export type InferAllContext = unknown extends TRoute + ? TRoute + : TRoute extends { + types: { + allContext: infer TAllContext + } + } + ? TAllContext + : {} + +export type ResolveSearchSchemaFnInput = + TSearchValidator extends (input: infer TSearchSchemaInput) => any + ? TSearchSchemaInput extends SearchSchemaInput + ? Omit + : ResolveSearchSchemaFn + : AnySchema + +export type ResolveSearchSchemaInput = + TSearchValidator extends AnyStandardSchemaValidator + ? NonNullable['input'] + : TSearchValidator extends AnyValidatorAdapter + ? TSearchValidator['types']['input'] + : TSearchValidator extends AnyValidatorObj + ? ResolveSearchSchemaFnInput + : ResolveSearchSchemaFnInput + +export type ResolveSearchSchemaFn = TSearchValidator extends ( + ...args: any +) => infer TSearchSchema + ? TSearchSchema + : AnySchema + +export type ResolveSearchSchema = + unknown extends TSearchValidator + ? TSearchValidator + : TSearchValidator extends AnyStandardSchemaValidator + ? NonNullable['output'] + : TSearchValidator extends AnyValidatorAdapter + ? TSearchValidator['types']['output'] + : TSearchValidator extends AnyValidatorObj + ? ResolveSearchSchemaFn + : ResolveSearchSchemaFn + +export type ParseSplatParams = TPath & + `${string}$` extends never + ? TPath & `${string}$/${string}` extends never + ? never + : '_splat' + : '_splat' + +export interface SplatParams { + _splat?: string +} + +export type ResolveParams = + ParseSplatParams extends never + ? Record, string> + : Record, string> & SplatParams + +export type ParseParamsFn = ( + rawParams: ResolveParams, +) => TParams extends Record, any> + ? TParams + : Record, any> + +export type StringifyParamsFn = ( + params: TParams, +) => ResolveParams + +export type ParamsOptions = { + params?: { + parse?: ParseParamsFn + stringify?: StringifyParamsFn + } + + /** + @deprecated Use params.parse instead + */ + parseParams?: ParseParamsFn + + /** + @deprecated Use params.stringify instead + */ + stringifyParams?: StringifyParamsFn +} + +interface RequiredStaticDataRouteOption { + staticData: StaticDataRouteOption +} + +interface OptionalStaticDataRouteOption { + staticData?: StaticDataRouteOption +} + +export type UpdatableStaticRouteOption = {} extends StaticDataRouteOption + ? OptionalStaticDataRouteOption + : RequiredStaticDataRouteOption + +export type MetaDescriptor = + | { charSet: 'utf-8' } + | { title: string } + | { name: string; content: string } + | { property: string; content: string } + | { httpEquiv: string; content: string } + | { 'script:ld+json': LdJsonObject } + | { tagName: 'meta' | 'link'; [name: string]: string } + | Record + +type LdJsonObject = { [Key in string]: LdJsonValue } & { + [Key in string]?: LdJsonValue | undefined +} +type LdJsonArray = Array | ReadonlyArray +type LdJsonPrimitive = string | number | boolean | null +type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray + +export type RouteLinkEntry = {} + +export type SearchValidator = + | ValidatorObj + | ValidatorFn + | ValidatorAdapter + | StandardSchemaValidator + | undefined + +export type AnySearchValidator = SearchValidator + +export type DefaultSearchValidator = SearchValidator< + Record, + AnySchema +> + +export type LooseReturnType = T extends ( + ...args: Array +) => infer TReturn + ? TReturn + : never + +export type LooseAsyncReturnType = T extends ( + ...args: Array +) => infer TReturn + ? TReturn extends Promise + ? TReturn + : TReturn + : never + +export type ContextReturnType = unknown extends TContextFn + ? TContextFn + : LooseReturnType extends never + ? AnyContext + : LooseReturnType + +export type ContextAsyncReturnType = unknown extends TContextFn + ? TContextFn + : LooseAsyncReturnType extends never + ? AnyContext + : LooseAsyncReturnType + +export type ResolveRouteContext = Assign< + ContextReturnType, + ContextAsyncReturnType +> + +export type ResolveLoaderData = unknown extends TLoaderFn + ? TLoaderFn + : LooseAsyncReturnType extends never + ? {} + : LooseAsyncReturnType + +export type RoutePrefix< + TPrefix extends string, + TPath extends string, +> = string extends TPath + ? RootRouteId + : TPath extends string + ? TPrefix extends RootRouteId + ? TPath extends '/' + ? '/' + : `/${TrimPath}` + : `${TPrefix}/${TPath}` extends '/' + ? '/' + : `/${TrimPathLeft<`${TrimPathRight}/${TrimPath}`>}` + : never + +export type TrimPath = '' extends T + ? '' + : TrimPathRight> + +export type TrimPathLeft = + T extends `${RootRouteId}/${infer U}` + ? TrimPathLeft + : T extends `/${infer U}` + ? TrimPathLeft + : T +export type TrimPathRight = T extends '/' + ? '/' + : T extends `${infer U}/` + ? TrimPathRight + : T + +/** + * @deprecated Use `ErrorComponentProps` instead. + */ +export type ErrorRouteProps = { + error: unknown + info?: { componentStack: string } + reset: () => void +} + +export type ErrorComponentProps = { + error: Error + info?: { componentStack: string } + reset: () => void +} +export type NotFoundRouteProps = { + // TODO: Make sure this is `| null | undefined` (this is for global not-founds) + data: unknown +} + +// diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts new file mode 100644 index 0000000000..12aebe9d47 --- /dev/null +++ b/packages/router-core/src/router.ts @@ -0,0 +1,50 @@ +import type { DeferredPromiseState } from './defer' +import type { ControlledPromise } from './utils' + +export interface ViewTransitionOptions { + types: Array +} + +export function defaultSerializeError(err: unknown) { + if (err instanceof Error) { + const obj = { + name: err.name, + message: err.message, + } + + if (process.env.NODE_ENV === 'development') { + ;(obj as any).stack = err.stack + } + + return obj + } + + return { + data: err, + } +} +export interface ExtractedBaseEntry { + dataType: '__beforeLoadContext' | 'loaderData' + type: string + path: Array + id: number + matchIndex: number +} + +export interface ExtractedStream extends ExtractedBaseEntry { + type: 'stream' + streamState: StreamState +} + +export interface ExtractedPromise extends ExtractedBaseEntry { + type: 'promise' + promiseState: DeferredPromiseState +} + +export type ExtractedEntry = ExtractedStream | ExtractedPromise + +export type StreamState = { + promises: Array> +} + +export type TrailingSlashOption = 'always' | 'never' | 'preserve' diff --git a/packages/react-router/src/searchMiddleware.ts b/packages/router-core/src/searchMiddleware.ts similarity index 100% rename from packages/react-router/src/searchMiddleware.ts rename to packages/router-core/src/searchMiddleware.ts diff --git a/packages/react-router/src/searchParams.ts b/packages/router-core/src/searchParams.ts similarity index 100% rename from packages/react-router/src/searchParams.ts rename to packages/router-core/src/searchParams.ts diff --git a/packages/react-router/src/serializer.ts b/packages/router-core/src/serializer.ts similarity index 100% rename from packages/react-router/src/serializer.ts rename to packages/router-core/src/serializer.ts diff --git a/packages/router-core/src/structuralSharing.ts b/packages/router-core/src/structuralSharing.ts new file mode 100644 index 0000000000..93111cd8c3 --- /dev/null +++ b/packages/router-core/src/structuralSharing.ts @@ -0,0 +1,7 @@ +import type { Constrain } from './utils' + +export interface OptionalStructuralSharing { + readonly structuralSharing?: + | Constrain + | undefined +} diff --git a/packages/router-core/src/utils.ts b/packages/router-core/src/utils.ts new file mode 100644 index 0000000000..d332b209d7 --- /dev/null +++ b/packages/router-core/src/utils.ts @@ -0,0 +1,403 @@ +export type NoInfer = [T][T extends any ? 0 : never] +export type IsAny = 1 extends 0 & TValue + ? TYesResult + : TNoResult + +export type PickAsRequired = Omit< + TValue, + TKey +> & + Required> + +export type PickRequired = { + [K in keyof T as undefined extends T[K] ? never : K]: T[K] +} + +export type PickOptional = { + [K in keyof T as undefined extends T[K] ? K : never]: T[K] +} + +// from https://stackoverflow.com/a/76458160 +export type WithoutEmpty = T extends any ? ({} extends T ? never : T) : never + +export type Expand = T extends object + ? T extends infer O + ? O extends Function + ? O + : { [K in keyof O]: O[K] } + : never + : T + +export type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial + } + : T + +export type MakeDifferenceOptional = Omit< + TRight, + keyof TLeft +> & { + [K in keyof TLeft & keyof TRight]?: TRight[K] +} + +// from https://stackoverflow.com/a/53955431 +// eslint-disable-next-line @typescript-eslint/naming-convention +export type IsUnion = ( + T extends any ? (U extends T ? false : true) : never +) extends false + ? false + : true + +export type IsNonEmptyObject = T extends object + ? keyof T extends never + ? false + : true + : false + +export type Assign = TLeft extends any + ? TRight extends any + ? IsNonEmptyObject extends false + ? TRight + : IsNonEmptyObject extends false + ? TLeft + : keyof TLeft & keyof TRight extends never + ? TLeft & TRight + : Omit & TRight + : never + : never + +export type IntersectAssign = TLeft extends any + ? TRight extends any + ? IsNonEmptyObject extends false + ? TRight + : IsNonEmptyObject extends false + ? TLeft + : TRight & TLeft + : never + : never + +export type Timeout = ReturnType + +export type Updater = + | TResult + | ((prev?: TPrevious) => TResult) + +export type NonNullableUpdater = + | TResult + | ((prev: TPrevious) => TResult) + +export type ExtractObjects = TUnion extends MergeAllPrimitive + ? never + : TUnion + +export type PartialMergeAllObject = + ExtractObjects extends infer TObj + ? { + [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any + ? TKey extends keyof TObj + ? TObj[TKey] + : never + : never + } + : never + +export type MergeAllPrimitive = + | ReadonlyArray + | number + | string + | bigint + | boolean + | symbol + | undefined + | null + +export type ExtractPrimitives = TUnion extends MergeAllPrimitive + ? TUnion + : TUnion extends object + ? never + : TUnion + +export type PartialMergeAll = + | ExtractPrimitives + | PartialMergeAllObject + +export type Constrain = + | (T extends TConstraint ? T : never) + | TDefault + +export type ConstrainLiteral = + | (T & TConstraint) + | TDefault + +/** + * To be added to router types + */ +export type UnionToIntersection = ( + T extends any ? (arg: T) => any : never +) extends (arg: infer T) => any + ? T + : never + +/** + * Merges everything in a union into one object. + * This mapped type is homomorphic which means it preserves stuff! :) + */ +export type MergeAllObjects< + TUnion, + TIntersected = UnionToIntersection>, +> = [keyof TIntersected] extends [never] + ? never + : { + [TKey in keyof TIntersected]: TUnion extends any + ? TUnion[TKey & keyof TUnion] + : never + } + +export type MergeAll = + | MergeAllObjects + | ExtractPrimitives + +export type ValidateJSON = ((...args: Array) => any) extends T + ? unknown extends T + ? never + : 'Function is not serializable' + : { [K in keyof T]: ValidateJSON } + +export function last(arr: Array) { + return arr[arr.length - 1] +} + +function isFunction(d: any): d is Function { + return typeof d === 'function' +} + +export function functionalUpdate( + updater: Updater | NonNullableUpdater, + previous: TPrevious, +): TResult { + if (isFunction(updater)) { + return updater(previous) + } + + return updater +} + +export function pick( + parent: TValue, + keys: Array, +): Pick { + return keys.reduce((obj: any, key: TKey) => { + obj[key] = parent[key] + return obj + }, {} as any) +} + +/** + * This function returns `prev` if `_next` is deeply equal. + * If not, it will replace any deeply equal children of `b` with those of `a`. + * This can be used for structural sharing between immutable JSON values for example. + * Do not use this with signals + */ +export function replaceEqualDeep(prev: any, _next: T): T { + if (prev === _next) { + return prev + } + + const next = _next as any + + const array = isPlainArray(prev) && isPlainArray(next) + + if (array || (isPlainObject(prev) && isPlainObject(next))) { + const prevItems = array ? prev : Object.keys(prev) + const prevSize = prevItems.length + const nextItems = array ? next : Object.keys(next) + const nextSize = nextItems.length + const copy: any = array ? [] : {} + + let equalItems = 0 + + for (let i = 0; i < nextSize; i++) { + const key = array ? i : (nextItems[i] as any) + if ( + ((!array && prevItems.includes(key)) || array) && + prev[key] === undefined && + next[key] === undefined + ) { + copy[key] = undefined + equalItems++ + } else { + copy[key] = replaceEqualDeep(prev[key], next[key]) + if (copy[key] === prev[key] && prev[key] !== undefined) { + equalItems++ + } + } + } + + return prevSize === nextSize && equalItems === prevSize ? prev : copy + } + + return next +} + +// Copied from: https://github.com/jonschlinkert/is-plain-object +export function isPlainObject(o: any) { + if (!hasObjectPrototype(o)) { + return false + } + + // If has modified constructor + const ctor = o.constructor + if (typeof ctor === 'undefined') { + return true + } + + // If has modified prototype + const prot = ctor.prototype + if (!hasObjectPrototype(prot)) { + return false + } + + // If constructor does not have an Object-specific method + if (!prot.hasOwnProperty('isPrototypeOf')) { + return false + } + + // Most likely a plain Object + return true +} + +function hasObjectPrototype(o: any) { + return Object.prototype.toString.call(o) === '[object Object]' +} + +export function isPlainArray(value: unknown): value is Array { + return Array.isArray(value) && value.length === Object.keys(value).length +} + +function getObjectKeys(obj: any, ignoreUndefined: boolean) { + let keys = Object.keys(obj) + if (ignoreUndefined) { + keys = keys.filter((key) => obj[key] !== undefined) + } + return keys +} + +export function deepEqual( + a: any, + b: any, + opts?: { partial?: boolean; ignoreUndefined?: boolean }, +): boolean { + if (a === b) { + return true + } + + if (typeof a !== typeof b) { + return false + } + + if (isPlainObject(a) && isPlainObject(b)) { + const ignoreUndefined = opts?.ignoreUndefined ?? true + const aKeys = getObjectKeys(a, ignoreUndefined) + const bKeys = getObjectKeys(b, ignoreUndefined) + + if (!opts?.partial && aKeys.length !== bKeys.length) { + return false + } + + return bKeys.every((key) => deepEqual(a[key], b[key], opts)) + } + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false + } + return !a.some((item, index) => !deepEqual(item, b[index], opts)) + } + + return false +} + +export type StringLiteral = T extends string + ? string extends T + ? string + : T + : never + +export type ThrowOrOptional = TThrow extends true + ? T + : T | undefined + +export type ControlledPromise = Promise & { + resolve: (value: T) => void + reject: (value: any) => void + status: 'pending' | 'resolved' | 'rejected' + value?: T +} + +export function createControlledPromise(onResolve?: (value: T) => void) { + let resolveLoadPromise!: (value: T) => void + let rejectLoadPromise!: (value: any) => void + + const controlledPromise = new Promise((resolve, reject) => { + resolveLoadPromise = resolve + rejectLoadPromise = reject + }) as ControlledPromise + + controlledPromise.status = 'pending' + + controlledPromise.resolve = (value: T) => { + controlledPromise.status = 'resolved' + controlledPromise.value = value + resolveLoadPromise(value) + onResolve?.(value) + } + + controlledPromise.reject = (e) => { + controlledPromise.status = 'rejected' + rejectLoadPromise(e) + } + + return controlledPromise +} + +/** + * + * @deprecated use `jsesc` instead + */ +export function escapeJSON(jsonString: string) { + return jsonString + .replace(/\\/g, '\\\\') // Escape backslashes + .replace(/'/g, "\\'") // Escape single quotes + .replace(/"/g, '\\"') // Escape double quotes +} + +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + const keysA = Object.keys(objA) + if (keysA.length !== Object.keys(objB).length) { + return false + } + + for (const item of keysA) { + if ( + !Object.prototype.hasOwnProperty.call(objB, item) || + !Object.is(objA[item as keyof T], objB[item as keyof T]) + ) { + return false + } + } + return true +} diff --git a/packages/react-router/src/validators.ts b/packages/router-core/src/validators.ts similarity index 100% rename from packages/react-router/src/validators.ts rename to packages/router-core/src/validators.ts diff --git a/packages/react-router/tests/path.test.ts b/packages/router-core/tests/path.test.ts similarity index 100% rename from packages/react-router/tests/path.test.ts rename to packages/router-core/tests/path.test.ts diff --git a/packages/react-router/tests/qss.test.ts b/packages/router-core/tests/qss.test.ts similarity index 100% rename from packages/react-router/tests/qss.test.ts rename to packages/router-core/tests/qss.test.ts diff --git a/packages/react-router/tests/utils.test.ts b/packages/router-core/tests/utils.test.ts similarity index 100% rename from packages/react-router/tests/utils.test.ts rename to packages/router-core/tests/utils.test.ts diff --git a/packages/router-core/tsconfig.json b/packages/router-core/tsconfig.json new file mode 100644 index 0000000000..9b21ee123b --- /dev/null +++ b/packages/router-core/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "vite.config.ts", "tests"] +} diff --git a/packages/router-core/vite.config.ts b/packages/router-core/vite.config.ts new file mode 100644 index 0000000000..5edc0264cb --- /dev/null +++ b/packages/router-core/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/config/vite' +import packageJson from './package.json' + +const config = defineConfig({ + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'jsdom', + typecheck: { enabled: true }, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: './src/index.ts', + srcDir: './src', + }), +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13527f6935..cd0527c492 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,7 @@ overrides: vite: 6.0.11 '@tanstack/react-query': 5.64.2 '@tanstack/history': workspace:* + '@tanstack/router-core': workspace:* '@tanstack/react-cross-context': workspace:* '@tanstack/react-router': workspace:* '@tanstack/router-cli': workspace:* @@ -3511,6 +3512,9 @@ importers: '@tanstack/react-store': specifier: ^0.7.0 version: 0.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/router-core': + specifier: workspace:* + version: link:../router-core jsesc: specifier: ^3.1.0 version: 3.1.0 @@ -3580,6 +3584,15 @@ importers: specifier: ^17.0.33 version: 17.0.33 + packages/router-core: + dependencies: + '@tanstack/history': + specifier: workspace:* + version: link:../history + '@tanstack/store': + specifier: ^0.6.0 + version: 0.6.0 + packages/router-devtools: dependencies: '@tanstack/react-router': @@ -6177,6 +6190,9 @@ packages: react: ^19.0.0 react-dom: ^19.0.0 + '@tanstack/store@0.6.0': + resolution: {integrity: sha512-+m2OBglsjXcLmmKOX6/9v8BDOCtyxhMmZLsRUDswOOSdIIR9mvv6i0XNKsmTh3AlYU8c1mRcodC8/Vyf+69VlQ==} + '@tanstack/store@0.7.0': resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} @@ -12989,6 +13005,8 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) + '@tanstack/store@0.6.0': {} + '@tanstack/store@0.7.0': {} '@tanstack/virtual-core@3.11.2': {} diff --git a/scripts/publish.js b/scripts/publish.js index 9d99ae22d3..c56777bf8e 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -12,6 +12,10 @@ await publish({ name: '@tanstack/history', packageDir: 'packages/history', }, + { + name: '@tanstack/router-core', + packageDir: 'packages/router-core', + }, { name: '@tanstack/react-router', packageDir: 'packages/react-router',