Skip to content

Commit 8a91a3d

Browse files
authored
Restructure README (#28)
1 parent 8d8a848 commit 8a91a3d

4 files changed

Lines changed: 221 additions & 222 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cd examples/svelte && npm i && npm run dev
2020
## Public API
2121

2222
- `conformal`: `getPath`, `setPath`, `decode`, `parseFormData`, `serialize`; types: `PathsFromObject`, `Submission`
23-
- `conformal/zod`: `string`, `number`, `boolean`, `date`, `bigint`, `enum`, `file`, `url`, `email`
23+
- `conformal/zod`: `string`, `number`, `boolean`, `date`, `bigint`, `enum`, `file`, `url`, `email`, `object`, `array`
2424

2525
Exports live in `src/index.ts` and `src/zod/index.ts`.
2626

@@ -43,7 +43,7 @@ Exports live in `src/index.ts` and `src/zod/index.ts`.
4343

4444
```bash
4545
# Fast local CI
46-
npm run format:check && npm run typecheck && npm run test && npm run build
46+
npm run format:check && npm run typecheck && npm run test
4747

4848
# Focus tests
4949
npx vitest run -t "<name>" # by test name

README.md

Lines changed: 40 additions & 220 deletions
Original file line numberDiff line numberDiff line change
@@ -2,262 +2,82 @@
22

33
> Type-safe form submissions for the modern web.
44
5-
Conformal helps you work with native [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) the way frameworks are moving: directly. It solves two major pain points:
5+
Conformal helps you work with native [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData). It solves two major pain points:
66

77
-**Strongly typed FormData parsing** – Turn native `FormData` into real objects with full TypeScript inference (nested objects and arrays with dot/bracket notation).
88
-**Canonical submission flow** – A single `Submission` object that preserves raw input, separates field vs. form errors, and standardizes the success/error states.
99

10-
Works everywhere: In browsers, Node.js, and edge runtimes with React, Vue, Svelte, or vanilla JavaScript. No framework lock-in.
10+
Works everywhere: In browsers, Node.js, and edge runtimes with React, Vue, Svelte, or vanilla JavaScript.
1111

1212
### Table of Contents
1313

14-
- [Installation](#installation)
14+
- [Getting Started](#getting-started)
1515
- [Live Examples](#live-examples)
16-
- [Usage](#usage)
17-
- [parseFormData](#parseformdata)
18-
- [Submission](#submission)
19-
- [decode](#decode)
20-
- [serialize](#serialize)
21-
- [getPath](#getpath)
22-
- [setPath](#setpath)
23-
- [PathsFromObject](#pathsfromobject)
24-
- [Zod Field Schemas](#zod-field-schemas)
16+
- [API Reference](#api-reference)
2517
- [License](#license)
2618

27-
## Installation
19+
## Getting Started
2820

2921
Install Conformal via npm or the package manager of your choice:
3022

3123
```bash
3224
npm install conformal
3325
```
3426

35-
## Live Examples
36-
37-
- **React** - Form actions with useActionState: [StackBlitz](https://stackblitz.com/github/marcomuser/conformal/tree/main/examples/react?embed=1&theme=dark&preset=node&file=src/Form.tsx) | [Source](https://github.com/marcomuser/conformal/tree/main/examples/react)
38-
39-
- **SvelteKit** - Server-side form actions: [StackBlitz](https://stackblitz.com/github/marcomuser/conformal/tree/main/examples/svelte?embed=1&theme=dark&preset=node&file=src/routes/%2Bpage.server.ts) | [Source](https://github.com/marcomuser/conformal/tree/main/examples/svelte)
40-
41-
## Usage
42-
43-
### parseFormData
44-
45-
The `parseFormData` function parses and validates [FormData](https://developer.mozilla.org/docs/Web/API/FormData) against a [Standard Schema](https://standardschema.dev). It internally uses the [decode](#decode) function to first convert the `FormData` into a structured object before applying schema validation.
46-
47-
**🚀 Try it yourself**: This example includes an import map and can be run directly in a browser!
48-
49-
```html
50-
<body>
51-
<form id="userForm">
52-
<input type="text" name="name" placeholder="Name" />
53-
<input type="number" name="age" placeholder="Age" />
54-
<input type="text" name="hobbies" placeholder="Hobby 1" />
55-
<input type="text" name="hobbies" placeholder="Hobby 2" />
56-
<button type="submit">Submit</button>
57-
</form>
58-
59-
<script type="importmap">
60-
{
61-
"imports": {
62-
"conformal": "https://cdn.jsdelivr.net/npm/conformal/+esm",
63-
"zod": "https://cdn.jsdelivr.net/npm/zod/+esm"
64-
}
65-
}
66-
</script>
67-
68-
<script type="module">
69-
import { parseFormData } from "conformal";
70-
import * as z from "zod";
71-
72-
const schema = z.object({
73-
name: z.string(),
74-
age: z.coerce.number(),
75-
hobbies: z.string().array(),
76-
});
77-
78-
const form = document.getElementById("userForm");
79-
form.addEventListener("submit", (event) => {
80-
event.preventDefault();
81-
82-
const formData = new FormData(form);
83-
const submission = parseFormData(schema, formData).submission();
84-
85-
if (submission.status === "success") {
86-
console.log(submission.value); // Successful result value
87-
console.log(submission.input); // Raw parsed form data
88-
} else {
89-
console.log(submission.fieldErrors); // Field-specific validation errors
90-
console.log(submission.formErrors); // Form-level validation errors
91-
console.log(submission.input); // Raw parsed form data
92-
}
93-
});
94-
</script>
95-
</body>
96-
```
97-
98-
This will result in the following data structure:
27+
Here's a quick example showing how Conformal handles form validation with a user registration form:
9928

10029
```typescript
101-
const value = {
102-
name: "John Doe",
103-
age: 30,
104-
hobbies: ["Music", "Coding"],
105-
};
106-
```
107-
108-
The `parseFormData` function returns a `SchemaResult` object that extends the standard schema validation result with a `submission()` method. This method provides a consistent `Submission` object that makes it easy to handle both successful and failed validation results:
30+
import { parseFormData } from "conformal";
31+
import * as z from "zod"; // Tip: Use conformal/zod for automatic form input preprocessing
32+
33+
const schema = z.object({
34+
name: z.string().min(2, "Name must be at least 2 characters"),
35+
email: z.email("Invalid email address"),
36+
age: z.coerce.number().min(18, "Must be at least 18 years old"),
37+
acceptTerms: z.coerce.boolean(),
38+
});
10939

110-
```typescript
111-
const submission = parseFormData(schema, formData).submission();
40+
// In your form action or handler
41+
const result = parseFormData(schema, formData);
42+
const submission = result.submission();
11243

11344
if (submission.status === "success") {
114-
// Access validated data
115-
const validatedData = submission.value;
116-
// Access raw parsed form data
117-
const rawInput = submission.input;
45+
// submission.value is fully typed: { name: string, email: string, age: number, acceptTerms: boolean }
46+
console.log("User registered:", submission.value);
11847
} else {
119-
// Handle validation errors
120-
const fieldErrors = submission.fieldErrors; // Field-specific errors
121-
const formErrors = submission.formErrors; // Form-level errors
122-
// Access raw parsed form data even on failure
123-
const rawInput = submission.input;
48+
// submission.fieldErrors contains validation errors: { email: ["Invalid email address"] }
49+
console.log("Validation errors:", submission.fieldErrors);
50+
// submission.input preserves the raw user input for re-display
51+
console.log("User input:", submission.input);
12452
}
12553
```
12654

127-
### Submission
128-
129-
The `Submission` type represents the result of form validation and provides a clean interface for handling both successful and failed validation results. This is the type that the `submission()` method returns from `parseFormData`.
130-
131-
**Properties:**
132-
133-
- **`status`**: A string that tells you the outcome - either `"success"` when validation passes, `"error"` when it fails, or `"idle"` for initial states
134-
- **`value`**: Contains your validated and typed data when `status` is `"success"`. This is `undefined` when there are validation errors
135-
- **`input`**: Always contains the raw user input that was submitted, regardless of validation success or failure. This is useful for preserving user input even when validation fails
136-
- **`fieldErrors`**: An object that maps field names to arrays of error messages. For example, `{ "email": ["Invalid email format"], "age": ["Must be a number"] }`. This is empty when validation succeeds
137-
- **`formErrors`**: An array of form-level validation errors that aren't tied to specific fields. For example, `["Passwords don't match", "Terms must be accepted"]`. This is empty when validation succeeds
55+
That's it! Conformal automatically handles FormData parsing, type coercion, and provides a clean submission interface.
13856

139-
**Key Benefits:**
140-
141-
- **Type Safety**: Full TypeScript support with automatic type inference for your data
142-
- **Data Preservation**: Raw input is always available, even on validation failure
143-
- **Granular Error Handling**: Separate field and form-level errors for precise UI feedback
144-
- **Immutable**: All properties are read-only, preventing accidental mutations
145-
146-
### decode
147-
148-
The `decode` function allows you to convert a `FormData` object into a structured object with typed values. It supports both dot notation for nested objects and square bracket notation for arrays. You can mix dot and square bracket notation to create complex structures. The `decode` function allows you to create your own schema validator in cases where `parseFormData` does not support your use case.
149-
150-
```typescript
151-
import { decode } from "conformal";
152-
153-
const formData = new FormData();
154-
formData.append("user.name", "John Doe");
155-
formData.append("user.age", "30");
156-
formData.append("user.contacts[0].type", "email");
157-
formData.append("user.contacts[0].value", "john.doe@example.com");
158-
formData.append("user.contacts[1].type", "phone");
159-
formData.append("user.contacts[1].value", "123-456-7890");
160-
161-
const result = decode<{
162-
user: {
163-
name: string;
164-
age: string;
165-
contacts: { type: string; value: string }[];
166-
};
167-
}>(formData);
168-
```
169-
170-
This will result in the following data structure:
171-
172-
```typescript
173-
const result = {
174-
user: {
175-
name: "John Doe",
176-
age: "30",
177-
contacts: [
178-
{ type: "email", value: "john.doe@example.com" },
179-
{ type: "phone", value: "123-456-7890" },
180-
],
181-
},
182-
};
183-
```
184-
185-
### serialize
186-
187-
The `serialize` function transforms fully typed values back to the InputValue shape for use in form elements. This is particularly useful for setting default values in form fields when you have validated data from a previous submission and want to pre-fill forms with existing data from a database.
188-
189-
```typescript
190-
import { serialize } from "conformal";
191-
192-
console.log(serialize(123)); // "123"
193-
console.log(serialize(true)); // "on"
194-
console.log(serialize(new Date())); // "2025-01-17T17:04:25.059Z"
195-
console.log(serialize({ username: "test", age: 100 })); // { username: "test", age: "100" }
196-
```
197-
198-
### getPath
199-
200-
Retrieve a value from an object using a path. This function is a foundational tool for handling object paths using dot and square bracket notation. It's particularly useful for developers building custom client-side validation libraries or complex data manipulation patterns.
201-
202-
```typescript
203-
import { getPath } from "conformal";
204-
205-
const value = getPath({ a: { b: { c: ["hey", "Hi!"] } } }, "a.b.c[1]");
206-
// Returns 'Hi!'
207-
```
208-
209-
### setPath
210-
211-
Set a value in an object using a path. The `setPath` function is used internally by the `decode` function and provides powerful object manipulation capabilities. **Note**: Creates copies only where needed to preserve immutability, avoiding unnecessary deep copying for better performance.
212-
213-
```typescript
214-
import { setPath } from "conformal";
215-
216-
const newObj = setPath({ a: { b: { c: [] } } }, "a.b.c[1]", "hey");
217-
// Returns { a: { b: { c: [<empty>, 'hey'] } } }
218-
```
219-
220-
### PathsFromObject
57+
## Live Examples
22158

222-
Extract all possible paths from an object type while automatically excluding paths that lead to browser-specific built-in types such as Blob, FileList, and Date. This type utility is useful for creating abstractions that enable type-safe access to specific fields within complex form data structures.
59+
- **React** - Form actions with useActionState: [StackBlitz](https://stackblitz.com/github/marcomuser/conformal/tree/main/examples/react?embed=1&theme=dark&preset=node&file=src/Form.tsx) | [Source](https://github.com/marcomuser/conformal/tree/main/examples/react)
22360

224-
```typescript
225-
import type { PathsFromObject } from "conformal";
61+
- **SvelteKit** - Server-side form actions: [StackBlitz](https://stackblitz.com/github/marcomuser/conformal/tree/main/examples/svelte?embed=1&theme=dark&preset=node&file=src/routes/%2Bpage.server.ts) | [Source](https://github.com/marcomuser/conformal/tree/main/examples/svelte)
22662

227-
interface UserForm {
228-
user: {
229-
name: string;
230-
profilePicture: File;
231-
contacts: { type: string; value: string }[];
232-
};
233-
}
63+
## API Reference
23464

235-
type Paths = PathsFromObject<UserForm>;
236-
// Paths will be "user" | "user.name" | "user.profilePicture" | "user.contacts" | `user.contacts[${number}]` | `user.contacts[${number}].type` | `user.contacts[${number}].value`
237-
```
65+
### Core Functions
23866

239-
## Zod Field Schemas
67+
- **[`parseFormData`](src/README.md#parseformdata)** - Parse FormData with schema validation and get Submission object
68+
- **[`decode`](src/README.md#decode)** - Convert FormData to structured objects (no validation)
69+
- **[`serialize`](src/README.md#serialize)** - Transform typed values back to form-compatible strings
70+
- **[`getPath`](src/README.md#getpath)** - Safely access nested values using dot/bracket notation
71+
- **[`setPath`](src/README.md#setpath)** - Immutably set nested values using dot/bracket notation
24072

241-
Conformal provides optional Zod utilities that are **thin preprocessing wrappers** around Zod schemas. They automatically handle form input patterns (empty strings, type coercion, boolean detection) while maintaining **100% Zod compatibility**.
73+
### Types
24274

243-
**Zero learning curve** - use them exactly like regular Zod schemas with all methods (`.optional()`, `.min()`, `.max()` etc.). Import from `conformal/zod` to keep your bundle lean if you don't use Zod.
75+
- **[`Submission`](src/README.md#submission)** - Standardized submission result with success/error states
76+
- **[`PathsFromObject`](src/README.md#pathsfromobject)** - Type utility to extract all possible object paths
24477

245-
```typescript
246-
import * as zf from "conformal/zod";
78+
### Zod Utilities
24779

248-
const formSchema = zf.object({
249-
name: zf.string().optional(),
250-
email: zf.email(),
251-
age: zf.number().min(13, "Must be at least 13 years old"),
252-
hobbies: zf.array(zf.string()),
253-
birthDate: zf.date(),
254-
acceptTerms: zf.boolean(),
255-
profilePicture: zf.file(),
256-
accountType: zf.enum(["personal", "business"]),
257-
website: zf.url().optional(),
258-
transactionAmount: zf.bigint(),
259-
});
260-
```
80+
- **[Zod Field Schemas](src/zod/README.md#field-schemas)** - Zod schemas with automatic form input preprocessing
26181

26282
## License
26383

0 commit comments

Comments
 (0)