Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dry-cats-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"conformal": patch
---

Align serialize false boolean behavior with coerceBoolean
9 changes: 9 additions & 0 deletions .changeset/huge-planets-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"conformal": minor
---

Add coerce functions

- Add `coerceString`, `coerceNumber`, `coerceBigint`, `coerceBoolean`, `coerceDate`, `coerceFile`, `coerceArray` functions
- These utilities help convert form input values to their expected types
- Essential for building custom schema implementations (like zod preproccessors or valibot transforms)
17 changes: 17 additions & 0 deletions .changeset/many-beds-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"conformal": minor
---

Deprecate zod utilities

- Mark `conformal/zod` utilities as deprecated
- Zod's `z.preprocess` returns a `ZodPipe` which doesn't allow method chaining, making these utilities less useful than expected
- Zod utilities will be removed in the next major release
- Users can migrate to using z.preprocess with the new coerce functions directly:

```typescript
import * as z from "zod";
import { coerceNumber } from "conformal";

z.preprocess(coerceNumber, z.number().min(5));
```
11 changes: 11 additions & 0 deletions .changeset/rich-feet-throw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"conformal": minor
---

Add valibot schemas

- Add `conformal/valibot` subpath with valibot utilities
- Provides `string`, `number`, `boolean`, `date`, `bigint`, `picklist`, `file`, `array` schemas
- Uses conformal's coerce functions for automatic form input preprocessing
- Fully compatible with valibot and can be mixed with regular valibot schemas
- Marked as experimental - API may change
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ cd examples/svelte && npm i && npm run dev

## Public API

- `conformal`: `getPath`, `setPath`, `decode`, `parseFormData`, `serialize`; types: `PathsFromObject`, `Submission`
- `conformal/zod`: `string`, `number`, `boolean`, `date`, `bigint`, `enum`, `file`, `url`, `email`, `object`, `array`
- `conformal`: `getPath`, `setPath`, `decode`, `parseFormData`, `serialize`, `coerceString`, `coerceNumber`, `coerceBigint`, `coerceBoolean`, `coerceDate`, `coerceFile`, `coerceArray`; types: `PathsFromObject`, `Submission`
- `conformal/valibot`: `string`, `number`, `boolean`, `date`, `bigint`, `picklist`, `file`, `array` (experimental)
- `conformal/zod`: `string`, `number`, `boolean`, `date`, `bigint`, `enum`, `file`, `url`, `email`, `object`, `array` (deprecated)

Exports live in `src/index.ts` and `src/zod/index.ts`.
Exports live in `src/index.ts`, `src/valibot/index.ts`, and `src/zod/index.ts`.

## Non‑negotiable invariants

Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Here's a quick example showing how Conformal handles form validation with a user

```typescript
import { parseFormData } from "conformal";
import * as z from "zod"; // Tip: Use conformal/zod for automatic form input preprocessing
import * as z from "zod"; // Tip: Use conformal's coerce functions for form input preprocessing

const schema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
Expand Down Expand Up @@ -70,16 +70,26 @@ That's it! Conformal automatically handles FormData parsing, type coercion, and
- **[`getPath`](src/README.md#getpath)** - Safely access nested values using dot/bracket notation
- **[`setPath`](src/README.md#setpath)** - Immutably set nested values using dot/bracket notation

### Coerce Functions

- **[`coerceString`](src/README.md#coercestring)** - String handling
- **[`coerceFile`](src/README.md#coercefile)** - File handling
- **[`coerceNumber`](src/README.md#coercenumber)** - String to number coercion
- **[`coerceBigint`](src/README.md#coercebigint)** - String to BigInt coercion
- **[`coerceBoolean`](src/README.md#coerceboolean)** - String to boolean coercion
- **[`coerceDate`](src/README.md#coercedate)** - String to Date coercion
- **[`coerceArray`](src/README.md#coercearray)** - Coerce to array

### Types

- **[`Submission`](src/README.md#submission)** - Standardized submission result with success/error states
- **[`PathsFromObject`](src/README.md#pathsfromobject)** - Type utility to extract all possible object paths

### Zod Utilities
### Valibot Utilities

> ⚠️ **Experimental**: These utilities are still in development and may change.

- **[Zod Field Schemas](src/zod/README.md#field-schemas)** - Zod schemas with automatic form input preprocessing
- **[Valibot Field Schemas](src/valibot/README.md#field-schemas)** - Valibot schemas with automatic form input preprocessing

## License

Expand Down
32 changes: 26 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@
"types": "./dist/index.d.ts",
"main": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./zod": {
"types": "./dist/zod/index.d.ts",
"default": "./dist/zod/index.js"
}
".": "./dist/index.js",
"./valibot": "./dist/valibot/index.js",
"./zod": "./dist/zod/index.js"
},
"scripts": {
"build": "tsc",
Expand Down Expand Up @@ -51,12 +46,18 @@
"@types/node": "^24.5.2",
"prettier": "^3.6.2",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
"valibot": "^1.1.0",
"vitest": "^3.2.4",
"zod": "^4.1.11"
},
"peerDependencies": {
"valibot": "^1.0.0",
"zod": "^4.0.0"
},
"peerDependenciesMeta": {
"valibot": {
"optional": true
},
"zod": {
"optional": true
}
Expand Down
113 changes: 111 additions & 2 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@

### Table of Contents

- [Functions](#functions)
- [Core Functions](#core-functions)
- [parseFormData](#parseformdata)
- [decode](#decode)
- [serialize](#serialize)
- [getPath](#getpath)
- [setPath](#setpath)
- [Coerce Functions](#coerce-functions)
- [coerceString](#coercestring)
- [coerceNumber](#coercenumber)
- [coerceBigint](#coercebigint)
- [coerceBoolean](#coerceboolean)
- [coerceDate](#coercedate)
- [coerceFile](#coercefile)
- [coerceArray](#coercearray)
- [Types](#types)
- [Submission](#submission)
- [PathsFromObject](#pathsfromobject)

## Functions
## Core Functions

### parseFormData

Expand Down Expand Up @@ -107,6 +115,107 @@ const newObj = setPath({ a: { b: { c: [] } } }, "a.b.c[1]", "hey");
// Returns { a: { b: { c: [<empty>, 'hey'] } } }
```

