Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

zod-form-data

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

zod-form-data - npm Package Compare versions

Comparing version 0.0.1-beta.0 to 1.0.0

.turbo/turbo-dev.log

24

browser/helpers.d.ts

@@ -8,11 +8,21 @@ import { z, ZodArray, ZodEffects, ZodNumber, ZodString, ZodTypeAny } from "zod";

export declare const numeric: InputType<ZodNumber>;
declare type BooleanCheckboxOpts = {
declare type CheckboxOpts = {
trueValue?: string;
};
export declare const booleanCheckbox: ({ trueValue, }?: BooleanCheckboxOpts) => z.ZodUnion<[z.ZodEffects<z.ZodLiteral<string>, boolean, string>, z.ZodEffects<z.ZodLiteral<undefined>, boolean, undefined>]>;
declare type RepeatableField = {
(): ZodEffects<ZodArray<ZodString>>;
<ProvidedType extends ZodTypeAny>(schema: ProvidedType): ZodEffects<ZodArray<ProvidedType>>;
};
export declare const repeatableField: RepeatableField;
export declare const checkbox: ({ trueValue }?: CheckboxOpts) => z.ZodUnion<[z.ZodEffects<z.ZodLiteral<string>, boolean, string>, z.ZodEffects<z.ZodLiteral<undefined>, boolean, undefined>]>;
export declare const repeatable: InputType<ZodArray<any>>;
export declare const repeatableOfType: <T extends z.ZodTypeAny>(schema: T) => z.ZodEffects<z.ZodArray<T, "many">, T["_output"][], T["_input"][]>;
export declare const formData: (shape: z.ZodRawShape) => z.ZodEffects<z.ZodObject<z.ZodRawShape, "strip", z.ZodTypeAny, {
[x: string]: any;
[x: number]: any;
}, {
[x: string]: any;
[x: number]: any;
}>, {
[x: string]: any;
[x: number]: any;
}, {
[x: string]: any;
[x: number]: any;
}>;
export {};

@@ -17,10 +17,39 @@ import { z } from "zod";

])), schema);
export const booleanCheckbox = ({ trueValue = "on", } = {}) => z.union([
export const checkbox = ({ trueValue = "on" } = {}) => z.union([
z.literal(trueValue).transform(() => true),
z.literal(undefined).transform(() => false),
]);
export const repeatableField = (schema = z.string()) => z.preprocess((val) => {
if (Array.isArray(val))
return val;
return [val];
}, z.array(schema));
export const repeatable = (schema = z.array(text())) => {
return z.preprocess((val) => {
if (Array.isArray(val))
return val;
if (val === undefined)
return [];
return [val];
}, schema);
};
export const repeatableOfType = (schema) => repeatable(z.array(schema));
const entries = z.array(z.tuple([z.string(), z.any()]));
export const formData = (shape) => z.preprocess(preprocessIfValid(
// We're avoiding using `instanceof` here because different environments
// won't necessarily have `FormData` or `URLSearchParams`
z
.any()
.refine((val) => Symbol.iterator in val)
.transform((val) => [...val])
.refine((val) => entries.safeParse(val).success)
.transform((data) => {
const map = new Map();
for (const [key, value] of data) {
if (map.has(key)) {
map.get(key).push(value);
}
else {
map.set(key, [value]);
}
}
return [...map.entries()].reduce((acc, [key, value]) => {
acc[key] = value.length === 1 ? value[0] : value;
return acc;
}, {});
})), z.object(shape));

@@ -8,11 +8,21 @@ import { z, ZodArray, ZodEffects, ZodNumber, ZodString, ZodTypeAny } from "zod";

export declare const numeric: InputType<ZodNumber>;
declare type BooleanCheckboxOpts = {
declare type CheckboxOpts = {
trueValue?: string;
};
export declare const booleanCheckbox: ({ trueValue, }?: BooleanCheckboxOpts) => z.ZodUnion<[z.ZodEffects<z.ZodLiteral<string>, boolean, string>, z.ZodEffects<z.ZodLiteral<undefined>, boolean, undefined>]>;
declare type RepeatableField = {
(): ZodEffects<ZodArray<ZodString>>;
<ProvidedType extends ZodTypeAny>(schema: ProvidedType): ZodEffects<ZodArray<ProvidedType>>;
};
export declare const repeatableField: RepeatableField;
export declare const checkbox: ({ trueValue }?: CheckboxOpts) => z.ZodUnion<[z.ZodEffects<z.ZodLiteral<string>, boolean, string>, z.ZodEffects<z.ZodLiteral<undefined>, boolean, undefined>]>;
export declare const repeatable: InputType<ZodArray<any>>;
export declare const repeatableOfType: <T extends z.ZodTypeAny>(schema: T) => z.ZodEffects<z.ZodArray<T, "many">, T["_output"][], T["_input"][]>;
export declare const formData: (shape: z.ZodRawShape) => z.ZodEffects<z.ZodObject<z.ZodRawShape, "strip", z.ZodTypeAny, {
[x: string]: any;
[x: number]: any;
}, {
[x: string]: any;
[x: number]: any;
}>, {
[x: string]: any;
[x: number]: any;
}, {
[x: string]: any;
[x: number]: any;
}>;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.repeatableField = exports.booleanCheckbox = exports.numeric = exports.text = void 0;
exports.formData = exports.repeatableOfType = exports.repeatable = exports.checkbox = exports.numeric = exports.text = void 0;
const zod_1 = require("zod");

@@ -22,12 +22,43 @@ const stripEmpty = zod_1.z.literal("").transform(() => undefined);

exports.numeric = numeric;
const booleanCheckbox = ({ trueValue = "on", } = {}) => zod_1.z.union([
const checkbox = ({ trueValue = "on" } = {}) => zod_1.z.union([
zod_1.z.literal(trueValue).transform(() => true),
zod_1.z.literal(undefined).transform(() => false),
]);
exports.booleanCheckbox = booleanCheckbox;
const repeatableField = (schema = zod_1.z.string()) => zod_1.z.preprocess((val) => {
if (Array.isArray(val))
return val;
return [val];
}, zod_1.z.array(schema));
exports.repeatableField = repeatableField;
exports.checkbox = checkbox;
const repeatable = (schema = zod_1.z.array((0, exports.text)())) => {
return zod_1.z.preprocess((val) => {
if (Array.isArray(val))
return val;
if (val === undefined)
return [];
return [val];
}, schema);
};
exports.repeatable = repeatable;
const repeatableOfType = (schema) => (0, exports.repeatable)(zod_1.z.array(schema));
exports.repeatableOfType = repeatableOfType;
const entries = zod_1.z.array(zod_1.z.tuple([zod_1.z.string(), zod_1.z.any()]));
const formData = (shape) => zod_1.z.preprocess(preprocessIfValid(
// We're avoiding using `instanceof` here because different environments
// won't necessarily have `FormData` or `URLSearchParams`
zod_1.z
.any()
.refine((val) => Symbol.iterator in val)
.transform((val) => [...val])
.refine((val) => entries.safeParse(val).success)
.transform((data) => {
const map = new Map();
for (const [key, value] of data) {
if (map.has(key)) {
map.get(key).push(value);
}
else {
map.set(key, [value]);
}
}
return [...map.entries()].reduce((acc, [key, value]) => {
acc[key] = value.length === 1 ? value[0] : value;
return acc;
}, {});
})), zod_1.z.object(shape));
exports.formData = formData;
{
"name": "zod-form-data",
"version": "0.0.1-beta.0",
"version": "1.0.0",
"browser": "./browser/index.js",

@@ -5,0 +5,0 @@ "main": "./build/index.js",

# zod-form-data
Helpers for using [zod](https://github.com/colinhacks/zod) to parse `FormData` or `URLSearchParams`.
Validation helpers for [zod](https://github.com/colinhacks/zod)
specifically for parsing `FormData` or `URLSearchParams`.
This is particularly useful when using [remix](https://github.com/remix-run/remix)
and combos well with [remix-validated-form](https://github.com/airjp73/remix-validated-form).
The main goal of this library is deal with the pain point that everything in `FormData` is a string.
Sometimes, properly validating this kind of data requires a lot of extra hoop jumping and preprocessing.
With the helpers in `zod-form-data`, you can write your types closer to how you want to.
## Example
```tsx
import { zfd } from 'zod-form-data';
const schema = zfd.formData({
name: zfd.text(),
age: zfd.numeric(
z.number().min(25).max(50)
),
likesPizza: zfd.checkbox()
})
// This example is using `remix`, but it will work
// with any `FormData` or `URLSearchParams` no matter where you get it from.
export const action = async ({ request }) => {
const { name, age, likesPizza } = schema.parse(await request.formData())
// do something with parsed data
}
```
## Installation
```bash
npm install zod-form-data
```
## Contributing
The eventual goal is to have a helper to preprocess and/or validate most types of native inputs.
If you have a helper for an input type that isn't in this library, feel free to open a PR to add it!
## API Reference
Contents
* [formData](#formData)
* [text](#text)
* [numeric](#numeric)
* [checkbox](#checkbox)
* [repeatable](#repeatable)
* [repeatableOfType](#repeatableOfType)
### formData
This helper takes the place of the `z.object` at the root of your schema.
It wraps your schema in a `z.preprocess` that extracts all the data out of a `FormData`
and transforms it into a regular object.
If the `FormData` contains multiple entries with the same field name,
it will automatically turn that field into an array.
(If you're expecting multiple values for a field, use [repeatable](#repeatable).)
The primary use-case for this helper is to accept `FormData`,
but it works with any iterable that returns entries.
This means it can accept `URLSearchParams` or regular objects as well.
#### Usage
You can use this the same way you would use `z.object`.
```ts
const schema = zfd.formData({
field1: zfd.text(),
field2: zfd.text(),
})
const someFormData = new FormData();
const dataObject = schema.parse(someFormData);
```
### text
Transforms any empty strings to `undefined` before validating.
This makes it so empty strings will fail required checks,
allowing you to use `optional` for optional fields instead of `nonempty` for required fields.
If you call `zfd.text` with no arguments, it will assume the field is a required string by default.
If you want to customize the schema, you can pass that as an argument.
#### Usage
```ts
const const schema = zfd.formData({
requiredString: zfd.text(),
stringWithMinLength: zfd.text(z.string().min(10)),
optional: zfd.text(z.string().optional()),
})
```
### numeric
Coerces numerical strings to numbers transforms empty strings to `undefined` before validating.
If you call `zfd.number` with no arguments,
it will assume the field is a required number by default.
If you want to customize the schema, you can pass that as an argument.
_Note:_ The preprocessing only _coerces_ the value into a number. It doesn't use `parseInt`.
Something like `"24px"` will not be transformed and will be treated as a string.
#### Usage
```ts
const schema = zfd.formData({
requiredNumber: zfd.numeric(),
numberWithMin: zfd.numeric(z.number().min(13)),
optional: zfd.numeric(z.number().optional()),
})
```
### checkbox
Validates a checkbox field as a boolean.
Unlike other helpers, this is not a preprocesser,
but a complete schema that should do everything you need.
By default, it will treat `"on"` as true and `undefined` as false,
but you can customize the true value.
If you have a checkbox group and you want to leave the values as strings,
[repeatableField](#repeatableField) might be what you want.
#### Usage
```ts
const schema = zfd.formData({
defaultCheckbox: zfd.checkbox(),
checkboxWithValue: zfd.checkbox({ trueValue: "true" }),
mustBeTrue: zfd.checkbox().refine(val => val, "Please check this box")
})
```
#### Background on native checkbox behavior
If you're used to using client-side form libraries and haven't dealt with native form behavior much,
the native checkbox behavior might be non-intuitive (it was for me).
Take this checkbox:
```tsx
<input name="myCheckbox" type="checkbox" />
```
If you check this checkbox and submit the form, the value in the `FormData` will be `"on"`.
If you add a value prop:
```tsx
<input name="myCheckbox" type="checkbox" value="someValue" />
```
Then the checked value of the checkbox will be `"someValue"` instead of `"on"`.
If you leave the checkbox unchecked,
the `FormData` will not include an entry for `myCheckbox` at all.
([Further reading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#value))
### repeatable
Preprocesses a field where you expect multiple values could be present for the same field name
and transforms the value of that field to always be an array.
This is specifically meant to work with data transformed by `zfd.formData`
(or by `remix-validated-form`).
If you don't provide a schema, it will assume the field is an array of [zfd.text](#text) fields.
If you want to customize the type of the item, but don't care about validations on the array itself,
you can use [repeatableOfType](#repeatableOfType).
#### Usage
```ts
const schema = zfd.formData({
myCheckboxGroup: zfd.repeatable(),
atLeastOneItem: zfd.repeatable(z.array(zfd.text()).min(1)),
})
```
### repeatableOfType
A convenience wrapper for [repeatable](#repeatable).
Instead of passing the schema for an entire array, you pass in the schema for the item type.
#### Usage
```ts
const schema = zfd.formData({
repeatableNumberField: zfd.repeatableOfType(zfd.numeric())
})
```

@@ -35,9 +35,7 @@ import { z, ZodArray, ZodEffects, ZodNumber, ZodString, ZodTypeAny } from "zod";

type BooleanCheckboxOpts = {
type CheckboxOpts = {
trueValue?: string;
};
export const booleanCheckbox = ({
trueValue = "on",
}: BooleanCheckboxOpts = {}) =>
export const checkbox = ({ trueValue = "on" }: CheckboxOpts = {}) =>
z.union([

@@ -48,13 +46,48 @@ z.literal(trueValue).transform(() => true),

type RepeatableField = {
(): ZodEffects<ZodArray<ZodString>>;
<ProvidedType extends ZodTypeAny>(schema: ProvidedType): ZodEffects<
ZodArray<ProvidedType>
>;
export const repeatable: InputType<ZodArray<any>> = (
schema = z.array(text())
) => {
return z.preprocess((val) => {
if (Array.isArray(val)) return val;
if (val === undefined) return [];
return [val];
}, schema);
};
export const repeatableField: RepeatableField = (schema = z.string()) =>
z.preprocess((val) => {
if (Array.isArray(val)) return val;
return [val];
}, z.array(schema));
export const repeatableOfType = <T extends ZodTypeAny>(
schema: T
): ZodEffects<ZodArray<T>> => repeatable(z.array(schema));
const entries = z.array(z.tuple([z.string(), z.any()]));
export const formData = (shape: z.ZodRawShape) =>
z.preprocess(
preprocessIfValid(
// We're avoiding using `instanceof` here because different environments
// won't necessarily have `FormData` or `URLSearchParams`
z
.any()
.refine((val) => Symbol.iterator in val)
.transform((val) => [...val])
.refine(
(val): val is z.infer<typeof entries> =>
entries.safeParse(val).success
)
.transform((data): Record<string, unknown | unknown[]> => {
const map: Map<string, unknown[]> = new Map();
for (const [key, value] of data) {
if (map.has(key)) {
map.get(key)!.push(value);
} else {
map.set(key, [value]);
}
}
return [...map.entries()].reduce((acc, [key, value]) => {
acc[key] = value.length === 1 ? value[0] : value;
return acc;
}, {} as Record<string, unknown | unknown[]>);
})
),
z.object(shape)
);
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc