@thomasfosterau/effect-forms 0.2.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 +19 -19
- package/dist/index.d.ts +55 -4
- package/dist/index.js +494 -399
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
|
@@ -61,27 +61,27 @@ const state = await Effect.runPromise(form.validate(request));
|
|
|
61
61
|
|
|
62
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:
|
|
63
63
|
|
|
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`](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).
|
|
65
65
|
- **`empty()`** seeds a blank, unposted form from the defaults — what a `load` returns to render a create form.
|
|
66
66
|
|
|
67
67
|
## API
|
|
68
68
|
|
|
69
|
-
| Module
|
|
70
|
-
|
|
|
71
|
-
| [`Form`](src/Form.ts) | `make` — bind a schema to a form (validate + empty); `fromBuilder` — bind a `FormBuilder`. |
|
|
72
|
-
| [`FormBuilder`](src/FormBuilder.ts) | Compose a form field by field: `addField` / `merge` / `refine` / `refineEffect` / `buildSchema`. |
|
|
73
|
-
| [`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. |
|
|
74
|
-
| [`State`](src/State.ts) | `FormState`, `make`, `withMessage`, `withError`, `isValid`, `FormStateSchema` (transport). |
|
|
75
|
-
| [`Errors`](src/Errors.ts) | The nested `FormErrors` tree + `issuesToErrors` / `flattenErrors` / `getError` / `setError` / `mergeErrors` / `remapIndices`; `SourcedErrors` per-source tracking via `replaceAt` / `setSourceAt` / `clearSourceAt` / `mergeSourced`. |
|
|
76
|
-
| [`Issues`](src/Issues.ts) | `FormIssue` + the Effect Schema issue-tree walker. |
|
|
77
|
-
| [`FormData`](src/FormData.ts) | `formDataToObject` / `parseFieldPath` — structured parsing of an HTML body. |
|
|
78
|
-
| [`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. |
|
|
79
|
-
| [`Coerce`](src/Coerce.ts) | Tri-state (value / `null` / `undefined`) coercers for the form boundary. |
|
|
80
|
-
| [`Command`](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`](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`](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`](src/Result.ts) | `FormActionResult` — the framework-neutral submission outcome (success / failure / redirect / error). |
|
|
84
|
-
| [`Validate`](src/Validate.ts) | `validateValue` / `validateValueSync` — ad-hoc value/field validation; `FieldValidators` + `runSyncFieldValidator` / `runAsyncFieldValidator` — per-field sync/async validators (debounced, fiber-cancellable). |
|
|
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). |
|
|
85
85
|
|
|
86
86
|
## Notes
|
|
87
87
|
|
|
@@ -93,4 +93,4 @@ Modules are exposed as Effect-style namespaces (`Form`, `State`, `Result`, `Erro
|
|
|
93
93
|
|
|
94
94
|
## License
|
|
95
95
|
|
|
96
|
-
[MIT](LICENSE)
|
|
96
|
+
[MIT](https://github.com/thomasfosterau/effect-forms/blob/trunk/packages/forms/LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -158,10 +158,9 @@ interface FormIssue {
|
|
|
158
158
|
/**
|
|
159
159
|
* Render one terminal issue into a human-readable message. The default is
|
|
160
160
|
* Effect Schema's own formatter (`SchemaIssue.makeFormatterDefault`), which
|
|
161
|
-
* renders a sentence such as `Expected string`. (An issue's `toString` is
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* custom copy.
|
|
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.
|
|
165
164
|
*
|
|
166
165
|
* A failed schema check reaches the formatter as the `Filter` node that
|
|
167
166
|
* wraps its leaf, because the check's own message (e.g. `Expected a value
|
|
@@ -209,6 +208,19 @@ declare const isLeafConstraint: (value: InputConstraint | InputConstraints) => v
|
|
|
209
208
|
* Derive the {@link InputConstraints} for a struct schema. Non-struct schemas
|
|
210
209
|
* yield an empty tree. `overrides` (deep-merged over the derived tree, and
|
|
211
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
|
+
* ```
|
|
212
224
|
*/
|
|
213
225
|
declare function constraintsFromSchema(schema: Schema.Top, overrides?: InputConstraints): InputConstraints;
|
|
214
226
|
/**
|
|
@@ -235,6 +247,16 @@ declare function getAt(obj: unknown, path: FormPath): unknown;
|
|
|
235
247
|
* arrays along the path are cloned (structural sharing elsewhere); missing
|
|
236
248
|
* containers are created as arrays for numeric segments and objects otherwise.
|
|
237
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
|
+
* ```
|
|
238
260
|
*/
|
|
239
261
|
declare function setAt<T>(obj: T, path: FormPath, value: unknown): T;
|
|
240
262
|
/** Whether `path` is `root` or nested beneath it. */
|
|
@@ -642,6 +664,22 @@ interface Form<S extends Schema.Top, Message = unknown> {
|
|
|
642
664
|
* its requirements are the schema's decoding services) and `empty()` seeds a
|
|
643
665
|
* blank form from `defaults`. The returned {@link Form} is stateless and
|
|
644
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
|
+
* ```
|
|
645
683
|
*/
|
|
646
684
|
declare function make<S extends Schema.Top, Message = unknown>(schema: S, options: MakeFormOptions<S>): Form<S, Message>;
|
|
647
685
|
/** Options for {@link fromBuilder}. Like {@link MakeFormOptions} but `defaults` is optional. */
|
|
@@ -701,6 +739,19 @@ declare function parseFieldPath(name: string): string[];
|
|
|
701
739
|
* bracket path notation (see {@link parseFieldPath}); repeated bare keys
|
|
702
740
|
* become arrays; `File` values pass through. The result is ready to hand to
|
|
703
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
|
+
* ```
|
|
704
755
|
*/
|
|
705
756
|
declare function formDataToObject(source: FormBodySource): Record<string, unknown>;
|
|
706
757
|
/** Read and parse a `Request`'s form body. Errors (e.g. wrong content-type) reject. */
|