remix-typedjson
Version:
This package is a replacement for superjson to use in your Remix app. It handles a subset of types that `superjson` supports, but is faster and smaller.
126 lines (125 loc) • 4.06 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { Await, useActionData, useFetcher, useLoaderData, useMatches, } from '@remix-run/react';
import { defer } from '@remix-run/server-runtime';
import * as _typedjson from './typedjson';
export const typedjson = (data, init = {}) => {
let responseInit = typeof init === 'number' ? { status: init } : init;
let headers = new Headers(responseInit.headers);
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json; charset=utf-8');
}
return new Response(stringifyRemix(data), {
...responseInit,
headers,
});
};
export const typeddefer = (data, init = {}) => {
// wrap any Promises in the data with new Promises that will serialize the
// resolved data and add the meta to the response
Object.entries(data).forEach(([key, value]) => {
if (value instanceof Promise) {
;
data[key] = value.then(resolvedData => {
const { meta } = _typedjson.serialize(resolvedData);
if (meta) {
;
resolvedData['$$meta'] = meta;
}
return resolvedData;
});
}
else {
const { meta } = _typedjson.serialize(data);
if (meta) {
;
data['$$meta'] = meta;
}
}
});
let responseInit = typeof init === 'number' ? { status: init } : init;
return defer(data, responseInit);
};
export function TypedAwait(props) {
if (!props.children)
return null;
return (_jsx(Await, { ...props, children: data => {
if (data === null)
return null;
let deserializedData = deserializeRemix(data);
return props.children(deserializedData);
} }));
}
export function useTypedLoaderData() {
const data = useLoaderData();
return deserializeRemix(data);
}
export function useTypedActionData() {
const data = useActionData();
return deserializeRemix(data);
}
export function useTypedFetcher(opts) {
const fetcher = useFetcher(opts);
if (fetcher.data) {
const newData = deserializeRemix(fetcher.data);
fetcher.data = newData ?? undefined;
}
return fetcher;
}
export function useTypedRouteLoaderData(id) {
const match = useMatches().find(match => match.id === id);
if (!match)
return undefined;
return deserializeRemix(match.data);
}
export function stringifyRemix(data) {
// prevent double JSON stringification
let { json, meta } = _typedjson.serialize(data);
if (json && meta) {
if (json.startsWith('{')) {
json = `${json.substring(0, json.length - 1)},\"$$meta\":${JSON.stringify(meta)}}`;
}
else if (json.startsWith('[')) {
json = `{"$$obj":${json},"$$meta":${JSON.stringify(meta)}}`;
}
}
return json;
}
export function deserializeRemix(data) {
if (!data)
return data;
if (data.$$obj) {
// handle arrays wrapped in an object
return data.$$meta
? _typedjson.applyMeta(data.$$obj, data.$$meta)
: data.$$obj;
}
else if (data.$$meta) {
// handle object with $$meta key
// remove before applying meta
const meta = data.$$meta;
delete data.$$meta;
return _typedjson.applyMeta(data, meta);
}
return data;
}
/**
* A redirect response. Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/api/remix#redirect
*/
export const redirect = (url, init = 302) => {
let responseInit = init;
if (typeof responseInit === 'number') {
responseInit = { status: responseInit };
}
else if (typeof responseInit.status === 'undefined') {
responseInit.status = 302;
}
let headers = new Headers(responseInit.headers);
headers.set('Location', url);
return new Response(null, {
...responseInit,
headers,
});
};