UNPKG

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
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, }); };