remix-typedjson
Advanced tools
Comparing version 0.2.2 to 0.3.0
# CHANGELOG | ||
## 🚀 v0.2.3 | ||
## 🚀 v0.3.0 | ||
- ✨ Add support for `typeddefer` and `<TypedAwait>` [#20](https://github.com/kiliman/remix-typedjson/issues/20) | ||
## 🚀 v0.2.2 | ||
- 📦 Fix peer dependency to support Remix v2 | ||
@@ -6,0 +10,0 @@ |
@@ -1,4 +0,4 @@ | ||
export { deserializeRemix, redirect, stringifyRemix, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export type { RemixSerializedType, TypedFetcherWithComponents, TypedJsonResponse, TypedMetaFunction, UseDataFunctionReturn, } from './remix'; | ||
export { TypedAwait, deserializeRemix, redirect, stringifyRemix, typeddefer, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export type { RemixSerializedType, TypedAwaitProps, TypedFetcherWithComponents, TypedJsonResponse, TypedMetaFunction, UseDataFunctionReturn, } from './remix'; | ||
export { applyMeta, deserialize, parse, registerCustomType, serialize, stringify, } from './typedjson'; | ||
export type { MetaType, TypedJsonResult } from './typedjson'; |
@@ -1,2 +0,2 @@ | ||
export { deserializeRemix, redirect, stringifyRemix, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export { TypedAwait, deserializeRemix, redirect, stringifyRemix, typeddefer, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export { applyMeta, deserialize, parse, registerCustomType, serialize, stringify, } from './typedjson'; |
@@ -0,1 +1,2 @@ | ||
/// <reference types="react" /> | ||
import { HtmlMetaDescriptor, Params, type FetcherWithComponents } from '@remix-run/react'; | ||
@@ -6,2 +7,3 @@ import type { MetaType } from './typedjson'; | ||
typedjson(): Promise<T>; | ||
typeddefer(): Promise<T>; | ||
}; | ||
@@ -21,2 +23,9 @@ export interface AppLoadContext { | ||
export declare const typedjson: TypedJsonFunction; | ||
export declare const typeddefer: TypedJsonFunction; | ||
export type TypedAwaitProps<T> = { | ||
resolve: Promise<T>; | ||
errorElement?: React.ReactNode; | ||
children: (data: T) => React.ReactNode; | ||
}; | ||
export declare function TypedAwait<T>(props: TypedAwaitProps<T>): JSX.Element | null; | ||
export declare function useTypedLoaderData<T = AppData>(): UseDataFunctionReturn<T>; | ||
@@ -30,6 +39,6 @@ export declare function useTypedActionData<T = AppData>(): UseDataFunctionReturn<T> | null; | ||
export type RemixSerializedType<T> = { | ||
__obj__: T | null; | ||
__meta__?: MetaType | null; | ||
$$obj: T | null; | ||
$$meta?: MetaType | null; | ||
} & (T | { | ||
__meta__?: MetaType; | ||
$$meta?: MetaType; | ||
}); | ||
@@ -36,0 +45,0 @@ export declare function stringifyRemix<T>(data: T): string | null | undefined; |
@@ -1,2 +0,4 @@ | ||
import { useActionData, useFetcher, useLoaderData, useMatches, } from '@remix-run/react'; | ||
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'; | ||
@@ -14,2 +16,38 @@ export const typedjson = (data, init = {}) => { | ||
}; | ||
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() { | ||
@@ -42,6 +80,6 @@ const data = useLoaderData(); | ||
if (json.startsWith('{')) { | ||
json = `${json.substring(0, json.length - 1)},\"__meta__\":${JSON.stringify(meta)}}`; | ||
json = `${json.substring(0, json.length - 1)},\"$$meta\":${JSON.stringify(meta)}}`; | ||
} | ||
else if (json.startsWith('[')) { | ||
json = `{"__obj__":${json},"__meta__":${JSON.stringify(meta)}}`; | ||
json = `{"$$obj":${json},"$$meta":${JSON.stringify(meta)}}`; | ||
} | ||
@@ -54,13 +92,13 @@ } | ||
return data; | ||
if (data.__obj__) { | ||
if (data.$$obj) { | ||
// handle arrays wrapped in an object | ||
return data.__meta__ | ||
? _typedjson.applyMeta(data.__obj__, data.__meta__) | ||
: data.__obj__; | ||
return data.$$meta | ||
? _typedjson.applyMeta(data.$$obj, data.$$meta) | ||
: data.$$obj; | ||
} | ||
else if (data.__meta__) { | ||
// handle object with __meta__ key | ||
else if (data.$$meta) { | ||
// handle object with $$meta key | ||
// remove before applying meta | ||
const meta = data.__meta__; | ||
delete data.__meta__; | ||
const meta = data.$$meta; | ||
delete data.$$meta; | ||
return _typedjson.applyMeta(data, meta); | ||
@@ -67,0 +105,0 @@ } |
@@ -1,4 +0,4 @@ | ||
export { deserializeRemix, redirect, stringifyRemix, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export type { RemixSerializedType, TypedFetcherWithComponents, TypedJsonResponse, TypedMetaFunction, UseDataFunctionReturn, } from './remix'; | ||
export { TypedAwait, deserializeRemix, redirect, stringifyRemix, typeddefer, typedjson, useTypedActionData, useTypedFetcher, useTypedLoaderData, useTypedRouteLoaderData, } from './remix'; | ||
export type { RemixSerializedType, TypedAwaitProps, TypedFetcherWithComponents, TypedJsonResponse, TypedMetaFunction, UseDataFunctionReturn, } from './remix'; | ||
export { applyMeta, deserialize, parse, registerCustomType, serialize, stringify, } from './typedjson'; | ||
export type { MetaType, TypedJsonResult } from './typedjson'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.stringify = exports.serialize = exports.registerCustomType = exports.parse = exports.deserialize = exports.applyMeta = exports.useTypedRouteLoaderData = exports.useTypedLoaderData = exports.useTypedFetcher = exports.useTypedActionData = exports.typedjson = exports.stringifyRemix = exports.redirect = exports.deserializeRemix = void 0; | ||
exports.stringify = exports.serialize = exports.registerCustomType = exports.parse = exports.deserialize = exports.applyMeta = exports.useTypedRouteLoaderData = exports.useTypedLoaderData = exports.useTypedFetcher = exports.useTypedActionData = exports.typedjson = exports.typeddefer = exports.stringifyRemix = exports.redirect = exports.deserializeRemix = exports.TypedAwait = void 0; | ||
var remix_1 = require("./remix"); | ||
Object.defineProperty(exports, "TypedAwait", { enumerable: true, get: function () { return remix_1.TypedAwait; } }); | ||
Object.defineProperty(exports, "deserializeRemix", { enumerable: true, get: function () { return remix_1.deserializeRemix; } }); | ||
Object.defineProperty(exports, "redirect", { enumerable: true, get: function () { return remix_1.redirect; } }); | ||
Object.defineProperty(exports, "stringifyRemix", { enumerable: true, get: function () { return remix_1.stringifyRemix; } }); | ||
Object.defineProperty(exports, "typeddefer", { enumerable: true, get: function () { return remix_1.typeddefer; } }); | ||
Object.defineProperty(exports, "typedjson", { enumerable: true, get: function () { return remix_1.typedjson; } }); | ||
@@ -9,0 +11,0 @@ Object.defineProperty(exports, "useTypedActionData", { enumerable: true, get: function () { return remix_1.useTypedActionData; } }); |
@@ -0,1 +1,2 @@ | ||
/// <reference types="react" /> | ||
import { HtmlMetaDescriptor, Params, type FetcherWithComponents } from '@remix-run/react'; | ||
@@ -6,2 +7,3 @@ import type { MetaType } from './typedjson'; | ||
typedjson(): Promise<T>; | ||
typeddefer(): Promise<T>; | ||
}; | ||
@@ -21,2 +23,9 @@ export interface AppLoadContext { | ||
export declare const typedjson: TypedJsonFunction; | ||
export declare const typeddefer: TypedJsonFunction; | ||
export type TypedAwaitProps<T> = { | ||
resolve: Promise<T>; | ||
errorElement?: React.ReactNode; | ||
children: (data: T) => React.ReactNode; | ||
}; | ||
export declare function TypedAwait<T>(props: TypedAwaitProps<T>): JSX.Element | null; | ||
export declare function useTypedLoaderData<T = AppData>(): UseDataFunctionReturn<T>; | ||
@@ -30,6 +39,6 @@ export declare function useTypedActionData<T = AppData>(): UseDataFunctionReturn<T> | null; | ||
export type RemixSerializedType<T> = { | ||
__obj__: T | null; | ||
__meta__?: MetaType | null; | ||
$$obj: T | null; | ||
$$meta?: MetaType | null; | ||
} & (T | { | ||
__meta__?: MetaType; | ||
$$meta?: MetaType; | ||
}); | ||
@@ -36,0 +45,0 @@ export declare function stringifyRemix<T>(data: T): string | null | undefined; |
@@ -26,4 +26,6 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.redirect = exports.deserializeRemix = exports.stringifyRemix = exports.useTypedRouteLoaderData = exports.useTypedFetcher = exports.useTypedActionData = exports.useTypedLoaderData = exports.typedjson = void 0; | ||
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")); | ||
@@ -42,2 +44,40 @@ const typedjson = (data, init = {}) => { | ||
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() { | ||
@@ -74,6 +114,6 @@ const data = (0, react_1.useLoaderData)(); | ||
if (json.startsWith('{')) { | ||
json = `${json.substring(0, json.length - 1)},\"__meta__\":${JSON.stringify(meta)}}`; | ||
json = `${json.substring(0, json.length - 1)},\"$$meta\":${JSON.stringify(meta)}}`; | ||
} | ||
else if (json.startsWith('[')) { | ||
json = `{"__obj__":${json},"__meta__":${JSON.stringify(meta)}}`; | ||
json = `{"$$obj":${json},"$$meta":${JSON.stringify(meta)}}`; | ||
} | ||
@@ -87,13 +127,13 @@ } | ||
return data; | ||
if (data.__obj__) { | ||
if (data.$$obj) { | ||
// handle arrays wrapped in an object | ||
return data.__meta__ | ||
? _typedjson.applyMeta(data.__obj__, data.__meta__) | ||
: data.__obj__; | ||
return data.$$meta | ||
? _typedjson.applyMeta(data.$$obj, data.$$meta) | ||
: data.$$obj; | ||
} | ||
else if (data.__meta__) { | ||
// handle object with __meta__ key | ||
else if (data.$$meta) { | ||
// handle object with $$meta key | ||
// remove before applying meta | ||
const meta = data.__meta__; | ||
delete data.__meta__; | ||
const meta = data.$$meta; | ||
delete data.$$meta; | ||
return _typedjson.applyMeta(data, meta); | ||
@@ -100,0 +140,0 @@ } |
{ | ||
"name": "remix-typedjson", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "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.", | ||
@@ -5,0 +5,0 @@ "browser": "/dist/esm/index.js", |
@@ -54,4 +54,9 @@ # remix-typedjson | ||
❌ export const loader: LoaderFunction = async ({request}) => {} | ||
✅ export const loader = async ({request}: LoaderArgs) => {} | ||
✅ export async function loader({request}: LoaderArgs) {} | ||
❌ export const action: LoaderFunction = async ({request}) => {} | ||
✅ export const loader = async ({request}: DataFunctionArgs) => {} | ||
✅ export const action = async ({request}: DataFunctionArgs) => {} | ||
✅ export async function loader({request}: DataFunctionArgs) {} | ||
✅ export async function action({request}: DataFunctionArgs) {} | ||
``` | ||
@@ -91,2 +96,53 @@ | ||
## typeddefer | ||
✨ New in v0.3.0 | ||
Replacement for Remix `defer` helper. It also supports the optional `ResponseInit`, so you can return headers, etc. | ||
### Usage | ||
```js | ||
return typeddefer({ | ||
fastData: { message: 'This is fast data', today: new Date() }, | ||
slowData: new Promise(resolve => setTimeout(resolve, 2000)).then(() => { | ||
return { message: 'This is slow data', tomorrow: new Date() } | ||
}), | ||
}) | ||
``` | ||
In your route component, use the new `<TypedAwait>` component instead of the | ||
Remix `<Await>` component | ||
```js | ||
export default function DeferRoute() { | ||
const { fastData, slowData } = useTypedLoaderData<typeof loader>() | ||
return ( | ||
<main> | ||
<h1>Defer Route</h1> | ||
<h2>Fast Data</h2> | ||
<pre>{JSON.stringify(fastData, null, 2)}</pre> | ||
<div>fastData.today is {fastData.today.toLocaleString()}</div> | ||
<Suspense fallback={<p>Loading slow data...</p>}> | ||
<TypedAwait | ||
resolve={slowData} | ||
errorElement={<p>Error loading slow data!</p>} | ||
> | ||
{slowData => ( | ||
<div> | ||
<h2>Slow Data</h2> | ||
<pre>{JSON.stringify(slowData, null, 2)}</pre> | ||
<div> | ||
slowData.tomorrow is {slowData.tomorrow.toLocaleString()} | ||
</div> | ||
</div> | ||
)} | ||
</TypedAwait> | ||
</Suspense> | ||
</main> | ||
) | ||
} | ||
``` | ||
## `useTypedRouteLoaderData` | ||
@@ -177,5 +233,3 @@ | ||
export function registerCustomType<T>(entry: CustomTypeEntry<T>) { | ||
customTypeMap.set(entry.type, entry) | ||
} | ||
export function registerCustomType<T>(entry: CustomTypeEntry<T>) | ||
``` | ||
@@ -182,0 +236,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54578
958
321