remix-utils
Advanced tools
Comparing version
@@ -12,3 +12,3 @@ /** | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return json( | ||
@@ -22,3 +22,3 @@ * promiseHash({ | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return json( | ||
@@ -25,0 +25,0 @@ * promiseHash({ |
@@ -5,3 +5,3 @@ /** | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return json( | ||
@@ -15,3 +15,3 @@ * promiseHash({ | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return json( | ||
@@ -18,0 +18,0 @@ * promiseHash({ |
@@ -11,3 +11,3 @@ import type { Locales } from "../server/get-client-locales.js"; | ||
* // in the root loader | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let locales = getClientLocales(request); | ||
@@ -14,0 +14,0 @@ * return json({ locales }); |
@@ -11,3 +11,3 @@ import { useMatches } from "react-router"; | ||
* // in the root loader | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let locales = getClientLocales(request); | ||
@@ -14,0 +14,0 @@ * return json({ locales }); |
@@ -39,3 +39,3 @@ import type { Promisable } from "type-fest"; | ||
* // Create a response, then setup CORS for it | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -47,3 +47,3 @@ * let response = json<LoaderData>(data); | ||
* // Create response and setup CORS in a single line | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -62,3 +62,3 @@ * return await cors(request, json<LoaderData>(data)); | ||
* // Pass a configuration object to setup CORS | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -71,3 +71,3 @@ * return await cors(request, json<LoaderData>(data), { | ||
* // Mutate response and then return it | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -74,0 +74,0 @@ * let response = json<LoaderData>(data); |
@@ -144,3 +144,3 @@ const DEFAULT_OPTIONS = { | ||
* // Create a response, then setup CORS for it | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -152,3 +152,3 @@ * let response = json<LoaderData>(data); | ||
* // Create response and setup CORS in a single line | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -167,3 +167,3 @@ * return await cors(request, json<LoaderData>(data)); | ||
* // Pass a configuration object to setup CORS | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -176,3 +176,3 @@ * return await cors(request, json<LoaderData>(data), { | ||
* // Mutate response and then return it | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request); | ||
@@ -179,0 +179,0 @@ * let response = json<LoaderData>(data); |
@@ -61,3 +61,3 @@ import type { Cookie } from "react-router"; | ||
* @example | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* await csrf.validate(request); | ||
@@ -67,3 +67,3 @@ * // the request is authenticated and you can do anything here | ||
* @example | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* let formData = await request.formData() | ||
@@ -74,3 +74,3 @@ * await csrf.validate(formData, request.headers); | ||
* @example | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* let formData = await parseMultipartFormData(request); | ||
@@ -77,0 +77,0 @@ * await csrf.validate(formData, request.headers); |
@@ -9,3 +9,3 @@ export type Locales = string[] | undefined; | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let locales = getClientLocales(request) | ||
@@ -12,0 +12,0 @@ * let date = new Date().toLocaleDateString(locales, { |
@@ -6,3 +6,3 @@ /** | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let data = await getData(request) | ||
@@ -9,0 +9,0 @@ * let headers = new Headers() |
@@ -27,3 +27,3 @@ /** | ||
* | ||
* export async function loader({ context }: LoaderFunctionArgs) { | ||
* export async function loader({ context }: Route.LoaderArgs) { | ||
* let batcher = getBatcher(context); | ||
@@ -56,3 +56,3 @@ * let result = await batcher.batch("key", async () => { | ||
* | ||
* export async function loader({ context }: LoaderFunctionArgs) { | ||
* export async function loader({ context }: Route.LoaderArgs) { | ||
* let batcher = getBatcher(context); | ||
@@ -59,0 +59,0 @@ * let result = await getDataBatched(batcher); |
@@ -27,3 +27,3 @@ /** | ||
* | ||
* export async function loader({ context }: LoaderFunctionArgs) { | ||
* export async function loader({ context }: Route.LoaderArgs) { | ||
* let batcher = getBatcher(context); | ||
@@ -56,3 +56,3 @@ * let result = await batcher.batch("key", async () => { | ||
* | ||
* export async function loader({ context }: LoaderFunctionArgs) { | ||
* export async function loader({ context }: Route.LoaderArgs) { | ||
* let batcher = getBatcher(context); | ||
@@ -59,0 +59,0 @@ * let result = await getDataBatched(batcher); |
@@ -55,3 +55,3 @@ /** | ||
* | ||
* export async function loader(_: LoaderFunctionArgs) { | ||
* export async function loader(_: Route.LoaderArgs) { | ||
* let batcher = getBatcher(); | ||
@@ -58,0 +58,0 @@ * let result = await batcher.batch("key", async () => { |
@@ -42,3 +42,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let honeypotInputProps = await getHoneypotInputProps(); | ||
@@ -85,3 +85,3 @@ * return json({ honeypotInputProps }); | ||
* ```ts | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* // If this code runs, the honeypot check passed | ||
@@ -88,0 +88,0 @@ * let formData = await request.formData(); |
@@ -96,3 +96,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let payload = getJWTPayload(); | ||
@@ -99,0 +99,0 @@ * // ... |
@@ -96,3 +96,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let payload = getJWTPayload(); | ||
@@ -99,0 +99,0 @@ * // ... |
@@ -24,3 +24,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let requestID = getRequestID(); | ||
@@ -27,0 +27,0 @@ * // ... |
@@ -25,3 +25,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let collector = getTimingCollector(); | ||
@@ -28,0 +28,0 @@ * return await collector.measure("name", "optional description", async () => { |
@@ -25,3 +25,3 @@ /** | ||
* | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* let collector = getTimingCollector(); | ||
@@ -28,0 +28,0 @@ * return await collector.measure("name", "optional description", async () => { |
@@ -85,6 +85,32 @@ /** | ||
* ``` | ||
* | ||
* Optionally, you can also pass a getter function to | ||
* `unstable_createSessionMiddleware` to dynamically create the session storage | ||
* based on the request and context. | ||
* | ||
* ```ts | ||
* import { unstable_createSessionMiddleware } from "remix-utils/middleware/session"; | ||
* import { createCookieSessionStorage } from "react-router"; | ||
* | ||
* export const [sessionMiddleware, getSession] = | ||
* unstable_createSessionMiddleware((request, context) => { | ||
* let url = new URL(request.url); | ||
* let domain = url.hostname === "localhost" ? undefined : url.hostname; | ||
* return createCookieSessionStorage({ | ||
* cookie: createCookie("session", { domain }) | ||
* }); | ||
* }); | ||
* ``` | ||
* | ||
* This allows you to create a session storage that is specific to the request | ||
* and context, for example, to set a different cookie domain based on the | ||
* request hostname. | ||
* | ||
* This is useful if you have a multi-tenant application where each tenant | ||
* has its own domain and you want to use a different session cookie for each | ||
* tenant. | ||
* @author [Sergio Xalambrí](https://sergiodxa.com) | ||
* @module Middleware/Session | ||
*/ | ||
import type { Session, SessionData, SessionStorage, unstable_MiddlewareFunction } from "react-router"; | ||
import type { Session, SessionData, SessionStorage, unstable_MiddlewareFunction, unstable_RouterContextProvider } from "react-router"; | ||
import type { unstable_MiddlewareGetter } from "./utils.js"; | ||
@@ -112,3 +138,3 @@ /** | ||
* @example | ||
* // app/middlewares/session.ts | ||
* // app/middleware/session.ts | ||
* import { sessionStorage } from "~/session"; | ||
@@ -121,7 +147,7 @@ * import { unstable_createSessionMiddleware } from "remix-utils"; | ||
* // app/root.tsx | ||
* import { sessionMiddleware } from "~/middlewares/session"; | ||
* import { sessionMiddleware } from "~/middleware/session"; | ||
* export const unstable_middleware = [sessionMiddleware]; | ||
* | ||
* // app/routes/_index.tsx | ||
* import { getSession } from "~/middlewares/session"; | ||
* import { getSession } from "~/middleware/session"; | ||
* | ||
@@ -133,5 +159,34 @@ * export async function loader({ context }: Route.LoaderArgs) { | ||
* } | ||
* | ||
* @example | ||
* // app/middleware/session.ts | ||
* import { createCookieSessionStorage } from "react-router"; | ||
* import { unstable_createSessionMiddleware } from "remix-utils/middleware/session"; | ||
* | ||
* export const [sessionMiddleware, getSession] = | ||
* unstable_createSessionMiddleware((request, context) => { | ||
* let url = new URL(request.url); | ||
* let domain = url.hostname === "localhost" ? undefined : url.hostname; | ||
* return createCookieSessionStorage({ | ||
* cookie: createCookie("session", { domain }) | ||
* }) | ||
* }); | ||
*/ | ||
export declare function unstable_createSessionMiddleware<Data = SessionData, FlashData = Data>(sessionStorage: SessionStorage<Data, FlashData>, shouldCommit?: unstable_createSessionMiddleware.ShouldCommitFunction<Data>): unstable_createSessionMiddleware.ReturnType<Data, FlashData>; | ||
export declare function unstable_createSessionMiddleware<Data = SessionData, FlashData = Data>(sessionStorage: SessionStorage<Data, FlashData> | unstable_createSessionMiddleware.SessionStorageGetter<Data, FlashData>, shouldCommit?: unstable_createSessionMiddleware.ShouldCommitFunction<Data>): unstable_createSessionMiddleware.ReturnType<Data, FlashData>; | ||
export declare namespace unstable_createSessionMiddleware { | ||
/** | ||
* A function that returns a session storage object for the given request and | ||
* context. | ||
* @param request The request object | ||
* @param context The router context | ||
* @returns A session storage object | ||
*/ | ||
type SessionStorageGetter<Data, FlashData> = (request: Request, context: unstable_RouterContextProvider) => SessionStorage<Data, FlashData>; | ||
/** | ||
* A function that compares the previous and next session data to determine | ||
* if it changed and needs to be committed. | ||
* @param prev The previous session data | ||
* @param next The next session data | ||
* @returns A boolean indicating if the session should be committed | ||
*/ | ||
type ShouldCommitFunction<Data> = (prev: Partial<Data>, next: Partial<Data>) => boolean; | ||
@@ -138,0 +193,0 @@ type ReturnType<Data, FlashData> = [ |
@@ -23,3 +23,3 @@ import { unstable_createContext } from "react-router"; | ||
* @example | ||
* // app/middlewares/session.ts | ||
* // app/middleware/session.ts | ||
* import { sessionStorage } from "~/session"; | ||
@@ -32,7 +32,7 @@ * import { unstable_createSessionMiddleware } from "remix-utils"; | ||
* // app/root.tsx | ||
* import { sessionMiddleware } from "~/middlewares/session"; | ||
* import { sessionMiddleware } from "~/middleware/session"; | ||
* export const unstable_middleware = [sessionMiddleware]; | ||
* | ||
* // app/routes/_index.tsx | ||
* import { getSession } from "~/middlewares/session"; | ||
* import { getSession } from "~/middleware/session"; | ||
* | ||
@@ -44,2 +44,16 @@ * export async function loader({ context }: Route.LoaderArgs) { | ||
* } | ||
* | ||
* @example | ||
* // app/middleware/session.ts | ||
* import { createCookieSessionStorage } from "react-router"; | ||
* import { unstable_createSessionMiddleware } from "remix-utils/middleware/session"; | ||
* | ||
* export const [sessionMiddleware, getSession] = | ||
* unstable_createSessionMiddleware((request, context) => { | ||
* let url = new URL(request.url); | ||
* let domain = url.hostname === "localhost" ? undefined : url.hostname; | ||
* return createCookieSessionStorage({ | ||
* cookie: createCookie("session", { domain }) | ||
* }) | ||
* }); | ||
*/ | ||
@@ -50,3 +64,6 @@ export function unstable_createSessionMiddleware(sessionStorage, shouldCommit = defaultShouldCommit) { | ||
async function middleware({ request, context }, next) { | ||
let session = await sessionStorage.getSession(request.headers.get("Cookie")); | ||
let storage = typeof sessionStorage === "function" | ||
? sessionStorage(request, context) | ||
: sessionStorage; | ||
let session = await storage.getSession(request.headers.get("Cookie")); | ||
let initialData = structuredClone(session.data); | ||
@@ -56,3 +73,3 @@ context.set(sessionContext, session); | ||
if (shouldCommit(initialData, structuredClone(session.data))) { | ||
response.headers.append("Set-Cookie", await sessionStorage.commitSession(session)); | ||
response.headers.append("Set-Cookie", await storage.commitSession(session)); | ||
} | ||
@@ -59,0 +76,0 @@ return response; |
@@ -27,3 +27,3 @@ /** | ||
* | ||
* export async function loader({ context }: LoaderFunctionArgs) { | ||
* export async function loader({ context }: Route.LoaderArgs) { | ||
* let singleton = getSingleton(context); | ||
@@ -30,0 +30,0 @@ * let result = await singleton.method(); |
@@ -7,3 +7,3 @@ import { type Params, type unstable_MiddlewareFunction, unstable_RouterContextProvider } from "react-router"; | ||
next?: () => T | Promise<T>; | ||
}): Promise<T>; | ||
}): Promise<void | T>; | ||
export declare function catchResponse<T>(promise: Promise<T>): Promise<Response>; |
@@ -8,3 +8,3 @@ import { type UNSAFE_DataWithResponseInit } from "react-router"; | ||
* @example | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* await doSomething(request); | ||
@@ -11,0 +11,0 @@ * // If the user was on `/search?query=something` we redirect to that URL |
@@ -8,3 +8,3 @@ import { data } from "react-router"; | ||
* @example | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* await doSomething(request); | ||
@@ -11,0 +11,0 @@ * // If the user was on `/search?query=something` we redirect to that URL |
@@ -17,3 +17,3 @@ export interface RespondToHandlers { | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* // do any work independent of the response type before respondTo | ||
@@ -20,0 +20,0 @@ * let data = await getData(request); |
@@ -5,3 +5,3 @@ /** | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return notModified(); | ||
@@ -18,3 +18,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return javascript("console.log('Hello World')"); | ||
@@ -31,3 +31,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return css("body { color: red; }"); | ||
@@ -44,3 +44,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return pdf(await generatePDF(request.formData())); | ||
@@ -57,3 +57,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return html("<h1>Hello World</h1>"); | ||
@@ -98,3 +98,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return image(await takeScreenshot(), { type: "image/avif" }); | ||
@@ -101,0 +101,0 @@ * } |
@@ -5,3 +5,3 @@ /** | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return notModified(); | ||
@@ -20,3 +20,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return javascript("console.log('Hello World')"); | ||
@@ -43,3 +43,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return css("body { color: red; }"); | ||
@@ -66,3 +66,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return pdf(await generatePDF(request.formData())); | ||
@@ -89,3 +89,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return html("<h1>Hello World</h1>"); | ||
@@ -159,3 +159,3 @@ * } | ||
* @example | ||
* export async function loader({ request }: LoaderFunctionArgs) { | ||
* export async function loader({ request }: Route.LoaderArgs) { | ||
* return image(await takeScreenshot(), { type: "image/avif" }); | ||
@@ -162,0 +162,0 @@ * } |
@@ -0,3 +1,3 @@ | ||
import type { StandardSchemaV1 } from "@standard-schema/spec"; | ||
import type { Cookie } from "react-router"; | ||
import { z } from "zod"; | ||
import type { TypedCookie } from "./typed-cookie.js"; | ||
@@ -72,2 +72,2 @@ /** | ||
*/ | ||
export declare function rollingCookie<Schema extends z.ZodTypeAny>(cookie: Cookie | TypedCookie<Schema>, request: Request, responseHeaders: Headers): Promise<void>; | ||
export declare function rollingCookie<Schema extends StandardSchemaV1>(cookie: Cookie | TypedCookie<Schema>, request: Request, responseHeaders: Headers): Promise<void>; |
@@ -1,2 +0,1 @@ | ||
import { z } from "zod"; | ||
/** | ||
@@ -71,6 +70,6 @@ * Rolling cookies allows you to prolong the expiration of a cookie by updating | ||
export async function rollingCookie(cookie, request, responseHeaders) { | ||
const requestCookie = await cookie.parse(request.headers.get("Cookie")); | ||
let requestCookie = await cookie.parse(request.headers.get("Cookie")); | ||
if (!requestCookie) | ||
return; | ||
const responseCookie = await cookie.parse(responseHeaders.get("Set-Cookie")); | ||
let responseCookie = await cookie.parse(responseHeaders.get("Set-Cookie")); | ||
if (responseCookie !== null) | ||
@@ -77,0 +76,0 @@ return; |
@@ -14,3 +14,3 @@ import { z } from "zod"; | ||
* // json, otherwise return a redirect | ||
* export async function action({ request }: ActionFunctionArgs) { | ||
* export async function action({ request }: Route.ActionArgs) { | ||
* let dest = fetchDest(request); | ||
@@ -17,0 +17,0 @@ * if (dest === "empty") return json(data) |
@@ -0,9 +1,9 @@ | ||
import type { StandardSchemaV1 } from "@standard-schema/spec"; | ||
import { type Cookie, type CookieParseOptions, type CookieSerializeOptions } from "react-router"; | ||
import type { z } from "zod"; | ||
export interface TypedCookie<Schema extends z.ZodTypeAny> extends Cookie { | ||
export interface TypedCookie<Schema extends StandardSchemaV1> extends Cookie { | ||
isTyped: true; | ||
parse(cookieHeader: string | null, options?: CookieParseOptions): Promise<z.infer<Schema> | null>; | ||
serialize(value: z.input<Schema>, options?: CookieSerializeOptions): Promise<string>; | ||
parse(cookieHeader: string | null, options?: CookieParseOptions): Promise<StandardSchemaV1.InferOutput<Schema> | null>; | ||
serialize(value: StandardSchemaV1.InferInput<Schema>, options?: CookieSerializeOptions): Promise<string>; | ||
} | ||
export declare function createTypedCookie<Schema extends z.ZodTypeAny>({ cookie, schema, }: { | ||
export declare function createTypedCookie<Schema extends StandardSchemaV1>({ cookie, schema, }: { | ||
cookie: Cookie; | ||
@@ -17,2 +17,7 @@ schema: Schema; | ||
*/ | ||
export declare function isTypedCookie<Schema extends z.ZodTypeAny>(value: unknown): value is TypedCookie<Schema>; | ||
export declare function isTypedCookie<Schema extends StandardSchemaV1>(value: unknown): value is TypedCookie<Schema>; | ||
export declare class ValidationError extends Error { | ||
readonly issues: Readonly<StandardSchemaV1.Issue[]>; | ||
name: string; | ||
constructor(issues: Readonly<StandardSchemaV1.Issue[]>); | ||
} |
import { isCookie, } from "react-router"; | ||
export function createTypedCookie({ cookie, schema, }) { | ||
if (schema._def.typeName === "ZodObject") { | ||
let flashSchema = {}; | ||
for (let key in schema.shape) { | ||
flashSchema[flash(key)] = schema.shape[key].optional(); | ||
} | ||
} | ||
return { | ||
@@ -24,7 +18,14 @@ isTyped: true, | ||
let value = await cookie.parse(cookieHeader, options); | ||
return await parseSchemaWithFlashKeys(schema, value); | ||
if (value === null) | ||
return null; | ||
let result = await schema["~standard"].validate(value); | ||
if (result.issues) | ||
throw new ValidationError(result.issues); | ||
return result.value; | ||
}, | ||
async serialize(value, options) { | ||
let parsedValue = await parseSchemaWithFlashKeys(schema, value); | ||
return cookie.serialize(parsedValue, options); | ||
let result = await schema["~standard"].validate(value); | ||
if (result.issues) | ||
throw new ValidationError(result.issues); | ||
return cookie.serialize(result.value, options); | ||
}, | ||
@@ -45,17 +46,20 @@ }; | ||
} | ||
function parseSchemaWithFlashKeys(schema, value) { | ||
// if the Schema is not a ZodObject, we use it directly | ||
if (schema._def.typeName !== "ZodObject") { | ||
return schema.nullable().parseAsync(value); | ||
async function parseSchemaWithFlashKeys(schema, value) { | ||
let result = await schema["~standard"].validate(value); | ||
if (result.issues) | ||
throw new ValidationError(result.issues); | ||
return result.value; | ||
} | ||
export class ValidationError extends Error { | ||
issues; | ||
name = "ValidationError"; | ||
constructor(issues) { | ||
super("Validation error"); | ||
this.issues = issues; | ||
this.name = "ValidationError"; | ||
} | ||
// but if it's a ZodObject, we need to add support for flash keys, so we | ||
// get the shape of the schema, create a flash key for each key, and then we | ||
// extend the original schema with the flash schema and parse the value | ||
let objectSchema = schema; | ||
let flashSchema = {}; | ||
for (let key in objectSchema.shape) { | ||
flashSchema[flash(key)] = objectSchema.shape[key].optional(); | ||
} | ||
return objectSchema.extend(flashSchema).parseAsync(value); | ||
} | ||
function isObject(value) { | ||
return typeof value === "object" && value !== null && !Array.isArray(value); | ||
} | ||
//# sourceMappingURL=typed-cookie.js.map |
@@ -50,2 +50,5 @@ import { type CookieParseOptions, type CookieSerializeOptions, type SessionStorage } from "react-router"; | ||
} | ||
/** | ||
* @deprecated The createSessionStorage utils from React Router already supports typed sessions using a generic | ||
*/ | ||
export declare function createTypedSessionStorage<Schema extends z.AnyZodObject>({ sessionStorage, schema, }: { | ||
@@ -52,0 +55,0 @@ sessionStorage: SessionStorage; |
import { isSession, } from "react-router"; | ||
import { z } from "zod"; | ||
/** | ||
* @deprecated The createSessionStorage utils from React Router already supports typed sessions using a generic | ||
*/ | ||
export function createTypedSessionStorage({ sessionStorage, schema, }) { | ||
@@ -4,0 +7,0 @@ return { |
{ | ||
"name": "remix-utils", | ||
"version": "8.7.0", | ||
"version": "8.8.0", | ||
"license": "MIT", | ||
@@ -261,2 +261,3 @@ "engines": { | ||
"@oslojs/encoding": "^1.1.0", | ||
"@standard-schema/spec": "^1.0.0", | ||
"intl-parse-accept-language": "^1.0.0", | ||
@@ -284,2 +285,5 @@ "is-ip": "^5.0.1", | ||
}, | ||
"@standard-schema/spec": { | ||
"optional": true | ||
}, | ||
"react-router": { | ||
@@ -302,3 +306,3 @@ "optional": true | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "^0.17.4", | ||
"@arethetypeswrong/cli": "^0.18.1", | ||
"@biomejs/biome": "^1.7.2", | ||
@@ -308,25 +312,27 @@ "@edgefirst-dev/batcher": "^1.0.1", | ||
"@edgefirst-dev/server-timing": "^0.0.1", | ||
"@happy-dom/global-registrator": "^17.4.3", | ||
"@happy-dom/global-registrator": "^17.4.6", | ||
"@mjackson/file-storage": "^0.6.1", | ||
"@oslojs/crypto": "^1.0.1", | ||
"@oslojs/encoding": "^1.1.0", | ||
"@standard-schema/spec": "^1.0.0", | ||
"@testing-library/jest-dom": "^6.1.3", | ||
"@testing-library/react": "^16.2.0", | ||
"@testing-library/react": "^16.3.0", | ||
"@testing-library/user-event": "^14.6.1", | ||
"@total-typescript/tsconfig": "^1.0.4", | ||
"@types/bun": "^1.2.5", | ||
"@types/react": "^19.0.10", | ||
"@types/bun": "^1.2.12", | ||
"@types/react": "^19.1.3", | ||
"intl-parse-accept-language": "^1.0.0", | ||
"is-ip": "5.0.1", | ||
"msw": "^2.7.3", | ||
"react": "^19.0.0", | ||
"react-router": "^7.3.0", | ||
"msw": "^2.8.2", | ||
"react": "^19.1.0", | ||
"react-dom": "^19.1.0", | ||
"react-router": "^7.6.0", | ||
"ts-node": "^10.9.1", | ||
"typedoc": "^0.27.9", | ||
"typedoc": "^0.28.4", | ||
"typedoc-plugin-mdn-links": "^5.0.1", | ||
"typescript": "^5.8.2", | ||
"zod": "^3.24.2" | ||
"typescript": "^5.8.3", | ||
"zod": "^3.24.4" | ||
}, | ||
"dependencies": { | ||
"type-fest": "^4.37.0" | ||
"type-fest": "^4.41.0" | ||
}, | ||
@@ -333,0 +339,0 @@ "files": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
421692
0.91%6174
1.41%12
9.09%27
8%+ Added
Updated