@oomfware/forms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ BSD Zero Clause License
2
+
3
+ Copyright (c) 2026 Mary
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @oomfware/forms
2
+
3
+ form validation middleware.
4
+
5
+ ```sh
6
+ npm install @oomfware/forms
7
+ ```
8
+
9
+ ## usage
10
+
11
+ ### basic form
12
+
13
+ ```tsx
14
+ import { asyncContext, createRouter, route } from '@oomfware/fetch-router';
15
+ import { form, forms } from '@oomfware/forms';
16
+ import { render } from '@oomfware/jsx';
17
+ import * as v from 'valibot';
18
+
19
+ const routes = route({
20
+ login: '/login',
21
+ });
22
+
23
+ const router = createRouter({
24
+ middleware: [asyncContext()],
25
+ });
26
+
27
+ const loginForm = form(
28
+ v.object({
29
+ email: v.pipe(v.string(), v.email()),
30
+ _password: v.pipe(v.string(), v.minLength(8)),
31
+ }),
32
+ async (data, issue) => {
33
+ const user = await findUserByEmail(data.email);
34
+
35
+ if (!user || !(await verifyPassword(user, data._password))) {
36
+ invalid(issue.email('Invalid email or password'));
37
+ }
38
+
39
+ return { userId: user.id };
40
+ },
41
+ );
42
+
43
+ router.map(routes, {
44
+ middleware: [forms({ loginForm })],
45
+ actions: {
46
+ login() {
47
+ return render(
48
+ <form {...loginForm}>
49
+ {loginForm.fields.email.issues()?.[0] && (
50
+ <p class="error">{loginForm.fields.email.issues()![0].message}</p>
51
+ )}
52
+
53
+ <label>
54
+ Email
55
+ <input {...loginForm.fields.email.as('email')} />
56
+ </label>
57
+
58
+ <label>
59
+ Password
60
+ <input {...loginForm.fields._password.as('password')} />
61
+ </label>
62
+
63
+ <button>Sign in</button>
64
+ </form>,
65
+ );
66
+ },
67
+ },
68
+ });
69
+ ```
70
+
71
+ ### imperative validation
72
+
73
+ use `invalid()` to add validation errors after schema validation passes:
74
+
75
+ ```ts
76
+ import { form, invalid } from '@oomfware/forms';
77
+ import * as v from 'valibot';
78
+
79
+ export const registerForm = form(
80
+ v.object({
81
+ username: v.pipe(v.string(), v.minLength(3)),
82
+ email: v.pipe(v.string(), v.email()),
83
+ _password: v.pipe(v.string(), v.minLength(8)),
84
+ }),
85
+ async (data, issue) => {
86
+ const issues = [];
87
+
88
+ if (await isUsernameTaken(data.username)) {
89
+ issues.push(issue.username('Username is already taken'));
90
+ }
91
+
92
+ if (await isEmailRegistered(data.email)) {
93
+ issues.push(issue.email('Email is already registered'));
94
+ }
95
+
96
+ if (issues.length > 0) {
97
+ invalid(...issues);
98
+ }
99
+
100
+ return await createUser(data);
101
+ },
102
+ );
103
+ ```
104
+
105
+ ### nested and array fields
106
+
107
+ ```tsx
108
+ const profileForm = form(
109
+ v.object({
110
+ user: v.object({
111
+ name: v.string(),
112
+ bio: v.optional(v.string()),
113
+ }),
114
+ socialLinks: v.array(
115
+ v.object({
116
+ platform: v.string(),
117
+ url: v.pipe(v.string(), v.url()),
118
+ }),
119
+ ),
120
+ }),
121
+ async (data) => {
122
+ return await updateProfile(data);
123
+ },
124
+ );
125
+
126
+ // in template:
127
+ <input {...profileForm.fields.user.name.as('text')} />
128
+ <textarea {...profileForm.fields.user.bio.as('text')} />
129
+
130
+ {socialLinks.map((_, i) => (
131
+ <>
132
+ <input {...profileForm.fields.socialLinks[i].platform.as('text')} />
133
+ <input {...profileForm.fields.socialLinks[i].url.as('url')} />
134
+ </>
135
+ ))}
136
+ ```
137
+
138
+ ### input types
139
+
140
+ the `.as()` method generates appropriate props for different input types:
141
+
142
+ ```tsx
143
+ // text inputs
144
+ <input {...form.fields.name.as('text')} />
145
+ <input {...form.fields.email.as('email')} />
146
+ <input {...form.fields.password.as('password')} />
147
+
148
+ // numeric inputs (auto-parsed via n: prefix)
149
+ <input {...form.fields.age.as('number')} />
150
+ <input {...form.fields.rating.as('range')} />
151
+
152
+ // boolean checkbox (auto-parsed via b: prefix)
153
+ <input {...form.fields.subscribe.as('checkbox')} />
154
+
155
+ // checkbox group (multiple values)
156
+ <input {...form.fields.tags.as('checkbox', 'javascript')} />
157
+ <input {...form.fields.tags.as('checkbox', 'typescript')} />
158
+
159
+ // radio buttons
160
+ <input {...form.fields.color.as('radio', 'red')} />
161
+ <input {...form.fields.color.as('radio', 'blue')} />
162
+
163
+ // select
164
+ <select {...form.fields.country.as('select')}>
165
+ <option value="us">United States</option>
166
+ </select>
167
+
168
+ // hidden fields (for IDs, tokens, etc.)
169
+ <input {...form.fields.postId.as('hidden', postId)} />
170
+
171
+ // file uploads
172
+ <input {...form.fields.avatar.as('file')} />
173
+ <input {...form.fields.attachments.as('file multiple')} />
174
+ ```
175
+
176
+ ### accessing form result
177
+
178
+ ```tsx
179
+ router.map(routes, {
180
+ middleware: [forms({ messageForm })],
181
+ messages() {
182
+ if (messageForm.result) {
183
+ return render(<p>Message sent! ID: {messageForm.result.messageId}</p>);
184
+ }
185
+
186
+ return render(<form {...messageForm}>{/* form fields */}</form>);
187
+ },
188
+ });
189
+ ```
190
+
191
+ ### multiple submit actions
192
+
193
+ use `buttonProps` when a form has multiple submit buttons that trigger different actions:
194
+
195
+ ```tsx
196
+ import { form, forms } from '@oomfware/forms';
197
+ import { render } from '@oomfware/jsx';
198
+ import * as v from 'valibot';
199
+
200
+ const draftSchema = v.object({
201
+ title: v.string(),
202
+ content: v.string(),
203
+ });
204
+
205
+ const saveDraft = form(draftSchema, async (data) => {
206
+ return await saveDraftToDb(data);
207
+ });
208
+
209
+ const publishPost = form(draftSchema, async (data) => {
210
+ return await publishToDb(data);
211
+ });
212
+
213
+ router.map(routes, {
214
+ middleware: [forms({ saveDraft, publishPost })],
215
+ actions: {
216
+ editor() {
217
+ return render(
218
+ <form {...saveDraft}>
219
+ <input {...saveDraft.fields.title.as('text')} placeholder="Title" />
220
+ <textarea {...saveDraft.fields.content.as('text')} />
221
+
222
+ <button>Save draft</button>
223
+ <button {...publishPost.buttonProps}>Publish</button>
224
+ </form>,
225
+ );
226
+ },
227
+ },
228
+ });
229
+ ```
230
+
231
+ ### sensitive field redaction
232
+
233
+ fields prefixed with `_` are automatically redacted from form state on validation failure:
234
+
235
+ ```tsx
236
+ const loginForm = form(
237
+ v.object({
238
+ email: v.pipe(v.string(), v.email()),
239
+ _password: v.pipe(v.string(), v.minLength(8)), // redacted on error
240
+ }),
241
+ async (data) => {
242
+ // data._password is available here
243
+ },
244
+ );
245
+
246
+ // if validation fails, loginForm.fields._password.value() returns undefined
247
+ // this prevents passwords from being echoed back in the form
248
+ ```
@@ -0,0 +1,181 @@
1
+ import { RouterMiddleware } from "@oomfware/fetch-router";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region src/lib/types.d.ts
5
+ type MaybePromise<T> = T | Promise<T>;
6
+ //#endregion
7
+ //#region src/lib/form.d.ts
8
+ interface FormInput {
9
+ [key: string]: MaybeArray<string | number | boolean | File | FormInput>;
10
+ }
11
+ type MaybeArray<T> = T | T[];
12
+ interface FormIssue {
13
+ message: string;
14
+ path: (string | number)[];
15
+ }
16
+ /**
17
+ * the issue creator proxy passed to form callbacks.
18
+ * allows creating field-specific validation issues via property access.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * form(schema, async (data, issue) => {
23
+ * if (emailTaken(data.email)) {
24
+ * invalid(issue.email('Email already in use'));
25
+ * }
26
+ * // nested fields: issue.user.profile.name('Invalid name')
27
+ * // array fields: issue.items[0].name('Invalid item name')
28
+ * });
29
+ * ```
30
+ */
31
+ type InvalidField<T> = ((message: string) => StandardSchemaV1.Issue) & { [K in keyof T]-?: T[K] extends (infer U)[] ? InvalidFieldArray<U> : T[K] extends object ? InvalidField<T[K]> : (message: string) => StandardSchemaV1.Issue };
32
+ type InvalidFieldArray<T> = {
33
+ [index: number]: T extends object ? InvalidField<T> : (message: string) => StandardSchemaV1.Issue;
34
+ } & ((message: string) => StandardSchemaV1.Issue);
35
+ /**
36
+ * internal info attached to a form instance.
37
+ * used by the forms() middleware to identify and process forms.
38
+ */
39
+
40
+ /**
41
+ * the return value of a form() function.
42
+ * can be spread onto a <form> element.
43
+ */
44
+ interface Form<Input extends FormInput | void, Output> {
45
+ /** HTTP method */
46
+ readonly method: 'POST';
47
+ /** the form action URL */
48
+ readonly action: string;
49
+ /** the handler result, if submission was successful */
50
+ readonly result: Output | undefined;
51
+ /** access form fields using object notation */
52
+ readonly fields: FormFields<Input>;
53
+ /** spread this onto a <button> or <input type="submit"> */
54
+ readonly buttonProps: FormButtonProps;
55
+ }
56
+ interface FormButtonProps {
57
+ type: 'submit';
58
+ readonly formaction: string;
59
+ }
60
+ /** valid leaf value types for form fields */
61
+ type FormFieldValue = string | string[] | number | boolean | File | File[];
62
+ /** guard to prevent infinite recursion when T is unknown or has an index signature */
63
+ type WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;
64
+ /** base methods available on all form fields */
65
+ interface FormFieldMethods<T> {
66
+ /** get the current value */
67
+ value(): T | undefined;
68
+ /** set the value */
69
+ set(value: T): T;
70
+ /** get validation issues for this field */
71
+ issues(): FormIssue[] | undefined;
72
+ }
73
+ /** leaf field (primitives, files) with .as() method */
74
+ type FormFieldLeaf<T extends FormFieldValue> = FormFieldMethods<T> & {
75
+ /** get props for an input element */
76
+ as(type: string, value?: string): Record<string, unknown>;
77
+ };
78
+ /** container field (objects, arrays) with allIssues() method */
79
+ type FormFieldContainer<T> = FormFieldMethods<T> & {
80
+ /** get all issues for this field and descendants */
81
+ allIssues(): FormIssue[] | undefined;
82
+ };
83
+ /** fallback field type when recursion would be infinite */
84
+ type FormFieldUnknown<T> = FormFieldMethods<T> & {
85
+ /** get all issues for this field and descendants */
86
+ allIssues(): FormIssue[] | undefined;
87
+ /** get props for an input element */
88
+ as(type: string, value?: string): Record<string, unknown>;
89
+ } & {
90
+ [key: string | number]: FormFieldUnknown<unknown>;
91
+ };
92
+ /**
93
+ * recursive type to build form fields structure with proxy access.
94
+ * preserves type information through the object hierarchy.
95
+ */
96
+ type FormFields<T> = T extends void ? Record<string, never> : WillRecurseIndefinitely<T> extends true ? FormFieldUnknown<T> : NonNullable<T> extends string | number | boolean | File ? FormFieldLeaf<NonNullable<T>> : T extends string[] | File[] ? FormFieldLeaf<T> & { [K in number]: FormFieldLeaf<T[number]> } : T extends Array<infer U> ? FormFieldContainer<T> & { [K in number]: FormFields<U> } : FormFieldContainer<T> & { [K in keyof T]-?: FormFields<T[K]> };
97
+ /**
98
+ * creates a form without validation.
99
+ */
100
+ declare function form<Output>(fn: () => MaybePromise<Output>): Form<void, Output>;
101
+ /**
102
+ * creates a form with unchecked input (no validation).
103
+ */
104
+ declare function form<Input extends FormInput, Output>(validate: 'unchecked', fn: (data: Input, issue: InvalidField<Input>) => MaybePromise<Output>): Form<Input, Output>;
105
+ /**
106
+ * creates a form with Standard Schema validation.
107
+ */
108
+ declare function form<Schema extends StandardSchemaV1<FormInput, Record<string, unknown>>, Output>(validate: Schema, fn: (data: StandardSchemaV1.InferOutput<Schema>, issue: InvalidField<StandardSchemaV1.InferInput<Schema>>) => MaybePromise<Output>): Form<StandardSchemaV1.InferInput<Schema>, Output>;
109
+ //#endregion
110
+ //#region src/lib/middleware.d.ts
111
+ /**
112
+ * a record of form instances to register with the middleware.
113
+ */
114
+ type FormDefinitions = Record<string, Form<any, any>>;
115
+ /**
116
+ * creates a forms middleware that registers forms and handles form submissions.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * import { form, forms } from '@oomfware/forms';
121
+ * import * as v from 'valibot';
122
+ *
123
+ * const createUserForm = form(
124
+ * v.object({ name: v.string(), password: v.string() }),
125
+ * async (input, issue) => {
126
+ * // handle form submission
127
+ * },
128
+ * );
129
+ *
130
+ * router.map(routes.admin, {
131
+ * middleware: [forms({ createUserForm })],
132
+ * action() {
133
+ * return render(
134
+ * <form {...createUserForm}>
135
+ * <input {...createUserForm.fields.name.as('text')} required />
136
+ * </form>
137
+ * );
138
+ * },
139
+ * });
140
+ * ```
141
+ */
142
+ declare function forms(definitions: FormDefinitions): RouterMiddleware;
143
+ //#endregion
144
+ //#region src/lib/errors.d.ts
145
+ /**
146
+ * error thrown when form validation fails imperatively
147
+ */
148
+ declare class ValidationError extends Error {
149
+ issues: StandardSchemaV1.Issue[];
150
+ constructor(issues: StandardSchemaV1.Issue[]);
151
+ }
152
+ /**
153
+ * use this to throw a validation error to imperatively fail form validation.
154
+ * can be used in combination with `issue` passed to form actions to create field-specific issues.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * import { invalid, form } from '@oomfware/forms';
159
+ * import * as v from 'valibot';
160
+ *
161
+ * export const login = form(
162
+ * v.object({ name: v.string(), _password: v.string() }),
163
+ * async ({ name, _password }, issue) => {
164
+ * const success = tryLogin(name, _password);
165
+ * if (!success) {
166
+ * invalid('Incorrect username or password');
167
+ * }
168
+ *
169
+ * // ...
170
+ * }
171
+ * );
172
+ * ```
173
+ */
174
+ declare function invalid(...issues: (StandardSchemaV1.Issue | string)[]): never;
175
+ /**
176
+ * checks whether this is a validation error thrown by {@link invalid}.
177
+ */
178
+ declare function isValidationError(e: unknown): e is ValidationError;
179
+ //#endregion
180
+ export { type Form, type FormInput, type FormIssue, ValidationError, form, forms, invalid, isValidationError };
181
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/lib/form.ts","../src/lib/middleware.ts","../src/lib/errors.ts"],"sourcesContent":[],"mappings":"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"}