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.
162 lines (161 loc) • 5.84 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.redirect = exports.deserializeRemix = exports.stringifyRemix = exports.useTypedRouteLoaderData = exports.useTypedFetcher = exports.useTypedActionData = exports.useTypedLoaderData = exports.TypedAwait = exports.typeddefer = exports.typedjson = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("@remix-run/react");
const server_runtime_1 = require("@remix-run/server-runtime");
const _typedjson = __importStar(require("./typedjson"));
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,
});
};
exports.typedjson = typedjson;
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 (0, server_runtime_1.defer)(data, responseInit);
};
exports.typeddefer = typeddefer;
function TypedAwait(props) {
if (!props.children)
return null;
return ((0, jsx_runtime_1.jsx)(react_1.Await, { ...props, children: data => {
if (data === null)
return null;
let deserializedData = deserializeRemix(data);
return props.children(deserializedData);
} }));
}
exports.TypedAwait = TypedAwait;
function useTypedLoaderData() {
const data = (0, react_1.useLoaderData)();
return deserializeRemix(data);
}
exports.useTypedLoaderData = useTypedLoaderData;
function useTypedActionData() {
const data = (0, react_1.useActionData)();
return deserializeRemix(data);
}
exports.useTypedActionData = useTypedActionData;
function useTypedFetcher(opts) {
const fetcher = (0, react_1.useFetcher)(opts);
if (fetcher.data) {
const newData = deserializeRemix(fetcher.data);
fetcher.data = newData ?? undefined;
}
return fetcher;
}
exports.useTypedFetcher = useTypedFetcher;
function useTypedRouteLoaderData(id) {
const match = (0, react_1.useMatches)().find(match => match.id === id);
if (!match)
return undefined;
return deserializeRemix(match.data);
}
exports.useTypedRouteLoaderData = useTypedRouteLoaderData;
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;
}
exports.stringifyRemix = stringifyRemix;
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;
}
exports.deserializeRemix = deserializeRemix;
/**
* A redirect response. Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/api/remix#redirect
*/
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,
});
};
exports.redirect = redirect;