## Coerce Functions

The coerce functions provide utilities for converting form input values to their expected types. These functions are essential for building custom schema implementations (like zod or valibot schemas) where you need to transform string-based form data into proper JavaScript types before validation. All coerce functions handle empty strings by returning `undefined` and pass through non-matching types unchanged, making them safe to use in schema transformation pipelines.

### coerceString

Converts string input to a string value, returning `undefined` for empty strings.

```typescript
import { coerceString } from "conformal";

console.log(coerceString("hello")); // "hello"
console.log(coerceString("")); // undefined
console.log(coerceString(123)); // 123 (unchanged)
```

### coerceNumber

Converts string input to a number, returning `undefined` for empty or whitespace-only strings.

```typescript
import { coerceNumber } from "conformal";

console.log(coerceNumber("42")); // 42
console.log(coerceNumber("3.14")); // 3.14
console.log(coerceNumber("")); // undefined
console.log(coerceNumber(" ")); // undefined
console.log(coerceNumber("abc")); // NaN
```

### coerceBigint

Converts string input to a BigInt, returning `undefined` for empty or whitespace-only strings. Returns the original string if conversion fails.

```typescript
import { coerceBigint } from "conformal";

console.log(coerceBigint("42")); // 42n
console.log(coerceBigint("9007199254740991")); // 9007199254740991n
console.log(coerceBigint("")); // undefined
console.log(coerceBigint("abc")); // "abc" (unchanged)
```

### coerceBoolean

Converts string input to a boolean based on common truthy/falsy string values.

```typescript
import { coerceBoolean } from "conformal";

console.log(coerceBoolean("true")); // true
console.log(coerceBoolean("on")); // true
console.log(coerceBoolean("1")); // true
console.log(coerceBoolean("yes")); // true
console.log(coerceBoolean("false")); // false
console.log(coerceBoolean("off")); // false
console.log(coerceBoolean("0")); // false
console.log(coerceBoolean("no")); // false
console.log(coerceBoolean("")); // undefined
console.log(coerceBoolean("maybe")); // "maybe" (unchanged)
```

### coerceDate

Converts string input to a Date object, returning `undefined` for empty strings. Returns the original string if the date is invalid.

```typescript
import { coerceDate } from "conformal";

console.log(coerceDate("2023-01-01")); // Date object
console.log(coerceDate("")); // undefined
console.log(coerceDate("invalid-date")); // "invalid-date" (unchanged)
```

### coerceFile

Handles File objects, returning `undefined` for empty files (size 0).

```typescript
import { coerceFile } from "conformal";

const emptyFile = new File([], "test.txt");
const file = new File(["content"], "test.txt");

console.log(coerceFile(emptyFile)); // undefined
console.log(coerceFile(file)); // File object
console.log(coerceFile("not-a-file")); // "not-a-file" (unchanged)
```

### coerceArray

Converts any input to an array. Empty strings become empty arrays, arrays pass through unchanged, and other values are wrapped in an array.

```typescript
import { coerceArray } from "conformal";

console.log(coerceArray("")); // []
console.log(coerceArray([1, 2, 3])); // [1, 2, 3]
console.log(coerceArray("hello")); // ["hello"]
```

## Types

### Submission
Expand Down
Loading