@tanstack/router-core
Version:
Modern and scalable routing for React applications
308 lines (274 loc) • 9.27 kB
text/typescript
import { createPlugin } from 'seroval'
import { GLOBAL_TSR } from '../constants'
import type { Plugin, PluginInfo, SerovalNode } from 'seroval'
import type {
RegisteredConfigType,
RegisteredSsr,
SSROption,
} from '../../router'
import type { LooseReturnType } from '../../utils'
import type { AnyRoute, ResolveAllSSR } from '../../route'
import type { RawStream } from './RawStream'
declare const TSR_SERIALIZABLE: unique symbol
export type TSR_SERIALIZABLE = typeof TSR_SERIALIZABLE
export type TsrSerializable = { [TSR_SERIALIZABLE]: true }
export interface DefaultSerializable {
number: number
string: string
boolean: boolean
null: null
undefined: undefined
bigint: bigint
Date: Date
Uint8Array: Uint8Array
RawStream: RawStream
TsrSerializable: TsrSerializable
void: void
}
export interface SerializableExtensions extends DefaultSerializable {}
export type Serializable = SerializableExtensions[keyof SerializableExtensions]
export type UnionizeSerializationAdaptersInput<
TAdapters extends ReadonlyArray<AnySerializationAdapter>,
> = TAdapters[number]['~types']['input']
/**
* Create a strongly-typed serialization adapter for SSR hydration.
* Use to register custom types with the router serializer.
*/
export function createSerializationAdapter<
TInput = unknown,
TOutput = unknown,
const TExtendsAdapters extends
| ReadonlyArray<AnySerializationAdapter>
| never = never,
>(
opts: CreateSerializationAdapterOptions<TInput, TOutput, TExtendsAdapters>,
): SerializationAdapter<TInput, TOutput, TExtendsAdapters> {
return opts as unknown as SerializationAdapter<
TInput,
TOutput,
TExtendsAdapters
>
}
export interface CreateSerializationAdapterOptions<
TInput,
TOutput,
TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter> | never,
> {
key: string
extends?: TExtendsAdapters
test: (value: unknown) => value is TInput
toSerializable: (
value: TInput,
) => ValidateSerializable<
TOutput,
Serializable | UnionizeSerializationAdaptersInput<TExtendsAdapters>
>
fromSerializable: (value: TOutput) => TInput
}
export type ValidateSerializable<T, TSerializable> = T extends TSerializable
? T
: T extends (...args: Array<any>) => any
? SerializationError<'Function may not be serializable'>
: T extends RegisteredReadableStream
? SerializationError<'JSX is not be serializable'>
: T extends ReadonlyArray<any>
? ValidateSerializableArray<T, TSerializable>
: T extends Promise<any>
? ValidateSerializablePromise<T, TSerializable>
: T extends ReadableStream<any>
? ValidateReadableStream<T, TSerializable>
: T extends Set<any>
? ValidateSerializableSet<T, TSerializable>
: T extends Map<any, any>
? ValidateSerializableMap<T, TSerializable>
: T extends AsyncGenerator<any, any>
? ValidateSerializableAsyncGenerator<T, TSerializable>
: T extends object
? ValidateSerializableMapped<T, TSerializable>
: SerializationError<'Type may not be serializable'>
export type ValidateSerializableAsyncGenerator<T, TSerializable> =
T extends AsyncGenerator<infer T, infer TReturn, infer TNext>
? AsyncGenerator<
ValidateSerializable<T, TSerializable>,
ValidateSerializable<TReturn, TSerializable>,
TNext
>
: never
export type ValidateSerializablePromise<T, TSerializable> =
T extends Promise<infer TAwaited>
? Promise<ValidateSerializable<TAwaited, TSerializable>>
: never
export type ValidateReadableStream<T, TSerializable> =
T extends ReadableStream<infer TStreamed>
? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>
: never
export type ValidateSerializableSet<T, TSerializable> =
T extends Set<infer TItem>
? Set<ValidateSerializable<TItem, TSerializable>>
: never
export type ValidateSerializableMap<T, TSerializable> =
T extends Map<infer TKey, infer TValue>
? Map<
ValidateSerializable<TKey, TSerializable>,
ValidateSerializable<TValue, TSerializable>
>
: never
export type ValidateSerializableArray<T, TSerializable> = T extends readonly [
any,
...Array<any>,
]
? ValidateSerializableMapped<T, TSerializable>
: T extends Array<infer U>
? Array<ValidateSerializable<U, TSerializable>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<ValidateSerializable<U, TSerializable>>
: never
export type ValidateSerializableMapped<T, TSerializable> = {
[K in keyof T]: ValidateSerializable<T[K], TSerializable>
}
const SERIALIZATION_ERROR = Symbol.for('TSR_SERIALIZATION_ERROR')
export interface SerializationError<in out TMessage extends string> {
[SERIALIZATION_ERROR]: TMessage
}
export interface SerializationAdapter<
TInput,
TOutput,
TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,
> {
'~types': SerializationAdapterTypes<TInput, TOutput, TExtendsAdapters>
key: string
extends?: TExtendsAdapters
test: (value: unknown) => value is TInput
toSerializable: (value: TInput) => TOutput
fromSerializable: (value: TOutput) => TInput
}
export interface SerializationAdapterTypes<
TInput,
TOutput,
TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,
> {
input: TInput | UnionizeSerializationAdaptersInput<TExtendsAdapters>
output: TOutput
extends: TExtendsAdapters
}
export type AnySerializationAdapter = SerializationAdapter<any, any, any>
export interface AdapterNode extends PluginInfo {
v: SerovalNode
}
/** Create a Seroval plugin for server-side serialization only. */
/* @__NO_SIDE_EFFECTS__ */
export function makeSsrSerovalPlugin(
serializationAdapter: AnySerializationAdapter,
options: { didRun: boolean },
): Plugin<any, AdapterNode> {
return /* @__PURE__ */ createPlugin<any, AdapterNode>({
tag: '$TSR/t/' + serializationAdapter.key,
test: serializationAdapter.test,
parse: {
stream(value, ctx, _data) {
return {
v: ctx.parse(serializationAdapter.toSerializable(value)),
}
},
},
serialize(node, ctx, _data) {
options.didRun = true
return (
GLOBAL_TSR +
'.t.get("' +
serializationAdapter.key +
'")(' +
ctx.serialize(node.v) +
')'
)
},
// we never deserialize on the server during SSR
deserialize: undefined as never,
})
}
/** Create a Seroval plugin for client/server symmetric (de)serialization. */
/* @__NO_SIDE_EFFECTS__ */
export function makeSerovalPlugin(
serializationAdapter: AnySerializationAdapter,
): Plugin<any, AdapterNode> {
return /* @__PURE__ */ createPlugin<any, AdapterNode>({
tag: '$TSR/t/' + serializationAdapter.key,
test: serializationAdapter.test,
parse: {
sync(value, ctx, _data) {
return {
v: ctx.parse(serializationAdapter.toSerializable(value)),
}
},
async async(value, ctx, _data) {
return {
v: await ctx.parse(serializationAdapter.toSerializable(value)),
}
},
stream(value, ctx, _data) {
return {
v: ctx.parse(serializationAdapter.toSerializable(value)),
}
},
},
// we don't generate JS code outside of SSR (for now)
serialize: undefined as never,
deserialize(node, ctx, _data) {
return serializationAdapter.fromSerializable(ctx.deserialize(node.v))
},
})
}
export type ValidateSerializableInput<TRegister, T> = ValidateSerializable<
T,
RegisteredSerializableInput<TRegister>
>
export type RegisteredSerializableInput<TRegister> =
| (unknown extends RegisteredSerializationAdapters<TRegister>
? never
: RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>
? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']
: never)
| Serializable
export type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<
TRegister,
'serializationAdapters'
>
export type RegisteredSSROption<TRegister> =
unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>
? SSROption
: RegisteredConfigType<TRegister, 'defaultSsr'>
export type ValidateSerializableLifecycleResult<
TRegister,
TParentRoute extends AnyRoute,
TSSR,
TFn,
> =
false extends RegisteredSsr<TRegister>
? any
: ValidateSerializableLifecycleResultSSR<
TRegister,
TParentRoute,
TSSR,
TFn
> extends infer TInput
? TInput
: never
export type ValidateSerializableLifecycleResultSSR<
TRegister,
TParentRoute extends AnyRoute,
TSSR,
TFn,
> =
ResolveAllSSR<TParentRoute, TSSR> extends false
? any
: RegisteredSSROption<TRegister> extends false
? any
: ValidateSerializableInput<TRegister, LooseReturnType<TFn>>
export type RegisteredReadableStream =
unknown extends SerializerExtensions['ReadableStream']
? never
: SerializerExtensions['ReadableStream']
export interface DefaultSerializerExtensions {
ReadableStream: unknown
}
export interface SerializerExtensions extends DefaultSerializerExtensions {}