UNPKG

remix-params-helper

Version:

This package makes it simple to use Zod with standard URLSearchParams and FormData which are typically used in Remix apps.

219 lines (218 loc) 6.18 kB
import { ZodArray, ZodBoolean, ZodDate, ZodDefault, ZodEffects, ZodEnum, ZodLiteral, ZodNativeEnum, ZodNumber, ZodObject, ZodOptional, ZodString } from "zod"; function isIterable(maybeIterable) { return Symbol.iterator in Object(maybeIterable); } function parseParams(o, schema, key, value) { let shape = schema; while (shape instanceof ZodObject || shape instanceof ZodEffects) { shape = shape instanceof ZodObject ? shape.shape : shape instanceof ZodEffects ? shape._def.schema : null; if (shape === null) { throw new Error(`Could not find shape for key ${key}`); } } if (key.includes(".")) { let [parentProp, ...rest] = key.split("."); o[parentProp] = o[parentProp] ?? {}; parseParams(o[parentProp], shape[parentProp], rest.join("."), value); return; } let isArray = false; if (key.includes("[]")) { isArray = true; key = key.replace("[]", ""); } const def = shape[key]; if (def) { processDef(def, o, key, value); } } function getParamsInternal(params, schema) { let o = {}; let entries = []; if (isIterable(params)) { entries = Array.from(params); } else { entries = Object.entries(params); } for (let [key, value] of entries) { if (value === "") { continue; } parseParams(o, schema, key, value); } const result = schema.safeParse(o); if (result.success) { return { success: true, data: result.data, errors: void 0 }; } else { let errors = {}; const addError = (key, message) => { if (!errors.hasOwnProperty(key)) { errors[key] = message; } else { if (!Array.isArray(errors[key])) { errors[key] = [errors[key]]; } errors[key].push(message); } }; for (let issue of result.error.issues) { const { message, path, code, expected, received } = issue; const [key, index] = path; let value = o[key]; let prop = key; if (index !== void 0) { value = value[index]; prop = `${key}[${index}]`; } addError(key, message); } return { success: false, data: void 0, errors }; } } function getParams(params, schema) { return getParamsInternal(params, schema); } function getSearchParams(request, schema) { let url = new URL(request.url); return getParamsInternal(url.searchParams, schema); } async function getFormData(request, schema) { let data = await request.formData(); return getParamsInternal(data, schema); } function getParamsOrFail(params, schema) { const result = getParamsInternal(params, schema); if (!result.success) { throw new Error(JSON.stringify(result.errors)); } return result.data; } function getSearchParamsOrFail(request, schema) { let url = new URL(request.url); const result = getParamsInternal(url.searchParams, schema); if (!result.success) { throw new Error(JSON.stringify(result.errors)); } return result.data; } async function getFormDataOrFail(request, schema) { let data = await request.formData(); const result = getParamsInternal(data, schema); if (!result.success) { throw new Error(JSON.stringify(result.errors)); } return result.data; } function useFormInputProps(schema, options = {}) { const shape = schema.shape; const defaultOptions = options; return function props(key, options2 = {}) { options2 = { ...defaultOptions, ...options2 }; const def = shape[key]; if (!def) { throw new Error(`no such key: ${key}`); } return getInputProps(key, def); }; } function processDef(def, o, key, value) { let parsedValue; if (def instanceof ZodString || def instanceof ZodLiteral) { parsedValue = value; } else if (def instanceof ZodNumber) { const num = Number(value); parsedValue = isNaN(num) ? value : num; } else if (def instanceof ZodDate) { const date = Date.parse(value); parsedValue = isNaN(date) ? value : new Date(date); } else if (def instanceof ZodBoolean) { parsedValue = value === "true" ? true : value === "false" ? false : Boolean(value); } else if (def instanceof ZodNativeEnum || def instanceof ZodEnum) { parsedValue = value; } else if (def instanceof ZodOptional || def instanceof ZodDefault) { processDef(def._def.innerType, o, key, value); return; } else if (def instanceof ZodArray) { if (o[key] === void 0) { o[key] = []; } processDef(def.element, o, key, value); return; } else if (def instanceof ZodEffects) { processDef(def._def.schema, o, key, value); return; } else { throw new Error(`Unexpected type ${def._def.typeName} for key ${key}`); } if (Array.isArray(o[key])) { o[key].push(parsedValue); } else { o[key] = parsedValue; } } function getInputProps(name, def) { let type = "text"; let min, max, minlength, maxlength, pattern; if (def instanceof ZodString) { if (def.isEmail) { type = "email"; } else if (def.isURL) { type = "url"; } minlength = def.minLength ?? void 0; maxlength = def.maxLength ?? void 0; const check = def._def.checks.find((c) => c.kind === "regex"); pattern = check ? check.regex.source : void 0; } else if (def instanceof ZodNumber) { type = "number"; min = def.minValue ?? void 0; max = def.maxValue ?? void 0; } else if (def instanceof ZodBoolean) { type = "checkbox"; } else if (def instanceof ZodDate) { type = "date"; } else if (def instanceof ZodArray) { return getInputProps(name, def.element); } else if (def instanceof ZodOptional) { return getInputProps(name, def.unwrap()); } let inputProps = { name, type }; if (!def.isOptional()) inputProps.required = true; if (min) inputProps.min = min; if (max) inputProps.max = max; if (minlength && Number.isFinite(minlength)) inputProps.minLength = minlength; if (maxlength && Number.isFinite(maxlength)) inputProps.maxLength = maxlength; if (pattern) inputProps.pattern = pattern; return inputProps; } export { getFormData, getFormDataOrFail, getParams, getParamsOrFail, getSearchParams, getSearchParamsOrFail, useFormInputProps };