308 lines
9.1 KiB
Plaintext
308 lines
9.1 KiB
Plaintext
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 {}
|