diff --git a/packages/remix-server-runtime/serialize.ts b/packages/remix-server-runtime/serialize.ts index 6fd357b315c..34e9e828ec1 100644 --- a/packages/remix-server-runtime/serialize.ts +++ b/packages/remix-server-runtime/serialize.ts @@ -1,51 +1,90 @@ import type { AppData } from "./data"; import type { TypedDeferredData, TypedResponse } from "./responses"; -type JsonPrimitive = - | string - | number - | boolean - | String - | Number - | Boolean - | null; -type NonJsonPrimitive = undefined | Function | symbol; +type JsonPrimitive = boolean | number | string | null; +type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol; // Note: The return value has to be `any` and not `unknown` so it can match `void` /* * `any` is the only type that can let you equate `0` with `1` * See https://stackoverflow.com/a/49928360/1490091 */ type IsAny = 0 extends 1 & T ? true : false; +type IsNever = [T] extends [never] ? true : false; + +// eslint-disable-next-line @typescript-eslint/no-loss-of-precision +type PositiveInfinity = 1e999; +// eslint-disable-next-line @typescript-eslint/no-loss-of-precision +type NegativeInfinity = -1e999; + +declare const emptyObjectSymbol: unique symbol; +type EmptyObject = { [emptyObjectSymbol]?: never }; + +type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +type JsonArray = JsonValue[] | readonly JsonValue[]; +type JsonObject = { [Key in string]: JsonValue } & { + [Key in string]?: JsonValue | undefined; +}; +type JsonValue = JsonPrimitive | JsonObject | JsonArray; // prettier-ignore -type Serialize = +type Jsonify = IsAny extends true ? any : - T extends TypedDeferredData ? SerializeDeferred : + T extends PositiveInfinity | NegativeInfinity ? null : + T extends Boolean ? boolean : + T extends Number ? number : + T extends String ? string : + T extends Map | Set ? EmptyObject : + T extends TypedArray ? Record : + T extends NotJsonable ? never : T extends JsonPrimitive ? T : - T extends NonJsonPrimitive ? never : - T extends { toJSON(): infer U } ? U : + T extends { toJSON(): infer J } ? ((() => J) extends () => JsonValue ? J : Jsonify) : T extends [] ? [] : - T extends [unknown, ...unknown[]] ? SerializeTuple : - T extends ReadonlyArray ? (U extends NonJsonPrimitive ? null : Serialize)[] : - T extends object ? SerializeObject> : - never -; + T extends [unknown, ...unknown[]] ? JsonifyList : + T extends ReadonlyArray ? Array> : + T extends object ? JsonifyObject> : + never; +type FilterNonNever = T extends [infer F, ...infer R] + ? IsNever extends true + ? FilterNonNever + : [F, ...FilterNonNever] + : IsNever extends true + ? [] + : T; /** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */ -type SerializeTuple = T extends [infer F, ...infer R] - ? [Serialize, ...SerializeTuple] - : []; +type JsonifyList = T extends [infer F, ...infer R] + ? FilterNonNever<[Jsonify, ...JsonifyList]> + : Array>; /** JSON serialize objects (not including arrays) and classes */ -type SerializeObject = { - [k in keyof T as T[k] extends NonJsonPrimitive ? never : k]: Serialize; +type JsonifyObject = { + [Key in keyof Pick>]: Jsonify; }; +type FilterJsonableKeys = { + [Key in keyof T]: T[Key] extends NotJsonable ? never : Key; +}[keyof T]; + +type Serialize = T extends TypedDeferredData + ? SerializeDeferred + : Jsonify; // prettier-ignore type SerializeDeferred> = { [k in keyof T as T[k] extends Promise ? k : - T[k] extends NonJsonPrimitive ? never : + T[k] extends NotJsonable ? never : k ]: T[k] extends Promise @@ -60,15 +99,50 @@ type SerializeDeferred> = { * Example: { a: string | undefined} --> { a?: string} */ type UndefinedToOptional = { - // Property is not a union with `undefined`, keep as-is - [k in keyof T as undefined extends T[k] ? never : k]: T[k]; + // Property is not a union with `undefined`, keep it as-is. + [Key in keyof Pick>]: T[Key]; } & { - // Property _is_ a union with `defined`. Set as optional (via `?`) and remove `undefined` from the union - [k in keyof T as undefined extends T[k] ? k : never]?: Exclude< - T[k], - undefined - >; + // Property _is_ a union with defined value. Set as optional (via `?`) and remove `undefined` from the union. + [Key in keyof Pick>]?: Exclude; }; +// Returns `never` if the key or property is not jsonable without testing whether the property is required or optional otherwise return the key. +type BaseKeyFilter = Key extends symbol + ? never + : Type[Key] extends symbol + ? never + : [(...args: any[]) => any] extends [Type[Key]] + ? never + : Key; +/** + Returns the required keys. + */ +type FilterDefinedKeys = Exclude< + { + [Key in keyof T]: IsAny extends true + ? Key + : undefined extends T[Key] + ? never + : T[Key] extends undefined + ? never + : BaseKeyFilter; + }[keyof T], + undefined +>; +/** + Returns the optional keys. + */ +type FilterOptionalKeys = Exclude< + { + [Key in keyof T]: IsAny extends true + ? never + : undefined extends T[Key] + ? T[Key] extends undefined + ? never + : BaseKeyFilter + : never; + }[keyof T], + undefined +>; type ArbitraryFunction = (...args: any[]) => unknown;