@thomasfosterau/effect-forms 0.2.0 → 0.3.2

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 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`](../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 | Purpose |
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 no
162
- * longer a readable rendering as of Effect rc.118: it prints
163
- * `[object Object]`.) Override it to localise or to map specific `_tag`s to
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. */
@@ -894,6 +945,5 @@ interface ResolvedFieldTrigger<A, R = never> {
894
945
  * field-trigger dispatch runs.
895
946
  */
896
947
  declare function resolveFieldTrigger<A, R = never>(validators: FieldValidators<A, R> | undefined, trigger: FieldTrigger): ResolvedFieldTrigger<A, R>;
897
- //#endregion
898
948
  export { Coerce_d_exports as Coerce, Command_d_exports as Command, Constraints_d_exports as Constraints, Errors_d_exports as Errors, Field_d_exports as Field, Form_d_exports as Form, FormBuilder_d_exports as FormBuilder, FormData_d_exports as FormData, Issues_d_exports as Issues, Mode_d_exports as Mode, Path_d_exports as Path, Result_d_exports as Result, State_d_exports as State, Validate_d_exports as Validate };
899
949
  //# sourceMappingURL=index.d.ts.map