Skip to content

Commit

Permalink
feat(remix-server-runtime): bring Serialize type more in line with …
Browse files Browse the repository at this point in the history
…`type-fest`'s `Jsonify` type
  • Loading branch information
MichaelDeBoey committed Aug 1, 2023
1 parent 3b808ce commit 00dae7a
Showing 1 changed file with 105 additions and 31 deletions.
136 changes: 105 additions & 31 deletions packages/remix-server-runtime/serialize.ts
Original file line number Diff line number Diff line change
@@ -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<T> = 0 extends 1 & T ? true : false;
type IsNever<T> = [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<T> =
type Jsonify<T> =
IsAny<T> extends true ? any :
T extends TypedDeferredData<infer U> ? SerializeDeferred<U> :
T extends PositiveInfinity | NegativeInfinity ? null :
T extends Boolean ? boolean :
T extends Number ? number :
T extends String ? string :
T extends Map<any, any> | Set<any> ? EmptyObject :
T extends TypedArray ? Record<string, number> :
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<J>) :
T extends [] ? [] :
T extends [unknown, ...unknown[]] ? SerializeTuple<T> :
T extends ReadonlyArray<infer U> ? (U extends NonJsonPrimitive ? null : Serialize<U>)[] :
T extends object ? SerializeObject<UndefinedToOptional<T>> :
never
;
T extends [unknown, ...unknown[]] ? JsonifyList<T> :
T extends ReadonlyArray<infer U> ? Array<U extends NotJsonable ? null : Jsonify<U>> :
T extends object ? JsonifyObject<UndefinedToOptional<T>> :
never;

type FilterNonNever<T extends unknown[]> = T extends [infer F, ...infer R]
? IsNever<F> extends true
? FilterNonNever<R>
: [F, ...FilterNonNever<R>]
: IsNever<T[number]> extends true
? []
: T;
/** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */
type SerializeTuple<T extends unknown[]> = T extends [infer F, ...infer R]
? [Serialize<F>, ...SerializeTuple<R>]
: [];
type JsonifyList<T extends unknown[]> = T extends [infer F, ...infer R]
? FilterNonNever<[Jsonify<F>, ...JsonifyList<R>]>
: Array<Jsonify<T[number]>>;

/** JSON serialize objects (not including arrays) and classes */
type SerializeObject<T extends object> = {
[k in keyof T as T[k] extends NonJsonPrimitive ? never : k]: Serialize<T[k]>;
type JsonifyObject<T extends object> = {
[Key in keyof Pick<T, FilterJsonableKeys<T>>]: Jsonify<T[Key]>;
};
type FilterJsonableKeys<T extends object> = {
[Key in keyof T]: T[Key] extends NotJsonable ? never : Key;
}[keyof T];

type Serialize<T> = T extends TypedDeferredData<infer U>
? SerializeDeferred<U>
: Jsonify<T>;

// prettier-ignore
type SerializeDeferred<T extends Record<string, unknown>> = {
[k in keyof T as
T[k] extends Promise<unknown> ? k :
T[k] extends NonJsonPrimitive ? never :
T[k] extends NotJsonable ? never :
k
]:
T[k] extends Promise<infer U>
Expand All @@ -60,15 +99,50 @@ type SerializeDeferred<T extends Record<string, unknown>> = {
* Example: { a: string | undefined} --> { a?: string}
*/
type UndefinedToOptional<T extends object> = {
// 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, FilterDefinedKeys<T>>]: 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<T, FilterOptionalKeys<T>>]?: Exclude<T[Key], undefined>;
};
// 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<Type, Key extends keyof Type> = Key extends symbol
? never
: Type[Key] extends symbol
? never
: [(...args: any[]) => any] extends [Type[Key]]
? never
: Key;
/**
Returns the required keys.
*/
type FilterDefinedKeys<T extends object> = Exclude<
{
[Key in keyof T]: IsAny<T[Key]> extends true
? Key
: undefined extends T[Key]
? never
: T[Key] extends undefined
? never
: BaseKeyFilter<T, Key>;
}[keyof T],
undefined
>;
/**
Returns the optional keys.
*/
type FilterOptionalKeys<T extends object> = Exclude<
{
[Key in keyof T]: IsAny<T[Key]> extends true
? never
: undefined extends T[Key]
? T[Key] extends undefined
? never
: BaseKeyFilter<T, Key>
: never;
}[keyof T],
undefined
>;

type ArbitraryFunction = (...args: any[]) => unknown;

Expand Down

0 comments on commit 00dae7a

Please sign in to comment.