@thomasfosterau/effect-forms 0.1.0 → 0.3.1
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/README.md +35 -39
- package/dist/index.d.ts +85 -13
- package/dist/index.js +529 -411
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# @thomasfosterau/effect-forms
|
|
2
2
|
|
|
3
|
-
Framework-neutral, **Effect-native** form validation and state — schema-driven validation, nested errors, posted-value retention, status messages, and native input constraints, built on **Effect Schema** with no framework dependency. The Svelte 5 bindings live in [`@thomasfosterau/effect-forms-svelte`](
|
|
3
|
+
Framework-neutral, **Effect-native** form validation and state — schema-driven validation, nested errors, posted-value retention, status messages, and native input constraints, built on **Effect Schema** with no framework dependency. The Svelte 5 bindings live in [`@thomasfosterau/effect-forms-svelte`](https://www.npmjs.com/package/@thomasfosterau/effect-forms-svelte).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @thomasfosterau/effect-forms effect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`effect` is a peer dependency; the package never bundles its own copy.
|
|
8
12
|
|
|
9
|
-
##
|
|
13
|
+
## Usage
|
|
10
14
|
|
|
11
15
|
```ts
|
|
12
16
|
import * as Schema from "effect/Schema";
|
|
@@ -24,15 +28,17 @@ const signupForm = Form.make(Signup, {
|
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
// In a server action / load:
|
|
31
|
+
declare const request: Request;
|
|
27
32
|
const state = await Effect.runPromise(signupForm.validate(request));
|
|
28
33
|
// state: { id, valid, posted, data, errors, constraints, message? }
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
### Building a form incrementally
|
|
32
37
|
|
|
33
38
|
`Form.make` binds a hand-written `Schema.Struct`. When you'd rather compose a form field by field — reusing field groups, merging them, and attaching whole-form (cross-field) rules — reach for `FormBuilder`:
|
|
34
39
|
|
|
35
40
|
```ts
|
|
41
|
+
import * as Effect from "effect/Effect";
|
|
36
42
|
import * as Schema from "effect/Schema";
|
|
37
43
|
import { Field, Form, FormBuilder } from "@thomasfosterau/effect-forms";
|
|
38
44
|
|
|
@@ -47,6 +53,7 @@ const signup = FormBuilder.empty
|
|
|
47
53
|
|
|
48
54
|
// Bind it exactly like a schema (defaults are derived when omitted):
|
|
49
55
|
const form = Form.fromBuilder(signup, { id: "signup" });
|
|
56
|
+
declare const request: Request; // the incoming submission
|
|
50
57
|
const state = await Effect.runPromise(form.validate(request));
|
|
51
58
|
```
|
|
52
59
|
|
|
@@ -54,47 +61,36 @@ const state = await Effect.runPromise(form.validate(request));
|
|
|
54
61
|
|
|
55
62
|
Modules are exposed as Effect-style namespaces (`Form`, `State`, `Result`, `Errors`, …). `Form.make(schema, { defaults })` is the server-side validate-and-seed entry point:
|
|
56
63
|
|
|
57
|
-
- **`validate(input)`** accepts a `FormData`, `URLSearchParams`, `Request`, or a plain object. It parses the body (nested `author.name` / `tags[0]` / `tags[]` notation), decodes it through the schema collecting **every** field error (`errors: "all"`), and returns a serialisable [`FormState`](src/State.ts) — valid with the decoded data, or invalid with the nested [`FormErrors`](src/Errors.ts) tree and the **posted values preserved** so the form re-renders what the user typed. It is an `Effect`, so a schema that requires decoding services composes naturally; it never fails on the error channel (validation failures live in the returned state).
|
|
64
|
+
- **`validate(input)`** accepts a `FormData`, `URLSearchParams`, `Request`, or a plain object. It parses the body (nested `author.name` / `tags[0]` / `tags[]` notation), decodes it through the schema collecting **every** field error (`errors: "all"`), and returns a serialisable [`FormState`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/State.ts) — valid with the decoded data, or invalid with the nested [`FormErrors`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Errors.ts) tree and the **posted values preserved** so the form re-renders what the user typed. It is an `Effect`, so a schema that requires decoding services composes naturally; it never fails on the error channel (validation failures live in the returned state).
|
|
58
65
|
- **`empty()`** seeds a blank, unposted form from the defaults — what a `load` returns to render a create form.
|
|
59
66
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
| Module
|
|
63
|
-
|
|
|
64
|
-
| [`Form`](src/Form.ts) | `make` — bind a schema to a form (validate + empty); `fromBuilder` — bind a `FormBuilder`. |
|
|
65
|
-
| [`FormBuilder`](src/FormBuilder.ts) | Compose a form field by field: `addField` / `merge` / `refine` / `refineEffect` / `buildSchema`. |
|
|
66
|
-
| [`Field`](src/Field.ts) | `makeField` / `makeArrayField` field defs + guards, and blank-default derivation from a schema — `defaultValue` / `defaultEncodedValues` for a builder field, `schemaAt` / `defaultItemAt` for any nested path. |
|
|
67
|
-
| [`State`](src/State.ts) | `FormState`, `
|
|
68
|
-
| [`Errors`](src/Errors.ts) | The nested `FormErrors` tree + `issuesToErrors` / `flattenErrors` / `getError` / `setError` / `mergeErrors` / `remapIndices`; `
|
|
69
|
-
| [`Issues`](src/Issues.ts) | `FormIssue` + the Effect Schema issue-tree walker. |
|
|
70
|
-
| [`FormData`](src/FormData.ts) | `formDataToObject` / `parseFieldPath` — structured parsing of an HTML body. |
|
|
71
|
-
| [`Path`](src/Path.ts) | `getAt` / `setAt` (immutable) along a `FormPath`, `toString` / `fromString` notation conversion, the type-level `FieldPath<T>` / `PathValue<T, P>` deep-key projections a typed field handle is built on, and the `IndexMap` constructors (`removeAt` / `insertAt` / `swapAt` / `moveAt` / `replaceAt` / `clearAll`) + `remapIndex` / `remapKeyedRecord` for keeping per-index state in sync with an array mutation. |
|
|
72
|
-
| [`Coerce`](src/Coerce.ts) | Tri-state (value / `null` / `undefined`) coercers for the form boundary. |
|
|
73
|
-
| [`
|
|
74
|
-
| [`
|
|
75
|
-
| [`
|
|
76
|
-
| [`
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
| Module | Purpose |
|
|
70
|
+
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| [`Form`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Form.ts) | `make` — bind a schema to a form (validate + empty); `fromBuilder` — bind a `FormBuilder`. |
|
|
72
|
+
| [`FormBuilder`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/FormBuilder.ts) | Compose a form field by field: `addField` / `merge` / `refine` / `refineEffect` / `buildSchema`. |
|
|
73
|
+
| [`Field`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Field.ts) | `makeField` / `makeArrayField` field defs + guards, and blank-default derivation from a schema — `defaultValue` / `defaultEncodedValues` for a builder field, `schemaAt` / `defaultItemAt` for any nested path. |
|
|
74
|
+
| [`State`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/State.ts) | `FormState`, `make`, `withMessage`, `withError`, `isValid`, `FormStateSchema` (transport). |
|
|
75
|
+
| [`Errors`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Errors.ts) | The nested `FormErrors` tree + `issuesToErrors` / `flattenErrors` / `getError` / `setError` / `mergeErrors` / `remapIndices`; `SourcedErrors` per-source tracking via `replaceAt` / `setSourceAt` / `clearSourceAt` / `mergeSourced`. |
|
|
76
|
+
| [`Issues`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Issues.ts) | `FormIssue` + the Effect Schema issue-tree walker. |
|
|
77
|
+
| [`FormData`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/FormData.ts) | `formDataToObject` / `parseFieldPath` — structured parsing of an HTML body. |
|
|
78
|
+
| [`Path`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Path.ts) | `getAt` / `setAt` (immutable) along a `FormPath`, `toString` / `fromString` notation conversion, the type-level `FieldPath<T>` / `PathValue<T, P>` deep-key projections a typed field handle is built on, and the `IndexMap` constructors (`removeAt` / `insertAt` / `swapAt` / `moveAt` / `replaceAt` / `clearAll`) + `remapIndex` / `remapKeyedRecord` for keeping per-index state in sync with an array mutation. |
|
|
79
|
+
| [`Coerce`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Coerce.ts) | Tri-state (value / `null` / `undefined`) coercers for the form boundary. |
|
|
80
|
+
| [`Command`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Command.ts) | Derive form-input schemas, blank defaults and tri-state create / update adapters from a command-struct pair: `plan`, `formInputFields`, `blankDefaults`, `createAdapter`, `updateAdapter`, `jsonRowsFromField`. |
|
|
81
|
+
| [`Constraints`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Constraints.ts) | `constraintsFromSchema` — native input attributes (`required`, `minlength`/`maxlength`, `min`/`max`, `pattern`, `step`) derived from the schema AST, including into array items; `constraintAt` reads one field's leaf out of the derived tree. |
|
|
82
|
+
| [`Mode`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Mode.ts) | `FormMode` + `parse` — when a form validates (`onSubmit` / `onBlur` / `onChange`), whether it auto-submits, and when a posted form revalidates (`revalidate`). |
|
|
83
|
+
| [`Result`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Result.ts) | `FormActionResult` — the framework-neutral submission outcome (success / failure / redirect / error). |
|
|
84
|
+
| [`Validate`](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/src/Validate.ts) | `validateValue` / `validateValueSync` — ad-hoc value/field validation; `FieldValidators` + `runSyncFieldValidator` / `runAsyncFieldValidator` — per-field sync/async validators (debounced, fiber-cancellable). |
|
|
77
85
|
|
|
78
86
|
## Notes
|
|
79
87
|
|
|
80
88
|
- **Coercion is the schema's job.** An HTML body is all strings; build the schema from coercing leaves (`Schema.FiniteFromString`, `Schema.BooleanFromString`, the `Coerce` helpers) so the parsed object decodes cleanly. The parser never guesses types.
|
|
81
89
|
- **Errors nest by field.** Leaf fields are `string[]`; nested objects / array indices nest; a branch's own (cross-field) message lives under `_errors`. The tree is plain JSON — it crosses the SSR boundary unchanged.
|
|
82
|
-
- **Update semantics.** The `Coerce` helpers honour the value / `null` / `undefined` contract (set / clear / carry-forward)
|
|
83
|
-
- **Constraint derivation reads schema checks, not just optionality.** `constraintsFromSchema` walks each property's `.check()`s for the `meta` annotation Effect Schema's built-in checks carry (`isMinLength`, `
|
|
90
|
+
- **Update semantics.** The `Coerce` helpers honour the value / `null` / `undefined` contract (set / clear / carry-forward) for partial updates.
|
|
91
|
+
- **Constraint derivation reads schema checks, not just optionality.** `constraintsFromSchema` walks each property's `.check()`s for the `meta` annotation Effect Schema's built-in checks carry (`isMinLength`, `isBetweenLength`, `isPattern`, `isGreaterThanOrEqualTo`, `isBetween`, `isMultipleOf`, `isInt`, …), unwraps `optional` / `NullOr` unions, and descends into struct and array-item shapes. A check with no recognised `meta` tag (a bare `Schema.filter` predicate) derives nothing — there's no attribute to guess at. Anything the walk can't (or shouldn't) infer goes through the `overrides` argument, which always wins on conflict.
|
|
84
92
|
- **Errors have a source.** `Errors.SourcedErrors` keeps a `client` tree (browser-side validation) parallel to a `server` tree (the last submission), merged for display by `mergeSourced`. `Errors.replaceAt` / `setSourceAt` / `clearSourceAt` update one path in one source without disturbing the rest — what a reactive store needs so a server-reported failure survives a client revalidation of some _other_ field, but clears once its own field changes.
|
|
85
93
|
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
Source modules use PascalCase file names, matching Effect's own convention (`effect/Schema`, `effect/SchemaParser`). The package has a single entry point: every module is re-exported from the `.` barrel, so there are no subpaths to import from.
|
|
89
|
-
|
|
90
|
-
## Development
|
|
91
|
-
|
|
92
|
-
From the repository root:
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
vp run -r build # build every package with tsdown
|
|
96
|
-
vp check # format (Oxfmt), lint (Oxlint), type-check (tsgolint)
|
|
97
|
-
vp run -r test # unit tests
|
|
98
|
-
```
|
|
94
|
+
## License
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
[MIT](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ declare namespace Coerce_d_exports {
|
|
|
7
7
|
export { booleanFromCheckbox, emptyToNull, emptyToUndefined, numberFromField, trimToNull, trimToUndefined };
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* @copyright Thomas Foster
|
|
11
|
-
*
|
|
12
10
|
* Tri-state coercers for the HTML-form boundary.
|
|
13
11
|
*
|
|
14
12
|
* An HTML form submits every value as a string, and an absent optional field
|
|
@@ -22,6 +20,7 @@ declare namespace Coerce_d_exports {
|
|
|
22
20
|
* framework's `error()` helper (validation failures belong on the schema, not
|
|
23
21
|
* in a coercer).
|
|
24
22
|
*
|
|
23
|
+
* @copyright Thomas Foster
|
|
25
24
|
* @module effect-forms/Coerce
|
|
26
25
|
*/
|
|
27
26
|
/** Empty string / `undefined` → `undefined`; otherwise the value (skip semantics). */
|
|
@@ -143,7 +142,7 @@ declare const createAdapter: (commandPlan: CommandPlan, overrides?: AdaptOverrid
|
|
|
143
142
|
*/
|
|
144
143
|
declare const updateAdapter: (commandPlan: CommandPlan, overrides?: AdaptOverrides) => (input: FormInputRecord) => Record<string, unknown>;
|
|
145
144
|
declare namespace Issues_d_exports {
|
|
146
|
-
export { FormIssue, FormPath, LeafFormatter, defaultLeafFormatter, filterOutputToFormIssues, schemaIssuesToFormIssues };
|
|
145
|
+
export { FormIssue, FormPath, LeafFormatter, defaultLeafFormatter, filterOutputToFormIssues$1 as filterOutputToFormIssues, schemaIssuesToFormIssues };
|
|
147
146
|
}
|
|
148
147
|
/**
|
|
149
148
|
* A path into the form's data. String segments address object keys; number
|
|
@@ -157,13 +156,18 @@ interface FormIssue {
|
|
|
157
156
|
readonly message: string;
|
|
158
157
|
}
|
|
159
158
|
/**
|
|
160
|
-
* Render one terminal issue
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
159
|
+
* Render one terminal issue into a human-readable message. The default is
|
|
160
|
+
* Effect Schema's own formatter (`SchemaIssue.makeFormatterDefault`), which
|
|
161
|
+
* renders a sentence such as `Expected string`. (An issue's `toString` is not
|
|
162
|
+
* a readable rendering in Effect 4.0.0: it prints `[object Object]`.)
|
|
163
|
+
* Override it to localise or to map specific `_tag`s to custom copy.
|
|
164
|
+
*
|
|
165
|
+
* A failed schema check reaches the formatter as the `Filter` node that
|
|
166
|
+
* wraps its leaf, because the check's own message (e.g. `Expected a value
|
|
167
|
+
* with a length of at least 2`) lives on the `Filter`, not on the leaf.
|
|
164
168
|
*/
|
|
165
169
|
type LeafFormatter = (issue: SchemaIssue.Issue) => string;
|
|
166
|
-
/** The default leaf formatter — Effect Schema's own rendering of the
|
|
170
|
+
/** The default leaf formatter — Effect Schema's own rendering of the issue. */
|
|
167
171
|
declare const defaultLeafFormatter: LeafFormatter;
|
|
168
172
|
/**
|
|
169
173
|
* Walk an Effect Schema issue tree, emitting one {@link FormIssue} per leaf
|
|
@@ -180,7 +184,7 @@ declare function schemaIssuesToFormIssues(issue: SchemaIssue.Issue, format?: Lea
|
|
|
180
184
|
* ({@link module:effect-forms/Validate}) so both fold into the same nested
|
|
181
185
|
* error tree as schema-decode failures.
|
|
182
186
|
*/
|
|
183
|
-
declare function filterOutputToFormIssues(output: Schema.FilterOutput): FormIssue[];
|
|
187
|
+
declare function filterOutputToFormIssues$1(output: Schema.FilterOutput): FormIssue[];
|
|
184
188
|
declare namespace Constraints_d_exports {
|
|
185
189
|
export { InputConstraint, InputConstraints, constraintAt, constraintsFromSchema, isLeafConstraint };
|
|
186
190
|
}
|
|
@@ -204,6 +208,19 @@ declare const isLeafConstraint: (value: InputConstraint | InputConstraints) => v
|
|
|
204
208
|
* Derive the {@link InputConstraints} for a struct schema. Non-struct schemas
|
|
205
209
|
* yield an empty tree. `overrides` (deep-merged over the derived tree, and
|
|
206
210
|
* winning on conflict) is the seam for anything the AST walk does not infer.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* import * as Schema from "effect/Schema"
|
|
215
|
+
* import { Constraints } from "@thomasfosterau/effect-forms"
|
|
216
|
+
*
|
|
217
|
+
* const Signup = Schema.Struct({
|
|
218
|
+
* name: Schema.String.check(Schema.isMinLength(2)),
|
|
219
|
+
* nickname: Schema.optional(Schema.String),
|
|
220
|
+
* })
|
|
221
|
+
* console.log(Constraints.constraintsFromSchema(Signup))
|
|
222
|
+
* // { name: { required: true, minlength: 2 }, nickname: { required: false } }
|
|
223
|
+
* ```
|
|
207
224
|
*/
|
|
208
225
|
declare function constraintsFromSchema(schema: Schema.Top, overrides?: InputConstraints): InputConstraints;
|
|
209
226
|
/**
|
|
@@ -230,6 +247,16 @@ declare function getAt(obj: unknown, path: FormPath): unknown;
|
|
|
230
247
|
* arrays along the path are cloned (structural sharing elsewhere); missing
|
|
231
248
|
* containers are created as arrays for numeric segments and objects otherwise.
|
|
232
249
|
* The empty path returns `value`. Pure — `obj` is never mutated.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* import { Path } from "@thomasfosterau/effect-forms"
|
|
254
|
+
*
|
|
255
|
+
* const before = { authors: [{ name: "Ada" }] }
|
|
256
|
+
* const after = Path.setAt(before, ["authors", 0, "name"], "Grace")
|
|
257
|
+
* console.log(Path.getAt(after, ["authors", 0, "name"])) // "Grace"
|
|
258
|
+
* console.log(before.authors[0]?.name) // "Ada" — the input is untouched
|
|
259
|
+
* ```
|
|
233
260
|
*/
|
|
234
261
|
declare function setAt<T>(obj: T, path: FormPath, value: unknown): T;
|
|
235
262
|
/** Whether `path` is `root` or nested beneath it. */
|
|
@@ -521,6 +548,14 @@ declare const empty: FormBuilder<Record<never, never>, never>;
|
|
|
521
548
|
declare function buildSchema<TFields extends FieldsRecord, R>(self: FormBuilder<TFields, R>): Schema.Codec<DecodedFromFields<TFields>, EncodedFromFields<TFields>, R>;
|
|
522
549
|
/** The asynchronous refinements carried on a builder, as bare predicates. */
|
|
523
550
|
declare function asyncRefinements<TFields extends FieldsRecord, R>(self: FormBuilder<TFields, R>): ReadonlyArray<(values: DecodedFromFields<TFields>) => Effect.Effect<Schema.FilterOutput, never, R>>;
|
|
551
|
+
/**
|
|
552
|
+
* Normalise a filter predicate's `Schema.FilterOutput` into a flat list of
|
|
553
|
+
* {@link FormIssue}s (empty on success).
|
|
554
|
+
*
|
|
555
|
+
* @deprecated Use `Issues.filterOutputToFormIssues`; this re-export remains
|
|
556
|
+
* only for callers that imported it from `FormBuilder`.
|
|
557
|
+
*/
|
|
558
|
+
declare const filterOutputToFormIssues: typeof filterOutputToFormIssues$1;
|
|
524
559
|
/**
|
|
525
560
|
* Run every asynchronous refinement on `self` against `values` and collect
|
|
526
561
|
* their failures as {@link FormIssue}s (empty when all pass). Runs them
|
|
@@ -555,11 +590,17 @@ interface FormState<A, Message = unknown> {
|
|
|
555
590
|
}
|
|
556
591
|
/** Options for {@link make}. */
|
|
557
592
|
interface MakeFormStateOptions<Message> {
|
|
593
|
+
/** The form's id, distinguishing several forms on one page. Defaults to `"default"`. */
|
|
558
594
|
readonly id?: string;
|
|
595
|
+
/** Whether the state is valid. Defaults to `true` exactly when `errors` is empty. */
|
|
559
596
|
readonly valid?: boolean;
|
|
597
|
+
/** Whether the state comes from a submission. Defaults to `false`. */
|
|
560
598
|
readonly posted?: boolean;
|
|
599
|
+
/** The nested error tree. Defaults to no errors. */
|
|
561
600
|
readonly errors?: FormErrors;
|
|
601
|
+
/** A status message to render with the form. Omitted by default. */
|
|
562
602
|
readonly message?: Message;
|
|
603
|
+
/** Native input constraints. Omitted by default. */
|
|
563
604
|
readonly constraints?: InputConstraints;
|
|
564
605
|
}
|
|
565
606
|
/** Build a {@link FormState} from data plus overrides. `valid` defaults to "no errors". */
|
|
@@ -618,8 +659,27 @@ interface Form<S extends Schema.Top, Message = unknown> {
|
|
|
618
659
|
validate(input: FormInput): Effect.Effect<FormState<S["Type"], Message>, never, S["DecodingServices"]>;
|
|
619
660
|
}
|
|
620
661
|
/**
|
|
621
|
-
* Bind `schema` to a form
|
|
622
|
-
* {@link
|
|
662
|
+
* Bind `schema` to a form: `validate(input)` decodes a submission into a
|
|
663
|
+
* {@link FormState} (it never fails — validation failures live in the state;
|
|
664
|
+
* its requirements are the schema's decoding services) and `empty()` seeds a
|
|
665
|
+
* blank form from `defaults`. The returned {@link Form} is stateless and
|
|
666
|
+
* reusable across requests.
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
* ```ts
|
|
670
|
+
* import * as Effect from "effect/Effect"
|
|
671
|
+
* import * as Schema from "effect/Schema"
|
|
672
|
+
* import { Form } from "@thomasfosterau/effect-forms"
|
|
673
|
+
*
|
|
674
|
+
* const signup = Form.make(
|
|
675
|
+
* Schema.Struct({ name: Schema.String.check(Schema.isMinLength(2)) }),
|
|
676
|
+
* { id: "signup", defaults: { name: "" } },
|
|
677
|
+
* )
|
|
678
|
+
*
|
|
679
|
+
* const state = Effect.runSync(signup.validate(new URLSearchParams({ name: "A" })))
|
|
680
|
+
* console.log(state.valid) // false
|
|
681
|
+
* console.log(state.data) // { name: "A" } — the posted value, kept for re-render
|
|
682
|
+
* ```
|
|
623
683
|
*/
|
|
624
684
|
declare function make<S extends Schema.Top, Message = unknown>(schema: S, options: MakeFormOptions<S>): Form<S, Message>;
|
|
625
685
|
/** Options for {@link fromBuilder}. Like {@link MakeFormOptions} but `defaults` is optional. */
|
|
@@ -647,8 +707,6 @@ declare namespace FormData_d_exports {
|
|
|
647
707
|
export { FormBodySource, formDataToObject, parseFieldPath, requestToObject };
|
|
648
708
|
}
|
|
649
709
|
/**
|
|
650
|
-
* @copyright Thomas Foster
|
|
651
|
-
*
|
|
652
710
|
* Structured parsing of an HTML form body into a plain object the schema can
|
|
653
711
|
* decode.
|
|
654
712
|
*
|
|
@@ -664,6 +722,7 @@ declare namespace FormData_d_exports {
|
|
|
664
722
|
* `File` values pass through untouched; repeated bare keys collapse into an
|
|
665
723
|
* array. The parser is total and never throws.
|
|
666
724
|
*
|
|
725
|
+
* @copyright Thomas Foster
|
|
667
726
|
* @module effect-forms/FormData
|
|
668
727
|
*/
|
|
669
728
|
/** Anything that can be parsed into a structured form object. */
|
|
@@ -680,6 +739,19 @@ declare function parseFieldPath(name: string): string[];
|
|
|
680
739
|
* bracket path notation (see {@link parseFieldPath}); repeated bare keys
|
|
681
740
|
* become arrays; `File` values pass through. The result is ready to hand to
|
|
682
741
|
* `Schema.decodeUnknown` with a schema whose leaves coerce the string inputs.
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```ts
|
|
745
|
+
* import { FormData } from "@thomasfosterau/effect-forms"
|
|
746
|
+
*
|
|
747
|
+
* const body = new URLSearchParams([
|
|
748
|
+
* ["author.name", "Ada"],
|
|
749
|
+
* ["tags[]", "math"],
|
|
750
|
+
* ["tags[]", "engines"],
|
|
751
|
+
* ])
|
|
752
|
+
* console.log(FormData.formDataToObject(body))
|
|
753
|
+
* // { author: { name: "Ada" }, tags: ["math", "engines"] }
|
|
754
|
+
* ```
|
|
683
755
|
*/
|
|
684
756
|
declare function formDataToObject(source: FormBodySource): Record<string, unknown>;
|
|
685
757
|
/** Read and parse a `Request`'s form body. Errors (e.g. wrong content-type) reject. */
|