Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin solid-router and router-core #3086

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/router-core/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @ts-check

import rootConfig from '../../eslint.config.js'

export default [...rootConfig]
65 changes: 65 additions & 0 deletions packages/router-core/package.json
Original file line number Diff line number Diff line change
@@ -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/history"
},
"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"
}
}
94 changes: 94 additions & 0 deletions packages/router-core/src/Matches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { Constrain } from './utils'

export type AnyMatchAndValue = { match: any; value: any }

export type FindValueByIndex<
TKey,
TValue extends ReadonlyArray<any>,
> = TKey extends `${infer TIndex extends number}` ? TValue[TIndex] : never

export type FindValueByKey<TKey, TValue> =
TValue extends ReadonlyArray<any>
? FindValueByIndex<TKey, TValue>
: TValue[TKey & keyof TValue]

export type CreateMatchAndValue<TMatch, TValue> = TValue extends any
? {
match: TMatch
value: TValue
}
: never

export type NextMatchAndValue<
TKey,
TMatchAndValue extends AnyMatchAndValue,
> = TMatchAndValue extends any
? CreateMatchAndValue<
TMatchAndValue['match'],
FindValueByKey<TKey, TMatchAndValue['value']>
>
: never

export type IsMatchKeyOf<TValue> =
TValue extends ReadonlyArray<any>
? 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<TMatchAndValue['value']>}`

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<TFirst, TMatchAndValue>,
`${TParentPath}${TFirst}.`
>
: never
: {
path: IsMatchPath<TParentPath, TMatchAndValue>
result: IsMatchResult<TPath, TMatchAndValue>
}

export type IsMatch<TMatch, TPath> = IsMatchParse<
TPath,
TMatch extends any ? { match: TMatch; value: TMatch } : never
>

/**
* Narrows matches based on a path
* @experimental
*/
export const isMatch = <TMatch, TPath extends string>(
match: TMatch,
path: Constrain<TPath, IsMatch<TMatch, TPath>['path']>,
): match is IsMatch<TMatch, TPath>['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
}
20 changes: 20 additions & 0 deletions packages/router-core/src/RouterProvider.ts
Original file line number Diff line number Diff line change
@@ -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
}
52 changes: 52 additions & 0 deletions packages/router-core/src/defer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { defaultSerializeError } from './router'

export const TSR_DEFERRED_PROMISE = Symbol.for('TSR_DEFERRED_PROMISE')

export type DeferredPromiseState<T> = {
[TSR_DEFERRED_PROMISE]:
| {
status: 'pending'
data?: T
error?: unknown
}
| {
status: 'success'
data: T
}
| {
status: 'error'
data?: T
error: unknown
}
}

export type DeferredPromise<T> = Promise<T> & DeferredPromiseState<T>

export function defer<T>(
_promise: Promise<T>,
options?: {
serializeError?: typeof defaultSerializeError
},
) {
const promise = _promise as DeferredPromise<T>
// this is already deferred promise
if ((promise as any)[TSR_DEFERRED_PROMISE]) {
return promise
}
promise[TSR_DEFERRED_PROMISE] = { status: 'pending' }

promise
.then((data) => {
promise[TSR_DEFERRED_PROMISE].status = 'success'
promise[TSR_DEFERRED_PROMISE].data = data
})
.catch((error) => {
promise[TSR_DEFERRED_PROMISE].status = 'error'
;(promise[TSR_DEFERRED_PROMISE] as any).error = {
data: (options?.serializeError ?? defaultSerializeError)(error),
__isServerError: true,
}
})

return promise
}
9 changes: 9 additions & 0 deletions packages/router-core/src/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { HistoryLocation } from '@tanstack/history'

declare module '@tanstack/history' {
interface HistoryState {
__tempLocation?: HistoryLocation
__tempKey?: string
__hashScrollIntoViewOptions?: boolean | ScrollIntoViewOptions
}
}
20 changes: 20 additions & 0 deletions packages/router-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export * from './defer'
export * from './history'
export * from './isServerSideError'
export * from './link'
export * from './location'
export * from './manifest'
export * from './Matches'
export * from './path'
export * from './qss'
export * from './root'
export * from './route'
export * from './routeInfo'
export * from './router'
export * from './RouterProvider'
export * from './searchMiddleware'
export * from './searchParams'
export * from './structuralSharing'
export * from './transformer'
export * from './utils'
export * from './validators'
23 changes: 23 additions & 0 deletions packages/router-core/src/isServerSideError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function isServerSideError(error: unknown): error is {
__isServerError: true
data: Record<string, any>
} {
if (!(typeof error === 'object' && error && 'data' in error)) return false
if (!('__isServerError' in error && error.__isServerError)) return false
if (!(typeof error.data === 'object' && error.data)) return false

return error.__isServerError === true
}

export function defaultDeserializeError(serializedData: Record<string, any>) {
if ('name' in serializedData && 'message' in serializedData) {
const error = new Error(serializedData.message)
error.name = serializedData.name
if (process.env.NODE_ENV === 'development') {
error.stack = serializedData.stack
}
return error
}

return serializedData.data
}
Loading
Loading