Skip to content

Instantly share code, notes, and snippets.

@mahdiboomeri
Created December 27, 2022 20:42
Show Gist options
  • Save mahdiboomeri/a44661d62ba96f8306005eb303115533 to your computer and use it in GitHub Desktop.
Save mahdiboomeri/a44661d62ba96f8306005eb303115533 to your computer and use it in GitHub Desktop.

Revisions

  1. mahdiboomeri created this gist Dec 27, 2022.
    96 changes: 96 additions & 0 deletions useNonNullableSerializableFetch.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,96 @@
    import type { FetchError } from "ofetch";
    import type { NitroFetchRequest } from "nitropack";
    import type { FetchResult, UseFetchOptions, AsyncData } from "#app";
    import type {
    KeyOfRes,
    PickFrom,
    _AsyncData,
    } from "#app/composables/asyncData";
    import type { Ref } from "vue";

    /**
    * @see https://github.com/remix-run/remix/blob/2248669ed59fd716e267ea41df5d665d4781f4a9/packages/remix-server-runtime/serialize.ts
    */
    type JsonPrimitive =
    | string
    | number
    | boolean
    | String
    | Number
    | Boolean
    | null;
    type NonJsonPrimitive = undefined | Function | symbol;

    /*
    * `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;

    // prettier-ignore
    type Serialize<T> =
    IsAny<T> extends true ? any :
    T extends JsonPrimitive ? T :
    T extends Map<any,any> | Set<any> ? {} :
    T extends NonJsonPrimitive ? never :
    T extends { toJSON(): infer U } ? U :
    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;

    /** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */
    type SerializeTuple<T extends [unknown, ...unknown[]]> = {
    [k in keyof T]: T[k] extends NonJsonPrimitive ? null : Serialize<T[k]>;
    };

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

    /*
    * For an object T, if it has any properties that are a union with `undefined`,
    * make those into optional properties instead.
    *
    * Example: { a: string | undefined} --> { a?: string}
    */

    type FilterDefinedKeys<TObj extends object> = {
    [TKey in keyof TObj]: undefined extends TObj[TKey] ? never : TKey;
    }[keyof TObj];

    type UndefinedToOptional<T extends object> = {
    // Property is not a union with `undefined`, keep as-is
    [k in keyof Pick<T, FilterDefinedKeys<T>>]: T[k];
    } & {
    // Property _is_ a union with `defined`. Set as optional (via `?`) and remove `undefined` from the union
    [k in keyof Omit<T, FilterDefinedKeys<T>>]?: Exclude<T[k], undefined>;
    };

    interface _NonNullableAsyncData<Data, Error> extends _AsyncData<Data, Error> {
    data: Ref<Data>;
    }
    type NonNullableAsyncData<Data, Error> = _NonNullableAsyncData<Data, Error> &
    Promise<_NonNullableAsyncData<Data, Error>>;

    export const useSerializableFetch = <
    ResT = void,
    ErrorT = FetchError,
    ReqT extends NitroFetchRequest = NitroFetchRequest,
    _ResT = ResT extends void ? FetchResult<ReqT> : ResT,
    Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
    PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>
    >(
    request: Ref<ReqT> | ReqT | (() => ReqT),
    opts?: UseFetchOptions<_ResT, Transform, PickKeys>
    ) => {
    return useFetch<ResT, ErrorT, ReqT, _ResT, Transform, PickKeys>(
    request,
    opts
    ) as NonNullableAsyncData<
    PickFrom<Serialize<ReturnType<Transform>>, PickKeys>,
    ErrorT | null
    >;
    };