@thomasfosterau/effect-forms-svelte 0.3.1 → 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.
@@ -1,6 +1,5 @@
1
1
  import { Errors, Issues } from "@thomasfosterau/effect-forms";
2
2
  import * as Schema from "effect/Schema";
3
- //#region src/ClientValidate.d.ts
4
3
  type FormErrors = Errors.FormErrors;
5
4
  type FormPath = Issues.FormPath;
6
5
  type LeafFormatter = Issues.LeafFormatter;
@@ -13,6 +12,5 @@ export declare function validateData<S extends Schema.Top>(schema: S, data: unkn
13
12
  * field only.
14
13
  */
15
14
  export declare function validateField<S extends Schema.Top>(schema: S, data: unknown, path: FormPath, format?: LeafFormatter): readonly string[];
16
- //#endregion
17
15
  export {};
18
16
  //# sourceMappingURL=ClientValidate.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { Errors, Validate } from "@thomasfosterau/effect-forms";
2
- //#region src/ClientValidate.ts
2
+ // src/ClientValidate.ts
3
3
  /**
4
4
  * Client-side validation helpers — validate the whole form or a single field
5
5
  * against the same Effect Schema the server uses, returning the nested
@@ -30,7 +30,6 @@ function validateField(schema, data, path, format) {
30
30
  const errors = validateData(schema, data, format);
31
31
  return getError(errors, path) ?? [];
32
32
  }
33
- //#endregion
34
33
  export { validateData, validateField };
35
34
 
36
35
  //# sourceMappingURL=ClientValidate.js.map
@@ -1,5 +1,4 @@
1
1
  import { Errors, Result } from "@thomasfosterau/effect-forms";
2
- //#region src/EnhanceCore.d.ts
3
2
  type FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;
4
3
  type FormErrors = Errors.FormErrors;
5
4
  /**
@@ -87,6 +86,5 @@ export declare function computeDataApplication<A>(params: {
87
86
  readonly resetForm: boolean;
88
87
  readonly autoSubmit: boolean;
89
88
  }): DataApplication<A>;
90
- //#endregion
91
89
  export {};
92
90
  //# sourceMappingURL=EnhanceCore.d.ts.map
@@ -3,7 +3,7 @@ import * as Arr from "effect/Array";
3
3
  import * as Match from "effect/Match";
4
4
  import * as Predicate from "effect/Predicate";
5
5
  import * as Record from "effect/Record";
6
- //#region src/EnhanceCore.ts
6
+ // src/EnhanceCore.ts
7
7
  /**
8
8
  * Pure plumbing for the `enhance` action: serialise the store's data back into
9
9
  * a form body, interpret a server response into a {@link FormActionResult}, and
@@ -184,7 +184,6 @@ function computeDataApplication(params) {
184
184
  rebaseTo: isSuccess ? appliedData : void 0
185
185
  };
186
186
  }
187
- //#endregion
188
187
  export { computeDataApplication, objectToEntries, objectToFormData, parseActionResult, resultToUpdate };
189
188
 
190
189
  //# sourceMappingURL=EnhanceCore.js.map
@@ -1,7 +1,6 @@
1
1
  import { Command } from "@thomasfosterau/effect-forms";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { Component } from "svelte";
4
- //#region src/EntityForm.d.ts
5
4
  /** A render-time message thunk — pass the message function itself, uncalled. */
6
5
  export type MessageThunk = () => string;
7
6
  /** Presentation options for a scalar text entry. */
@@ -153,5 +152,4 @@ export interface DefineEntityFormOptions<C extends CommandStructLike, U extends
153
152
  * key — a command-struct change must surface here, at definition time.
154
153
  */
155
154
  export declare const defineEntityForm: <C extends CommandStructLike, U extends CommandStructLike>(options: DefineEntityFormOptions<C, U>, build: (f: EntryBuilders<CommandKeys<C, U>>) => readonly EntityFormEntry[]) => EntityFormDefinition;
156
- //#endregion
157
155
  //# sourceMappingURL=EntityForm.d.ts.map
@@ -6,7 +6,7 @@ import * as HashMap from "effect/HashMap";
6
6
  import * as HashSet from "effect/HashSet";
7
7
  import * as Option from "effect/Option";
8
8
  import * as Schema from "effect/Schema";
9
- //#region src/EntityForm.ts
9
+ // src/EntityForm.ts
10
10
  const builders = {
11
11
  text: (key, options) => ({
12
12
  _tag: "scalar",
@@ -72,12 +72,12 @@ const defineEntityForm = (options, build) => {
72
72
  const plan = Command.plan(withoutOmitted(options.create), withoutOmitted(options.update));
73
73
  const entries = build(builders);
74
74
  const byKey = HashMap.fromIterable(Arr.map(plan.fields, (field) => [field.key, field]));
75
- const declared = Arr.reduce(entries, HashSet.empty(), (declared, entry) => {
75
+ const declared = Arr.reduce(entries, HashSet.empty(), (seen, entry) => {
76
76
  const field = HashMap.get(byKey, entry.key);
77
77
  if (Option.isNone(field)) throw new EntityFormDefinitionError({ message: `defineEntityForm: entry "${entry.key}" is not a field of either command struct` });
78
- if (HashSet.has(declared, entry.key)) throw new EntityFormDefinitionError({ message: `defineEntityForm: entry "${entry.key}" is declared twice` });
78
+ if (HashSet.has(seen, entry.key)) throw new EntityFormDefinitionError({ message: `defineEntityForm: entry "${entry.key}" is declared twice` });
79
79
  if (Predicate.isTagged(entry, "select")) validateSelectEntry(entry, field.value);
80
- return HashSet.add(declared, entry.key);
80
+ return HashSet.add(seen, entry.key);
81
81
  });
82
82
  const missing = Arr.map(Arr.filter(plan.fields, (field) => !HashSet.has(declared, field.key)), (field) => field.key);
83
83
  if (Arr.isReadonlyArrayNonEmpty(missing)) throw new EntityFormDefinitionError({ message: `defineEntityForm: command fields not declared by any entry: ${missing.join(", ")} — declare or explicitly widget them` });
@@ -97,7 +97,6 @@ const defineEntityForm = (options, build) => {
97
97
  fieldFor: (key) => Option.getOrThrowWith(HashMap.get(byKey, key), () => new EntityFormDefinitionError({ message: `EntityFormDefinition: unknown field "${key}"` }))
98
98
  };
99
99
  };
100
- //#endregion
101
100
  export { defineEntityForm };
102
101
 
103
102
  //# sourceMappingURL=EntityForm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityForm.js","names":[],"sources":["../src/EntityForm.ts"],"sourcesContent":["/**\n * The typed entity-form definition builder.\n * `defineEntityForm(commands, (f) => [...])` pairs a `Command.plan`\n * derivation (form-input schemas, blank defaults, tri-state adapters — see\n * `effect-forms/Command`) with an ordered list of typed field\n * entries: shared scalar primitives by kind, and arbitrary widget components\n * for the bespoke fields (pickers, contributor lists). One generic renderer\n * component walks `definition.entries`; per-entity code shrinks to one\n * definition module.\n *\n * Field keys are type-constrained to the command structs' keys, and the\n * builder enforces exhaustiveness at definition time (a runtime check, kept\n * deliberately simple over deep conditional types): every derivable command\n * field must be declared exactly once, so adding a field to a command struct\n * breaks the entity's definition module until the form declares it.\n *\n * Labels and other message-bearing props are **thunks** (`() => string`,\n * e.g. an i18n message function) resolved at render time, never at\n * module scope — module-scope resolution would freeze the import-time locale.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/EntityForm\n */\n\nimport type { Component } from \"svelte\";\nimport { Command } from \"@thomasfosterau/effect-forms\";\nimport * as Arr from \"effect/Array\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Record from \"effect/Record\";\nimport * as Schema from \"effect/Schema\";\n\n/** A render-time message thunk — pass the message function itself, uncalled. */\nexport type MessageThunk = () => string;\n\n/** Presentation options for a scalar text entry. */\nexport interface TextEntryOptions {\n readonly label: MessageThunk;\n /** Presentation `type` attribute (`url`, `date`). Omitted → text input. */\n readonly type?: \"text\" | \"url\" | \"date\";\n /** Keyboard hint for numeric text fields; defaults from the field's derived kind. */\n readonly inputmode?: \"numeric\";\n /** Force required-ness (defaults to the schema-derived constraint). */\n readonly required?: boolean;\n readonly placeholder?: MessageThunk;\n}\n\n/** A scalar field rendered by the shared text/textarea primitives. */\nexport interface ScalarEntry {\n readonly _tag: \"scalar\";\n readonly key: string;\n readonly multiline: boolean;\n readonly options: TextEntryOptions;\n}\n\n/** Presentation options for a `<select>` entry over a literal-union (`enum` kind) field. */\nexport interface SelectEntryOptions {\n readonly label: MessageThunk;\n readonly required?: boolean;\n /**\n * Per-option render-time label thunks, keyed by the field's derived option\n * *value* (`Command.FieldPlan.options`, read via `fieldFor` — not declared\n * here). `defineEntityForm` throws at definition time if a derived value has\n * no matching thunk.\n */\n readonly optionLabels: Readonly<Record<string, MessageThunk>>;\n}\n\n/**\n * A `<select>` field over a literal-union (`enum` kind) command field. Option\n * *values* derive from the field's plan; this entry supplies only the\n * per-option labels — `defineEntityForm` validates the field is `enum`-kind\n * and that every derived value has a label.\n */\nexport interface SelectEntry {\n readonly _tag: \"select\";\n readonly key: string;\n readonly options: SelectEntryOptions;\n}\n\n/** Options for a widget entry (picker / rows list). */\nexport interface WidgetEntryOptions {\n /** Gate the submit button while this field's value is empty (required pickers). */\n readonly gate?: boolean;\n}\n\n/**\n * A bespoke widget field. The renderer bridges the store to the widget through\n * the widget contract — `name`, `value`, `onChange`, `errors` — which\n * are **reserved keys**: the bridge is spread last, so a `props()` thunk or a\n * load-supplied field prop can never silently sever the store binding.\n */\nexport interface WidgetEntry {\n readonly _tag: \"widget\";\n readonly key: string;\n /** `\"value\"` bridges a string FK; `\"rows\"` bridges a controlled row list. */\n readonly bridge: \"value\" | \"rows\";\n // reason: the renderer composes arbitrary widgets; per-widget prop checking\n // happens in the definition module via the builder's generic signature.\n readonly component: Component<any>;\n readonly props: () => Record<string, unknown>;\n readonly options: WidgetEntryOptions;\n}\n\n/** One ordered entry in an entity form definition. */\nexport type EntityFormEntry = ScalarEntry | WidgetEntry | SelectEntry;\n\n/** The per-mode submit labels. */\nexport interface SubmitMessages {\n readonly create: MessageThunk;\n readonly update: MessageThunk;\n}\n\n/** The runtime shape `defineEntityForm` accepts for a command struct. */\nexport interface CommandStructLike {\n readonly fields: Record<string, Schema.Top>;\n}\n\n/** The value `defineEntityForm` returns — everything one renderer needs. */\nexport interface EntityFormDefinition {\n readonly plan: Command.CommandPlan;\n readonly entries: readonly EntityFormEntry[];\n readonly createInput: Schema.Top;\n readonly updateInput: Schema.Top;\n readonly createDefaults: Readonly<Record<string, string>>;\n readonly updateDefaults: Readonly<Record<string, string>>;\n readonly adaptCreate: (input: Command.FormInputRecord) => Record<string, unknown>;\n readonly adaptUpdate: (input: Command.FormInputRecord) => Record<string, unknown>;\n readonly submit: SubmitMessages;\n /** The create struct carries a client-generated id (idempotent retries). */\n readonly clientId: boolean;\n /** Entries visible in one mode, in declaration order. */\n readonly entriesFor: (mode: \"create\" | \"update\") => readonly EntityFormEntry[];\n /** The plan field behind an entry key. */\n readonly fieldFor: (key: string) => Command.FieldPlan;\n}\n\n/** The writable command keys a definition may declare (the `id` key is renderer-owned). */\nexport type CommandKeys<C extends CommandStructLike, U extends CommandStructLike> = Exclude<\n Extract<keyof C[\"fields\"] | keyof U[\"fields\"], string>,\n \"id\"\n>;\n\n/** The entry builders handed to the definition callback, key-typed to the command pair. */\nexport interface EntryBuilders<K extends string> {\n /** A scalar field rendered by the shared `TextInputField`. */\n text(key: K, options: TextEntryOptions): EntityFormEntry;\n /** A scalar field rendered by the shared `TextAreaInputField`. */\n textarea(key: K, options: TextEntryOptions): EntityFormEntry;\n /**\n * A `<select>` field over a literal-union (`enum` kind) command field.\n * Option values are derived from the field's plan; `options.optionLabels`\n * supplies each derived value's render-time label thunk.\n */\n select(key: K, options: SelectEntryOptions): EntityFormEntry;\n /**\n * A bespoke string-valued widget (an FK picker). The renderer supplies\n * `name` / `value` / `onChange` / `errors`; `props()` supplies the rest at\n * render time (labels stay thunk-resolved).\n */\n custom<P extends Record<string, unknown>>(\n key: K,\n component: Component<never>,\n props: () => P,\n options?: WidgetEntryOptions,\n ): EntityFormEntry;\n /**\n * A bespoke row-list widget (a contributor list). The renderer owns the row\n * state and supplies `name` / `value` / `onChange` / `errors`; the widget\n * owns the wire serialisation (its hidden input).\n */\n rows<P extends Record<string, unknown>>(\n key: K,\n component: Component<never>,\n props: () => P,\n options?: WidgetEntryOptions,\n ): EntityFormEntry;\n}\n\n/** Everything `defineEntityForm` needs beyond the entries. */\nexport interface DefineEntityFormOptions<C extends CommandStructLike, U extends CommandStructLike> {\n readonly create: C;\n readonly update: U;\n readonly submit: SubmitMessages;\n /** Per-field adapter overrides, per mode — the irregular-coercion escape hatch. */\n readonly adaptOverrides?: {\n readonly create?: Command.AdaptOverrides;\n readonly update?: Command.AdaptOverrides;\n };\n /** Per-field Form-Input schema overrides, per mode (e.g. an optional create title with a server default). */\n readonly inputOverrides?: {\n readonly create?: Readonly<Record<string, Schema.Top>>;\n readonly update?: Readonly<Record<string, Schema.Top>>;\n };\n /**\n * Command fields deliberately absent from the standalone form (a carve-out,\n * e.g. BookSeries' `editors` — managed through the Book form's inline-create\n * path instead). Omitted fields are stripped from the create/update field\n * records before `Command.plan` runs, so they need no entry, are never\n * classified (a kind-less omitted field can't throw at definition time),\n * and never appear in the derived input schema, defaults, or adapters.\n */\n readonly omit?: readonly CommandKeys<C, U>[];\n}\n\nconst builders: EntryBuilders<string> = {\n text: (key, options) => ({ _tag: \"scalar\", key, multiline: false, options }),\n textarea: (key, options) => ({ _tag: \"scalar\", key, multiline: true, options }),\n select: (key, options) => ({ _tag: \"select\", key, options }),\n custom: (key, component, props, options = {}) => ({\n _tag: \"widget\",\n key,\n bridge: \"value\",\n // SAFETY: `Component<never>` is the widest component type (every component\n // is assignable to it); the entry erases its props to `any` because the\n // renderer only ever mounts it with this entry's own `props()` plus the\n // widget-contract bridge props, the props the definition module chose\n // this component for.\n component: component as Component<any>,\n props,\n options,\n }),\n rows: (key, component, props, options = {}) => ({\n _tag: \"widget\",\n key,\n bridge: \"rows\",\n // SAFETY: `Component<never>` is the widest component type (every component\n // is assignable to it); the entry erases its props to `any` because the\n // renderer only ever mounts it with this entry's own `props()` plus the\n // widget-contract bridge props, the props the definition module chose\n // this component for.\n component: component as Component<any>,\n props,\n options,\n }),\n};\n\n/**\n * A definition-time error: the entries don't match the command structs. Thrown\n * synchronously from `defineEntityForm` (and `fieldFor`), never as an Effect\n * failure — a definition module that doesn't match its commands must fail at\n * import.\n */\nclass EntityFormDefinitionError extends Schema.TaggedError<\n EntityFormDefinitionError,\n { readonly brand: unique symbol }\n>()(\"EntityFormDefinitionError\", { message: Schema.String }) {}\n\n/**\n * Validate an `f.select` entry against its plan field at definition time: the\n * field must be `enum`-kind (the only kind `Command.plan` derives option\n * values for), and every derived value must have a matching `optionLabels`\n * thunk.\n */\nconst validateSelectEntry = (entry: SelectEntry, field: Command.FieldPlan): void => {\n if (field.kind !== \"enum\") {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is f.select but its command field is \"${field.kind}\"-kind, not enum`,\n });\n }\n const missingLabels = (field.options ?? []).filter(\n (value) => !Object.prototype.hasOwnProperty.call(entry.options.optionLabels, value),\n );\n if (Arr.isReadonlyArrayNonEmpty(missingLabels)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is missing optionLabels for: ${missingLabels.join(\", \")}`,\n });\n }\n};\n\n/**\n * Build an entity-form definition from a command-struct pair and an ordered\n * entry list. Runs once at module load. Throws when the entries don't cover\n * the derivable command fields exactly (exhaustiveness) or name an unknown\n * key — a command-struct change must surface here, at definition time.\n */\nexport const defineEntityForm = <C extends CommandStructLike, U extends CommandStructLike>(\n options: DefineEntityFormOptions<C, U>,\n build: (f: EntryBuilders<CommandKeys<C, U>>) => readonly EntityFormEntry[],\n): EntityFormDefinition => {\n const omitted = HashSet.fromIterable<string>(options.omit ?? []);\n const withoutOmitted = (struct: CommandStructLike): CommandStructLike =>\n HashSet.size(omitted) === 0\n ? struct\n : { fields: Record.filter(struct.fields, (_, key) => !HashSet.has(omitted, key)) };\n const plan = Command.plan(withoutOmitted(options.create), withoutOmitted(options.update));\n const entries = build(builders);\n\n const byKey = HashMap.fromIterable(Arr.map(plan.fields, (field) => [field.key, field] as const));\n const declared = Arr.reduce(entries, HashSet.empty<string>(), (declared, entry) => {\n const field = HashMap.get(byKey, entry.key);\n if (Option.isNone(field)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is not a field of either command struct`,\n });\n }\n if (HashSet.has(declared, entry.key)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is declared twice`,\n });\n }\n if (Predicate.isTagged(entry, \"select\")) validateSelectEntry(entry, field.value);\n return HashSet.add(declared, entry.key);\n });\n const missing = Arr.map(\n Arr.filter(plan.fields, (field) => !HashSet.has(declared, field.key)),\n (field) => field.key,\n );\n if (Arr.isReadonlyArrayNonEmpty(missing)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: command fields not declared by any entry: ${missing.join(\", \")} — declare or explicitly widget them`,\n });\n }\n\n const entriesFor = (mode: \"create\" | \"update\") =>\n Arr.filter(entries, (entry) =>\n Option.exists(HashMap.get(byKey, entry.key), (field) =>\n mode === \"create\" ? field.inCreate : field.inUpdate,\n ),\n );\n\n return {\n plan,\n entries,\n createInput: Schema.Struct(\n Command.formInputFields(plan, \"create\", options.inputOverrides?.create),\n ),\n updateInput: Schema.Struct(\n Command.formInputFields(plan, \"update\", options.inputOverrides?.update),\n ),\n createDefaults: Command.blankDefaults(plan, \"create\"),\n updateDefaults: Command.blankDefaults(plan, \"update\"),\n adaptCreate: Command.createAdapter(plan, options.adaptOverrides?.create),\n adaptUpdate: Command.updateAdapter(plan, options.adaptOverrides?.update),\n submit: options.submit,\n clientId: plan.clientId,\n entriesFor,\n fieldFor: (key: string) =>\n Option.getOrThrowWith(\n HashMap.get(byKey, key),\n () =>\n new EntityFormDefinitionError({\n message: `EntityFormDefinition: unknown field \"${key}\"`,\n }),\n ),\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntityForm.js","names":[],"sources":["../src/EntityForm.ts"],"sourcesContent":["/**\n * The typed entity-form definition builder.\n * `defineEntityForm(commands, (f) => [...])` pairs a `Command.plan`\n * derivation (form-input schemas, blank defaults, tri-state adapters — see\n * `effect-forms/Command`) with an ordered list of typed field\n * entries: shared scalar primitives by kind, and arbitrary widget components\n * for the bespoke fields (pickers, contributor lists). One generic renderer\n * component walks `definition.entries`; per-entity code shrinks to one\n * definition module.\n *\n * Field keys are type-constrained to the command structs' keys, and the\n * builder enforces exhaustiveness at definition time (a runtime check, kept\n * deliberately simple over deep conditional types): every derivable command\n * field must be declared exactly once, so adding a field to a command struct\n * breaks the entity's definition module until the form declares it.\n *\n * Labels and other message-bearing props are **thunks** (`() => string`,\n * e.g. an i18n message function) resolved at render time, never at\n * module scope — module-scope resolution would freeze the import-time locale.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/EntityForm\n */\n\nimport type { Component } from \"svelte\";\nimport { Command } from \"@thomasfosterau/effect-forms\";\nimport * as Arr from \"effect/Array\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Record from \"effect/Record\";\nimport * as Schema from \"effect/Schema\";\n\n/** A render-time message thunk — pass the message function itself, uncalled. */\nexport type MessageThunk = () => string;\n\n/** Presentation options for a scalar text entry. */\nexport interface TextEntryOptions {\n readonly label: MessageThunk;\n /** Presentation `type` attribute (`url`, `date`). Omitted → text input. */\n readonly type?: \"text\" | \"url\" | \"date\";\n /** Keyboard hint for numeric text fields; defaults from the field's derived kind. */\n readonly inputmode?: \"numeric\";\n /** Force required-ness (defaults to the schema-derived constraint). */\n readonly required?: boolean;\n readonly placeholder?: MessageThunk;\n}\n\n/** A scalar field rendered by the shared text/textarea primitives. */\nexport interface ScalarEntry {\n readonly _tag: \"scalar\";\n readonly key: string;\n readonly multiline: boolean;\n readonly options: TextEntryOptions;\n}\n\n/** Presentation options for a `<select>` entry over a literal-union (`enum` kind) field. */\nexport interface SelectEntryOptions {\n readonly label: MessageThunk;\n readonly required?: boolean;\n /**\n * Per-option render-time label thunks, keyed by the field's derived option\n * *value* (`Command.FieldPlan.options`, read via `fieldFor` — not declared\n * here). `defineEntityForm` throws at definition time if a derived value has\n * no matching thunk.\n */\n readonly optionLabels: Readonly<Record<string, MessageThunk>>;\n}\n\n/**\n * A `<select>` field over a literal-union (`enum` kind) command field. Option\n * *values* derive from the field's plan; this entry supplies only the\n * per-option labels — `defineEntityForm` validates the field is `enum`-kind\n * and that every derived value has a label.\n */\nexport interface SelectEntry {\n readonly _tag: \"select\";\n readonly key: string;\n readonly options: SelectEntryOptions;\n}\n\n/** Options for a widget entry (picker / rows list). */\nexport interface WidgetEntryOptions {\n /** Gate the submit button while this field's value is empty (required pickers). */\n readonly gate?: boolean;\n}\n\n/**\n * A bespoke widget field. The renderer bridges the store to the widget through\n * the widget contract — `name`, `value`, `onChange`, `errors` — which\n * are **reserved keys**: the bridge is spread last, so a `props()` thunk or a\n * load-supplied field prop can never silently sever the store binding.\n */\nexport interface WidgetEntry {\n readonly _tag: \"widget\";\n readonly key: string;\n /** `\"value\"` bridges a string FK; `\"rows\"` bridges a controlled row list. */\n readonly bridge: \"value\" | \"rows\";\n // reason: the renderer composes arbitrary widgets; per-widget prop checking\n // happens in the definition module via the builder's generic signature.\n readonly component: Component<any>;\n readonly props: () => Record<string, unknown>;\n readonly options: WidgetEntryOptions;\n}\n\n/** One ordered entry in an entity form definition. */\nexport type EntityFormEntry = ScalarEntry | WidgetEntry | SelectEntry;\n\n/** The per-mode submit labels. */\nexport interface SubmitMessages {\n readonly create: MessageThunk;\n readonly update: MessageThunk;\n}\n\n/** The runtime shape `defineEntityForm` accepts for a command struct. */\nexport interface CommandStructLike {\n readonly fields: Record<string, Schema.Top>;\n}\n\n/** The value `defineEntityForm` returns — everything one renderer needs. */\nexport interface EntityFormDefinition {\n readonly plan: Command.CommandPlan;\n readonly entries: readonly EntityFormEntry[];\n readonly createInput: Schema.Top;\n readonly updateInput: Schema.Top;\n readonly createDefaults: Readonly<Record<string, string>>;\n readonly updateDefaults: Readonly<Record<string, string>>;\n readonly adaptCreate: (input: Command.FormInputRecord) => Record<string, unknown>;\n readonly adaptUpdate: (input: Command.FormInputRecord) => Record<string, unknown>;\n readonly submit: SubmitMessages;\n /** The create struct carries a client-generated id (idempotent retries). */\n readonly clientId: boolean;\n /** Entries visible in one mode, in declaration order. */\n readonly entriesFor: (mode: \"create\" | \"update\") => readonly EntityFormEntry[];\n /** The plan field behind an entry key. */\n readonly fieldFor: (key: string) => Command.FieldPlan;\n}\n\n/** The writable command keys a definition may declare (the `id` key is renderer-owned). */\nexport type CommandKeys<C extends CommandStructLike, U extends CommandStructLike> = Exclude<\n Extract<keyof C[\"fields\"] | keyof U[\"fields\"], string>,\n \"id\"\n>;\n\n/** The entry builders handed to the definition callback, key-typed to the command pair. */\nexport interface EntryBuilders<K extends string> {\n /** A scalar field rendered by the shared `TextInputField`. */\n text(key: K, options: TextEntryOptions): EntityFormEntry;\n /** A scalar field rendered by the shared `TextAreaInputField`. */\n textarea(key: K, options: TextEntryOptions): EntityFormEntry;\n /**\n * A `<select>` field over a literal-union (`enum` kind) command field.\n * Option values are derived from the field's plan; `options.optionLabels`\n * supplies each derived value's render-time label thunk.\n */\n select(key: K, options: SelectEntryOptions): EntityFormEntry;\n /**\n * A bespoke string-valued widget (an FK picker). The renderer supplies\n * `name` / `value` / `onChange` / `errors`; `props()` supplies the rest at\n * render time (labels stay thunk-resolved).\n */\n custom<P extends Record<string, unknown>>(\n key: K,\n component: Component<never>,\n props: () => P,\n options?: WidgetEntryOptions,\n ): EntityFormEntry;\n /**\n * A bespoke row-list widget (a contributor list). The renderer owns the row\n * state and supplies `name` / `value` / `onChange` / `errors`; the widget\n * owns the wire serialisation (its hidden input).\n */\n rows<P extends Record<string, unknown>>(\n key: K,\n component: Component<never>,\n props: () => P,\n options?: WidgetEntryOptions,\n ): EntityFormEntry;\n}\n\n/** Everything `defineEntityForm` needs beyond the entries. */\nexport interface DefineEntityFormOptions<C extends CommandStructLike, U extends CommandStructLike> {\n readonly create: C;\n readonly update: U;\n readonly submit: SubmitMessages;\n /** Per-field adapter overrides, per mode — the irregular-coercion escape hatch. */\n readonly adaptOverrides?: {\n readonly create?: Command.AdaptOverrides;\n readonly update?: Command.AdaptOverrides;\n };\n /** Per-field Form-Input schema overrides, per mode (e.g. an optional create title with a server default). */\n readonly inputOverrides?: {\n readonly create?: Readonly<Record<string, Schema.Top>>;\n readonly update?: Readonly<Record<string, Schema.Top>>;\n };\n /**\n * Command fields deliberately absent from the standalone form (a carve-out,\n * e.g. BookSeries' `editors` — managed through the Book form's inline-create\n * path instead). Omitted fields are stripped from the create/update field\n * records before `Command.plan` runs, so they need no entry, are never\n * classified (a kind-less omitted field can't throw at definition time),\n * and never appear in the derived input schema, defaults, or adapters.\n */\n readonly omit?: readonly CommandKeys<C, U>[];\n}\n\nconst builders: EntryBuilders<string> = {\n text: (key, options) => ({ _tag: \"scalar\", key, multiline: false, options }),\n textarea: (key, options) => ({ _tag: \"scalar\", key, multiline: true, options }),\n select: (key, options) => ({ _tag: \"select\", key, options }),\n custom: (key, component, props, options = {}) => ({\n _tag: \"widget\",\n key,\n bridge: \"value\",\n // SAFETY: `Component<never>` is the widest component type (every component\n // is assignable to it); the entry erases its props to `any` because the\n // renderer only ever mounts it with this entry's own `props()` plus the\n // widget-contract bridge props, the props the definition module chose\n // this component for.\n component: component as Component<any>,\n props,\n options,\n }),\n rows: (key, component, props, options = {}) => ({\n _tag: \"widget\",\n key,\n bridge: \"rows\",\n // SAFETY: `Component<never>` is the widest component type (every component\n // is assignable to it); the entry erases its props to `any` because the\n // renderer only ever mounts it with this entry's own `props()` plus the\n // widget-contract bridge props, the props the definition module chose\n // this component for.\n component: component as Component<any>,\n props,\n options,\n }),\n};\n\n/**\n * A definition-time error: the entries don't match the command structs. Thrown\n * synchronously from `defineEntityForm` (and `fieldFor`), never as an Effect\n * failure — a definition module that doesn't match its commands must fail at\n * import.\n */\nclass EntityFormDefinitionError extends Schema.TaggedError<\n EntityFormDefinitionError,\n { readonly brand: unique symbol }\n>()(\"EntityFormDefinitionError\", { message: Schema.String }) {}\n\n/**\n * Validate an `f.select` entry against its plan field at definition time: the\n * field must be `enum`-kind (the only kind `Command.plan` derives option\n * values for), and every derived value must have a matching `optionLabels`\n * thunk.\n */\nconst validateSelectEntry = (entry: SelectEntry, field: Command.FieldPlan): void => {\n if (field.kind !== \"enum\") {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is f.select but its command field is \"${field.kind}\"-kind, not enum`,\n });\n }\n const missingLabels = (field.options ?? []).filter(\n (value) => !Object.prototype.hasOwnProperty.call(entry.options.optionLabels, value),\n );\n if (Arr.isReadonlyArrayNonEmpty(missingLabels)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is missing optionLabels for: ${missingLabels.join(\", \")}`,\n });\n }\n};\n\n/**\n * Build an entity-form definition from a command-struct pair and an ordered\n * entry list. Runs once at module load. Throws when the entries don't cover\n * the derivable command fields exactly (exhaustiveness) or name an unknown\n * key — a command-struct change must surface here, at definition time.\n */\nexport const defineEntityForm = <C extends CommandStructLike, U extends CommandStructLike>(\n options: DefineEntityFormOptions<C, U>,\n build: (f: EntryBuilders<CommandKeys<C, U>>) => readonly EntityFormEntry[],\n): EntityFormDefinition => {\n const omitted = HashSet.fromIterable<string>(options.omit ?? []);\n const withoutOmitted = (struct: CommandStructLike): CommandStructLike =>\n HashSet.size(omitted) === 0\n ? struct\n : { fields: Record.filter(struct.fields, (_, key) => !HashSet.has(omitted, key)) };\n const plan = Command.plan(withoutOmitted(options.create), withoutOmitted(options.update));\n const entries = build(builders);\n\n const byKey = HashMap.fromIterable(Arr.map(plan.fields, (field) => [field.key, field] as const));\n const declared = Arr.reduce(entries, HashSet.empty<string>(), (seen, entry) => {\n const field = HashMap.get(byKey, entry.key);\n if (Option.isNone(field)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is not a field of either command struct`,\n });\n }\n if (HashSet.has(seen, entry.key)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: entry \"${entry.key}\" is declared twice`,\n });\n }\n if (Predicate.isTagged(entry, \"select\")) validateSelectEntry(entry, field.value);\n return HashSet.add(seen, entry.key);\n });\n const missing = Arr.map(\n Arr.filter(plan.fields, (field) => !HashSet.has(declared, field.key)),\n (field) => field.key,\n );\n if (Arr.isReadonlyArrayNonEmpty(missing)) {\n throw new EntityFormDefinitionError({\n message: `defineEntityForm: command fields not declared by any entry: ${missing.join(\", \")} — declare or explicitly widget them`,\n });\n }\n\n const entriesFor = (mode: \"create\" | \"update\") =>\n Arr.filter(entries, (entry) =>\n Option.exists(HashMap.get(byKey, entry.key), (field) =>\n mode === \"create\" ? field.inCreate : field.inUpdate,\n ),\n );\n\n return {\n plan,\n entries,\n createInput: Schema.Struct(\n Command.formInputFields(plan, \"create\", options.inputOverrides?.create),\n ),\n updateInput: Schema.Struct(\n Command.formInputFields(plan, \"update\", options.inputOverrides?.update),\n ),\n createDefaults: Command.blankDefaults(plan, \"create\"),\n updateDefaults: Command.blankDefaults(plan, \"update\"),\n adaptCreate: Command.createAdapter(plan, options.adaptOverrides?.create),\n adaptUpdate: Command.updateAdapter(plan, options.adaptOverrides?.update),\n submit: options.submit,\n clientId: plan.clientId,\n entriesFor,\n fieldFor: (key: string) =>\n Option.getOrThrowWith(\n HashMap.get(byKey, key),\n () =>\n new EntityFormDefinitionError({\n message: `EntityFormDefinition: unknown field \"${key}\"`,\n }),\n ),\n };\n};\n"],"mappings":"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"}
@@ -4,7 +4,6 @@ import { TaintedFields } from "./Tainted.js";
4
4
  import { Constraints, Errors, Field, FormBuilder, Issues, Mode, Path, Result, State, Validate } from "@thomasfosterau/effect-forms";
5
5
  import * as Schema from "effect/Schema";
6
6
  import * as Layer from "effect/Layer";
7
- //#region src/FormStore.svelte.d.ts
8
7
  type FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;
9
8
  type FormErrors = Errors.FormErrors;
10
9
  type FormPath = Issues.FormPath;
@@ -45,7 +44,7 @@ export declare class FieldHandle<V> {
45
44
  #private;
46
45
  /** The path in flat field-name notation — use as the input's `name` attribute. */
47
46
  readonly name: string;
48
- constructor(store: FormStore<Schema.Top, object, unknown>, path: FormPath);
47
+ constructor(store: FormStore<Schema.Top, object>, path: FormPath);
49
48
  /** The field's current value. Assignable — `bind:value={field.value}` works. */
50
49
  get value(): V;
51
50
  set value(next: V);
@@ -383,6 +382,5 @@ export type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> =
383
382
  export declare function createForm<TFields extends Field.FieldsRecord, R, Message = unknown>(builder: FormBuilder.FormBuilder<TFields, R>, options?: FormBuilderOptions<TFields, R, Message>): FormStore<BuiltSchema<TFields, R>, Field.EncodedFromFields<TFields>, Message, R>;
384
383
  export declare function createForm<S extends Schema.Top, Message = unknown, R = never>(seed: FormSeed<S>, options?: FormOptions<S, S["Type"], Message, R>): FormStore<S, S["Type"], Message, R>;
385
384
  export declare function createForm<S extends Schema.Top, A = S["Type"], Message = unknown, R = never>(initial: FormState<A, Message>, options: FormOptions<S, A, Message, R>): FormStore<S, A, Message, R>;
386
- //#endregion
387
385
  export {};
388
386
  //# sourceMappingURL=FormStore.svelte.d.ts.map
@@ -14,7 +14,7 @@ import * as Layer from "effect/Layer";
14
14
  import * as ManagedRuntime from "effect/ManagedRuntime";
15
15
  import * as SchemaParser from "effect/SchemaParser";
16
16
  import { createAttachmentKey } from "svelte/attachments";
17
- //#region src/FormStore.svelte.ts
17
+ // src/FormStore.svelte.ts
18
18
  /**
19
19
  * The reactive Svelte 5 form store.
20
20
  *
@@ -1013,7 +1013,6 @@ function createForm(source, options = {}) {
1013
1013
  }
1014
1014
  return new FormStore(source, options);
1015
1015
  }
1016
- //#endregion
1017
1016
  export { FieldHandle, FormStore, createForm };
1018
1017
 
1019
1018
  //# sourceMappingURL=FormStore.svelte.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormStore.svelte.js","names":["erased"],"sources":["../src/FormStore.svelte.ts"],"sourcesContent":["/**\n * The reactive Svelte 5 form store.\n *\n * {@link createForm} binds a schema to a reactive, rune-backed {@link FormStore}.\n * Give it either `{ schema, defaults }` (it wraps the core's `Form.make` to seed\n * an empty form) or a server-produced {@link FormState} to hydrate from. The\n * store exposes mutable `data` (bind inputs to it), typed `errors`\n * (`form.errors.name`), `submitting` / `delayed` / `message` / `valid`, tainted\n * tracking, and a spreadable {@link FormStore.props} for `<form {...form.props}>`\n * that wires progressive-enhancement submission (a Svelte attachment).\n *\n * Submission is handled one of two ways: an `onSubmit` handler the page\n * provides (full client-side / JS handling, with `cancel()` and an optional\n * returned result), or — by default — a POST to the form's action (e.g. a\n * derived SvelteKit action) whose result is applied back to the store.\n *\n * ## Validation engine v2\n *\n * `errors` merges two parallel trees (see {@link Errors.SourcedErrors}):\n * `server` (the last submission's response) and `client` (browser-side\n * validation — the schema, per-field validators, a builder's `refineEffect`\n * refinements). A field's value changing clears *only that field's* server\n * error — a `State.withError` uniqueness failure the user hasn't touched\n * survives a client revalidation of some other field. `clientErrors` /\n * `serverErrors` expose the per-source trees for a UI that wants to style\n * them differently.\n *\n * `validators` (keyed by {@link Path.toString}) attach per-field sync/async\n * validators (the `FieldValidators` shape — TanStack Form's `onChange(Async)`\n * / `onBlur(Async)` vocabulary). Async validators run as Effect fibers: a new\n * value interrupts the field's in-flight fiber before forking the next run —\n * debounce + \"latest wins\" cancellation fall out of that alone (see\n * {@link Validate.runAsyncFieldValidator}). `isValidating` /\n * {@link FormStore.isValidatingAt} reflect any fiber still running; supply\n * `layer` when a validator (or a builder's `refineEffect`) needs services.\n *\n * `Mode.RevalidateMode` (`options.mode.revalidate`, default `\"onChange\"`)\n * governs what happens *after* a failed post in `onSubmit` mode: fixing a\n * field re-validates it (clearing its error) without a resubmit.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/FormStore\n */\n\n// fallow-ignore-file unused-class-member -- `FormStore` / `FieldHandle` members\n// are the published store API, read by consumers' Svelte templates and\n// scripts; nothing inside this repo is meant to call them.\nimport {\n Constraints,\n Errors,\n Field,\n Form,\n FormBuilder,\n Issues,\n Mode,\n Path,\n Result,\n State,\n Validate,\n} from \"@thomasfosterau/effect-forms\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Effect from \"effect/Effect\";\nimport * as Fiber from \"effect/Fiber\";\nimport * as Layer from \"effect/Layer\";\nimport * as ManagedRuntime from \"effect/ManagedRuntime\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as R from \"effect/Record\";\nimport type * as Schema from \"effect/Schema\";\nimport type * as SchemaIssue from \"effect/SchemaIssue\";\nimport * as SchemaParser from \"effect/SchemaParser\";\nimport { createAttachmentKey } from \"svelte/attachments\";\nimport { validateData, validateField } from \"./ClientValidate.ts\";\nimport {\n computeDataApplication,\n type FormUpdate,\n objectToFormData,\n parseActionResult,\n resultToUpdate,\n} from \"./EnhanceCore.ts\";\nimport { dispatchChange, type FieldListener, type FormListeners } from \"./Listeners.ts\";\nimport {\n computeTainted,\n isAnyTainted,\n isTaintedAt,\n taintedLeafPaths,\n type TaintedFields,\n} from \"./Tainted.ts\";\n\n// Terse local aliases onto the namespaced core exports (the public API is\n// `import { Form, State, … } from \"@thomasfosterau/effect-forms\"`).\ntype FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;\ntype FormErrors = Errors.FormErrors;\ntype FormPath = Issues.FormPath;\ntype FormIssue = Issues.FormIssue;\ntype FormState<A, Message = unknown> = State.FormState<A, Message>;\ntype TypedFormErrors<A> = Errors.TypedFormErrors<A>;\ntype InputConstraint = Constraints.InputConstraint;\ntype InputConstraints = Constraints.InputConstraints;\ntype LeafFormatter = Issues.LeafFormatter;\n/** How a form validates (and optionally auto-submits, and revalidates after a post) as the user types. */\nexport type FormMode = Mode.FormMode;\n/** A field's sync / async validators — see {@link module:effect-forms/Validate}. */\nexport type FieldValidators<A, R = never> = Validate.FieldValidators<A, R>;\nconst { clearSourceAt, emptySourced, hasErrors, mergeSourced, noErrors, replaceAt, setSourceAt } =\n Errors;\nconst { getAt, setAt } = Path;\nconst makeForm = Form.make;\n\n/** `T` with its `readonly` modifiers dropped, for building an options object field by field. */\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/** View a runtime {@link FormErrors} tree through the data-shaped {@link TypedFormErrors} lens. */\nconst typedErrors = <A>(errors: FormErrors): TypedFormErrors<A> => {\n // SAFETY: `TypedFormErrors<A>` is a compile-time projection of `FormErrors`\n // onto `A`'s field names (the same runtime tree), so no value is reinterpreted.\n return errors as never;\n};\n\n/** Forget the data-shaped typing of a {@link TypedFormErrors} tree, back to the runtime {@link FormErrors}. */\nconst untypedErrors = <A>(errors: TypedFormErrors<A>): FormErrors => {\n // SAFETY: every `TypedFormErrors<A>` this store hands out was produced by\n // `typedErrors` from a `FormErrors` tree, so the runtime value is one.\n return errors as FormErrors;\n};\n\n/** Per-field state exposed on a {@link FieldHandle}, mirroring TanStack Form's field meta. */\nexport interface FieldMeta {\n /** Changed at some point, or blurred (TanStack: `isTouched = isDirty || isBlurred`). */\n readonly isTouched: boolean;\n /** Has lost focus at least once — distinct from {@link isTouched}. */\n readonly isBlurred: boolean;\n /**\n * Was changed away from its default value at some point, via `field.value =\n * …` (or another {@link FormStore} write method). Persistent: unlike {@link\n * isDefaultValue}, this does not flip back to `false` if the value is later\n * reverted to the default — only {@link FormStore.reset} clears it.\n */\n readonly isDirty: boolean;\n /** Whether the current value is (still, or again) equal to the field's default. */\n readonly isDefaultValue: boolean;\n}\n\n/**\n * A typed, reactive handle to one field, returned by {@link FormStore.field}.\n * Bundles everything a single input needs — `bind:value={field.value}`,\n * `name={field.name}`, `onblur={field.handleBlur}`, `{...field.constraints}`,\n * `field.visibleErrors`, `field.meta` — instead of wiring each by hand against\n * an untyped {@link FormPath}. See {@link Path.FieldPath} for how the path is\n * type-checked.\n */\nexport class FieldHandle<V> {\n readonly #store: FormStore<Schema.Top, object, unknown>;\n readonly #path: FormPath;\n /** The path in flat field-name notation — use as the input's `name` attribute. */\n readonly name: string;\n\n constructor(store: FormStore<Schema.Top, object, unknown>, path: FormPath) {\n this.#store = store;\n this.#path = path;\n this.name = Path.toString(path);\n }\n\n /** The field's current value. Assignable — `bind:value={field.value}` works. */\n get value(): V {\n // SAFETY: a `FieldHandle<V>` is only constructed by `FormStore.field<P>`,\n // whose `V` is `Path.PathValue<A, P>` — the static type at `#path` in the\n // store's data `A`.\n return this.#store.valueAt(this.#path) as V;\n }\n\n set value(next: V) {\n this.#store.setValue(this.#path, next);\n }\n\n /** All messages currently attached to this field. */\n get errors(): readonly string[] {\n return this.#store.errorsAt(this.#path);\n }\n\n /** This field's messages, gated by the form's validation mode (see {@link FormStore.shouldShowError}). */\n get visibleErrors(): readonly string[] {\n return this.#store.visibleErrorsAt(this.#path);\n }\n\n /** Native validation attributes for this field — spread onto the `<input>`. */\n get constraints(): InputConstraint {\n return this.#store.constraintAt(this.#path);\n }\n\n /** This field's {@link FieldMeta}. */\n get meta(): FieldMeta {\n const isBlurred = this.#store.isTouchedAt(this.#path);\n const isDirty = this.#store.isDirtyAt(this.#path);\n return {\n isTouched: isBlurred || isDirty,\n isBlurred,\n isDirty,\n isDefaultValue: this.#store.isDefaultValueAt(this.#path),\n };\n }\n\n /** Mark this field blurred — see {@link FormStore.handleBlur}. */\n handleBlur = (): void => {\n this.#store.handleBlur(this.#path);\n };\n}\n\n/** Transport: turn a submission into a {@link FormActionResult}. Defaults to `fetch`. */\nexport type SubmitFn<A, Message> = (\n body: FormData,\n action: string,\n form: HTMLFormElement,\n) => Promise<FormActionResult<A, Message>>;\n\n/** The submission passed to an {@link FormOptions.onSubmit} handler. */\nexport interface Submission<A, Message> {\n /** The current (reactive) form data. */\n readonly data: A;\n /** The multipart body that would be posted. */\n readonly formData: FormData;\n /** The form element being submitted. */\n readonly form: HTMLFormElement;\n /**\n * The submit button that triggered this submission (its `name`/`value` is\n * already folded into {@link Submission.formData}, matching native\n * `new FormData(form, submitter)` semantics), or `null` for an auto-submit\n * (no button was clicked).\n */\n readonly submitter: HTMLElement | null;\n /** Abort the submission entirely (no network request, no result applied). */\n readonly cancel: () => void;\n /**\n * Resolve the submission with a result computed in JS (e.g. via an API\n * client), instead of letting the default network POST run. The result is\n * applied to the store exactly as a server response would be.\n */\n readonly resolve: (result: FormActionResult<A, Message>) => void;\n}\n\n/** Options for {@link createForm}. */\nexport interface FormOptions<S extends Schema.Top, A, Message, R = never> {\n /** The schema to validate against in the browser (defaults to the bound schema). */\n readonly schema?: S;\n /**\n * When the form validates as the user types (and whether it auto-submits) —\n * see {@link module:effect-forms/Mode}. Defaults to `onSubmit`. `onChange` /\n * `onBlur` auto-validate the form; use {@link FormStore.shouldShowError} /\n * {@link FormStore.visibleErrorsAt} to gate display per field.\n */\n readonly mode?: FormMode;\n /**\n * The builder's field record, set automatically when {@link createForm} is\n * given a {@link FormBuilder}. A fallback for deriving a blank array item\n * when no `schema` is bound — with a schema, {@link FormStore.append} /\n * {@link FormStore.insertItem} resolve the item (at any nesting depth) by\n * walking the schema's AST instead.\n */\n readonly fields?: Field.FieldsRecord;\n /**\n * The builder itself, set automatically when {@link createForm} is given a\n * {@link FormBuilder}. Its `refineEffect` refinements run client-side as a\n * whole-form async validator whenever {@link FormStore.validate} runs, on the\n * form data decoded through the builder's schema (as on the server); while\n * the data doesn't decode they don't run, and the field errors say why.\n */\n readonly builder?: FormBuilder.FormBuilder<Field.FieldsRecord, R>;\n /** The action URL the form posts to (defaults to the current page's action). */\n readonly action?: string;\n /** Reset `data` back to the initial values after a successful submit. */\n readonly resetForm?: boolean;\n /** Warn (via `beforeunload`) when navigating away with unsaved changes. */\n readonly taintedMessage?: string | false;\n /** Milliseconds before an in-flight submission flips `delayed` to `true`. */\n readonly delayMs?: number;\n /** Override the message rendering used by client-side validation. */\n readonly validationFormat?: LeafFormatter;\n /** Override how a submission is sent (default: `fetch` to the form's action). */\n readonly submit?: SubmitFn<A, Message>;\n /**\n * Per-field sync/async validators, keyed by {@link Path.toString} (e.g.\n * `\"author.name\"`, `\"tags[0]\"`). See {@link FormStore.setFieldValidators} to\n * register these dynamically instead (e.g. once an array item exists).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly validators?: Record<string, FieldValidators<any, R>>;\n /**\n * Services an async field validator or a builder's `refineEffect`\n * refinement needs on its `R` channel. A `ManagedRuntime` is built from\n * this once per store and reused for every client-side Effect run.\n */\n readonly layer?: Layer.Layer<R>;\n /**\n * Handle the submission entirely in JS: given the current data, return the\n * {@link FormActionResult} to apply (e.g. by calling an API client). When set,\n * this runs instead of the network POST — the \"handled using JS on the page\"\n * path. `onSubmit`'s `cancel()` / `resolve()` still take precedence.\n */\n readonly handler?: (\n data: A,\n ) => FormActionResult<A, Message> | Promise<FormActionResult<A, Message>>;\n /**\n * Handle the submission in JS on the page. Receives the data + form, can\n * `cancel()` it or `resolve(result)` it directly; if it neither cancels nor\n * resolves, the default network POST runs.\n */\n readonly onSubmit?: (input: Submission<A, Message>) => void | Promise<void>;\n /** Called with the raw {@link FormActionResult} once the response is parsed. */\n readonly onResult?: (input: { readonly result: FormActionResult<A, Message> }) => void;\n /** Called with the computed {@link FormUpdate} before it is applied. */\n readonly onUpdate?: (input: {\n readonly update: FormUpdate<A, Message>;\n readonly result: FormActionResult<A, Message>;\n }) => void;\n /** Called after the update has been applied to the store. */\n readonly onUpdated?: (input: {\n readonly form: { readonly data: A; readonly errors: FormErrors; readonly valid: boolean };\n }) => void;\n /** Called on an unexpected (non-user-fixable) error. */\n readonly onError?: (input: { readonly error: string }) => void;\n /** Called on a redirect result (default: `window.location.assign`). */\n readonly onRedirect?: (input: { readonly location: string }) => void;\n /**\n * Field- and form-level listeners (onMount / onChange / onBlur) — see\n * {@link module:effect-forms-svelte/Listeners}. Fire independent of\n * `mode`'s validation, and tear down with {@link FormStore.enhance}'s\n * `destroy`.\n */\n readonly listeners?: FormListeners;\n}\n\nconst defaultSubmit = async <A, Message>(\n body: FormData,\n action: string,\n): Promise<FormActionResult<A, Message>> => {\n const response = await fetch(action, {\n method: \"POST\",\n body,\n headers: { accept: \"application/json\" },\n });\n const json: Promise<unknown> = response.json();\n return json.then(\n (parsed) => parseActionResult<A, Message>(parsed, response.status),\n (): FormActionResult<A, Message> => ({\n _tag: \"Error\",\n status: response.status,\n message: response.statusText || \"Request failed\",\n }),\n );\n};\n\n/**\n * Erase a store's type parameters for {@link FieldHandle}, which only calls\n * its path-addressed methods.\n */\nconst eraseStore = <S extends Schema.Top, A, Message, R>(\n store: FormStore<S, A, Message, R>,\n): FormStore<Schema.Top, object, unknown> => {\n const erased: object = store;\n // SAFETY: `FieldHandle` only calls the store's path-addressed methods\n // (`valueAt`, `setValue`, `errorsAt`, `handleBlur`, …), whose signatures take\n // a `FormPath` and `unknown` values whatever `S` / `A` / `Message` / `R` are,\n // so erasing those parameters loses nothing it relies on.\n return erased as FormStore<Schema.Top, object, unknown>;\n};\n\n/** Whether `index` is a valid position in an array of `length`. */\nconst inRange = (index: number, length: number): boolean => index >= 0 && index < length;\n\n/** A field trigger a validator or the revalidate mode reacts to. */\ntype FieldTrigger = \"change\" | \"blur\";\n\n/** A reactive form, returned by {@link createForm}. */\nexport class FormStore<S extends Schema.Top, A = S[\"Type\"], Message = unknown, R = never> {\n /** The reactive form data — bind inputs to its fields. */\n // SAFETY: the constructor assigns `data` before it returns, and nothing\n // reads it earlier, so it is never observed as `undefined`.\n data: A = $state<A>() as A;\n /** The current status message, if any. */\n message: Message | undefined = $state();\n /** Native input constraints derived server-side. */\n constraints: InputConstraints = $state({});\n /** Whether a submission is in flight. */\n submitting = $state(false);\n /** Whether the in-flight submission has passed `delayMs`. */\n delayed = $state(false);\n /** Whether the current state came from a submission. */\n posted = $state(false);\n /** Whether the last validation/submit was valid. */\n valid = $state(false);\n /** How many times {@link FormStore.enhance}'s submit handler has actually attempted a submission. */\n submissionAttempts = $state(0);\n\n /** The values sent by the last submission (for revert / changed-since-submit). */\n #lastSubmitted: A | undefined = $state();\n /**\n * Per-field blurred flags, keyed by {@link Path.toString}. Drives `onBlur`\n * display (via {@link shouldShowError}) and {@link FieldMeta.isBlurred}.\n */\n #touched: Record<string, boolean> = $state({});\n /**\n * Per-field persistent dirty flags, keyed by {@link Path.toString}. Set once\n * a field's value is changed away from its default via {@link setValue} or\n * an array mutator (`append` / `removeItem` / `swap` / `move`), and never\n * cleared except by {@link reset} — unlike {@link taintedAt}, a revert to the\n * default value does not clear it. Backs {@link FieldMeta.isDirty}. Only\n * tracks writes made through those methods — mutating `data` directly\n * (`bind:value={form.data.x}`, bypassing {@link FormStore.field}) never marks\n * a field dirty.\n */\n #dirtied: Record<string, boolean> = $state({});\n /** Parallel error trees — see {@link Errors.SourcedErrors}. `errors` is their merged view. */\n #sourced: Errors.SourcedErrors = $state(emptySourced);\n /** Whether *any* validation (whole-form or per-field) has run yet — see {@link canSubmit}. */\n #hasValidated = $state(false);\n /** Per-field / whole-form (\"\") async-validation-in-flight flags, keyed by `\"<path>:<trigger>\"` or `\"\"`. */\n #validating: Record<string, boolean> = $state({});\n /** Per-key run counter guarding `#validating` writes — see {@link FormStore.#runValidatorFiber}. */\n #validatingGeneration: Record<string, number> = {};\n /** The outcome of the last completed submission, for {@link isSubmitSuccessful}. */\n #lastSubmitOutcome: \"success\" | \"failure\" | undefined = $state(undefined);\n\n readonly id: string;\n /** The tainted baseline `tainted`/`isTainted` compare against — rebased on every successful submit. */\n #initial: A;\n readonly #options: FormOptions<S, A, Message, R>;\n readonly #mode: Mode.ParsedMode;\n readonly #attachmentKey = createAttachmentKey();\n readonly #runtime: ManagedRuntime.ManagedRuntime<R, never>;\n #autoSubmitNode: HTMLFormElement | undefined;\n #validateTimer: ReturnType<typeof setTimeout> | undefined;\n /** Per-field-listener debounce timers, keyed by `\"change:<path>\"` / `\"blur:<path>\"`. */\n #listenerTimers: Record<string, ReturnType<typeof setTimeout>> = {};\n /** Per-field validators, keyed by {@link Path.toString}; seeded from `options.validators`. */\n #fieldValidators: Record<string, FieldValidators<unknown, R>>;\n /** In-flight async-validator fibers, keyed by `\"<path>:<trigger>\"` (fields) or `\"\"` (whole-form `refineEffect`). */\n #fibers: Record<string, Fiber.Fiber<unknown, never>> = {};\n /** The error paths the last `refineEffect` run set, so the next run can clear exactly those before merging fresh ones. */\n #lastRefinementPaths: FormPath[] = [];\n /**\n * The decoder that turns the form data into the builder's decoded values\n * before its `refineEffect` refinements see them — `None` unless the store\n * was seeded from a {@link FormBuilder}.\n */\n readonly #decodeForRefinements: Option.Option<\n (input: A) => Effect.Effect<Field.DecodedFromFields<Field.FieldsRecord>, SchemaIssue.Issue, R>\n >;\n /** The data snapshot the field-tracking watcher last diffed against. */\n #previousData: A;\n\n /** The current nested error tree (server errors merged with client errors), typed to the form data's shape. */\n readonly errors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(mergeSourced(this.#sourced)),\n );\n /** Client-side-sourced errors only (schema, per-field validators, `refineEffect`). */\n readonly clientErrors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(this.#sourced.client),\n );\n /** Server-sourced errors only (the last submission's response). */\n readonly serverErrors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(this.#sourced.server),\n );\n /** Whether any async field validator or `refineEffect` run is currently in flight. */\n readonly isValidating: boolean = $derived(Arr.some(R.values(this.#validating), Boolean));\n /** Whether the form has ever been submitted (attempted). */\n readonly isSubmitted: boolean = $derived(this.submissionAttempts > 0);\n /** Whether the most recently *completed* submission succeeded. `false` before any submit completes. */\n readonly isSubmitSuccessful: boolean = $derived(this.#lastSubmitOutcome === \"success\");\n /**\n * Whether the submit button should be enabled: not mid-submit, not\n * mid-validation, and either nothing has been validated yet (a pristine\n * form isn't presumptively invalid — the server gets the first look) or the\n * last validation run said valid.\n */\n readonly canSubmit: boolean = $derived(\n !this.submitting && !this.isValidating && (!this.#hasValidated || this.valid),\n );\n\n /** The tainted-field tree, re-derived as `data` changes. */\n readonly tainted: TaintedFields = $derived.by(() => computeTainted(this.#initial, this.data));\n /** Whether any field has been changed since the form was seeded. */\n readonly isTainted: boolean = $derived(isAnyTainted(this.tainted));\n /** The tree of fields changed since the last submission (empty before any submit). */\n readonly changedSinceSubmit: TaintedFields = $derived.by(() =>\n this.#lastSubmitted === undefined ? {} : computeTainted(this.#lastSubmitted, this.data),\n );\n /** Whether anything has changed since the last submission. */\n readonly hasChangedSinceSubmit: boolean = $derived(isAnyTainted(this.changedSinceSubmit));\n\n constructor(initial: FormState<A, Message>, options: FormOptions<S, A, Message, R> = {}) {\n this.id = initial.id;\n this.#initial = structuredClone(initial.data);\n this.#previousData = structuredClone(initial.data);\n this.#options = options;\n this.#mode = Mode.parse(options.mode);\n // SAFETY: `Layer.empty` is `Layer<never>`; when `options.layer` is omitted the\n // caller is asserting `R` is `never` (no validator / refineEffect\n // refinement on this store demands a service) — the same \"caller\n // asserts\" contract `validateValueSync` documents for a sync-only schema.\n this.#runtime = ManagedRuntime.make(options.layer ?? (Layer.empty as Layer.Layer<R>));\n this.#fieldValidators = { ...options.validators };\n this.#decodeForRefinements = Option.map(Option.fromUndefinedOr(options.builder), (builder) =>\n SchemaParser.decodeUnknownEffect(FormBuilder.buildSchema(builder), { errors: \"all\" }),\n );\n this.data = structuredClone(initial.data);\n this.#sourced = { client: noErrors, server: initial.errors ?? noErrors };\n this.message = initial.message;\n // The `{ schema, defaults }` overload always arrives with `constraints`\n // already derived (via the core's `Form.make`); the `FormState`-hydration\n // overload's caller may not have supplied any, so derive from\n // `options.schema` here too — the two overloads must be symmetric.\n this.constraints =\n initial.constraints ??\n (options.schema ? Constraints.constraintsFromSchema(options.schema) : {});\n this.posted = initial.posted;\n this.valid = initial.valid;\n // `canSubmit` treats \"never validated\" as presumptively valid (a pristine\n // form isn't guilty until proven so); hydrating from a `posted` state\n // means something already *has* judged it, so `valid` must be trusted\n // from the start — otherwise a server-rejected `FormState` briefly (or\n // permanently, if nothing re-triggers validation) reports `canSubmit: true`.\n this.#hasValidated = initial.posted;\n }\n\n /**\n * Spreadable attributes for the `<form>` element: `<form {...form.props}>`.\n * Sets `method=\"post\"` (and `action`, if configured) and attaches the\n * progressive-enhancement submit handler via a Svelte attachment.\n */\n get props(): Record<string | symbol, unknown> {\n const action = this.#options.action;\n return action === undefined\n ? { method: \"post\", [this.#attachmentKey]: this.#enhanceAttachment }\n : { method: \"post\", action, [this.#attachmentKey]: this.#enhanceAttachment };\n }\n\n /** Whether the field at `path` (or anything beneath it) is tainted. */\n taintedAt(path: FormPath): boolean {\n return isTaintedAt(this.tainted, path);\n }\n\n /** The messages currently attached to `path` (for dynamic / nested paths). */\n errorsAt(path: FormPath): readonly string[] {\n return Errors.getError(untypedErrors(this.errors), path) ?? [];\n }\n\n /** Whether the async validator(s) at `path` are currently running. */\n isValidatingAt(path: FormPath): boolean {\n const key = Path.toString(path);\n return this.#validating[`${key}:change`] === true || this.#validating[`${key}:blur`] === true;\n }\n\n /**\n * Register (or replace) `path`'s validators — the option-driven equivalent\n * of attaching `validators` at {@link createForm} time, for fields that\n * don't exist until later (e.g. an appended array item).\n */\n setFieldValidators(path: FormPath, validators: FieldValidators<unknown, R>): void {\n this.#fieldValidators = { ...this.#fieldValidators, [Path.toString(path)]: validators };\n }\n\n /** Re-validate the whole form against the schema (if one was supplied), plus a builder's `refineEffect` refinements. */\n validate(): boolean {\n if (this.#options.schema) {\n const clientErrors = validateData(\n this.#options.schema,\n this.data,\n this.#options.validationFormat,\n );\n this.#sourced = { ...this.#sourced, client: clientErrors };\n this.#hasValidated = true;\n this.#recomputeValid();\n }\n this.#runRefineEffect();\n return this.valid;\n }\n\n /** Validate a single field against the schema, returning that field's messages. */\n validateField(path: FormPath): readonly string[] {\n if (!this.#options.schema) return this.errorsAt(path);\n return validateField(this.#options.schema, this.data, path, this.#options.validationFormat);\n }\n\n /** Set the value at `path` (path-addressed, immutable). Bind directly to `data` otherwise. */\n setValue(path: FormPath, value: unknown): void {\n this.data = setAt(this.data, path, value);\n this.#markDirty(path);\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Read the value at `path`. */\n valueAt(path: FormPath): unknown {\n return getAt(this.data, path);\n }\n\n /** Mark the field at `path` touched (defaults to `true`) — drives `onBlur` display. */\n setTouched(path: FormPath, touched = true): void {\n this.#touched = { ...this.#touched, [Path.toString(path)]: touched };\n }\n\n /** Whether the field at `path` has been touched. */\n isTouchedAt(path: FormPath): boolean {\n return this.#touched[Path.toString(path)] === true;\n }\n\n /**\n * Once a change away from the default at `path` is seen (per the current\n * `data`, against `taintedAt`), sticks until {@link reset}. Called after\n * `data` has already been updated, by every method that writes it\n * (`setValue` and the array mutators below) — writing `data` directly\n * (`bind:value={form.data.x}`) bypasses this, same as it bypasses `tainted`\n * recomputing off anything but `data` itself would.\n */\n #markDirty(path: FormPath): void {\n const key = Path.toString(path);\n if (!this.#dirtied[key] && this.taintedAt(path)) {\n this.#dirtied = { ...this.#dirtied, [key]: true };\n }\n }\n\n /** Whether the field at `path` was ever changed away from its default — see {@link FieldMeta.isDirty}. */\n isDirtyAt(path: FormPath): boolean {\n return this.#dirtied[Path.toString(path)] === true;\n }\n\n /** Whether the field at `path` currently holds its default value — the negation of {@link taintedAt}. */\n isDefaultValueAt(path: FormPath): boolean {\n return !this.taintedAt(path);\n }\n\n /** The native validation attributes for `path` (a leaf of {@link constraints}). */\n constraintAt(path: FormPath): InputConstraint {\n return Constraints.constraintAt(this.constraints, path);\n }\n\n /**\n * A typed, reactive handle to the field at `path` — see {@link FieldHandle}.\n * `path` is type-checked against the form data's shape (see {@link\n * Path.FieldPath}); a typo'd segment or wrong-shaped path is a compile error.\n */\n field<P extends Path.FieldPath<A>>(path: P): FieldHandle<Path.PathValue<A, P>> {\n return new FieldHandle(eraseStore(this), Path.fromString(path));\n }\n\n /**\n * Handle a field losing focus: mark it touched, run its `onBlur` /\n * `onBlurAsync` validators (or, once posted, the revalidate-on-blur mode),\n * fire that field's `onBlur` listener (if any, debounced per its config),\n * and — in `onBlur` validation mode — validate the whole form\n * (auto-submitting if configured and something changed since the last\n * submit).\n */\n handleBlur(path: FormPath): void {\n this.setTouched(path, true);\n this.#onFieldBlurred(path);\n const key = Path.toString(path);\n const field = this.#options.listeners?.fields?.[key];\n if (field?.onBlur) {\n // Capture the value at blur time, not at timer-fire time — otherwise a\n // debounced listener would report whatever the field holds *now*, not\n // what it held when it was blurred.\n const value = this.valueAt(path);\n this.#scheduleListener(`blur:${key}`, field.debounce, () => field.onBlur?.({ path, value }));\n }\n if (this.#mode.validation === \"onBlur\") {\n this.validate();\n if (this.#mode.autoSubmit && this.valid && this.#hasUnsubmittedChanges()) this.#autoSubmit();\n }\n }\n\n /** Run `run` now, or after `debounce` (per-key timer, replacing any pending run for that key). */\n #scheduleListener(key: string, debounce: Duration.Input | undefined, run: () => void): void {\n if (debounce === undefined) {\n run();\n return;\n }\n clearTimeout(this.#listenerTimers[key]);\n this.#listenerTimers[key] = setTimeout(() => {\n delete this.#listenerTimers[key];\n run();\n }, Duration.toMillis(debounce));\n }\n\n /**\n * The listeners table to dispatch change events against, with each field's\n * `onChange` wrapped in this store's own debounce timer — or `undefined` if\n * nothing is configured to consume a change (an `onMount`-only config, say),\n * so the deep watcher can skip diffing the data tree entirely. Built once\n * per {@link enhance} call rather than per change, since the listener table\n * itself doesn't change across the mount's lifetime.\n */\n #buildDispatchListeners(): FormListeners | undefined {\n const listeners = this.#options.listeners;\n if (!listeners || (listeners.onChange === undefined && !listeners.fields)) return undefined;\n if (!listeners.fields) return listeners;\n const fields = R.fromEntries(\n Arr.map(R.toEntries(listeners.fields), ([key, field]): [string, FieldListener] => [\n key,\n field.onChange\n ? {\n ...field,\n onChange: (input) =>\n this.#scheduleListener(`change:${key}`, field.debounce, () =>\n field.onChange?.(input),\n ),\n }\n : field,\n ]),\n );\n return { ...listeners, fields };\n }\n\n /**\n * Whether there are edits worth auto-submitting: before any submit, any\n * change from the seeded values; afterwards, any change since the last\n * submission. Stops `autoSubmit` from re-posting server-echoed data in a loop.\n */\n #hasUnsubmittedChanges(): boolean {\n return this.#lastSubmitted === undefined ? this.isTainted : this.hasChangedSinceSubmit;\n }\n\n /**\n * Whether an error at `path` should be shown yet, per the validation mode:\n * always after a submit; in `onChange` once the field is tainted; in `onBlur`\n * once it is touched; never in `onSubmit` before posting.\n */\n shouldShowError(path: FormPath): boolean {\n if (this.posted) return true;\n if (this.#mode.validation === \"onChange\") return this.taintedAt(path);\n if (this.#mode.validation === \"onBlur\") return this.isTouchedAt(path);\n return false;\n }\n\n /** The messages at `path`, but only when {@link shouldShowError} allows it. */\n visibleErrorsAt(path: FormPath): readonly string[] {\n return this.shouldShowError(path) ? this.errorsAt(path) : [];\n }\n\n /** Read the array at `path` (empty when absent). */\n #arrayAt(path: FormPath): readonly unknown[] {\n // SAFETY: the array mutators' contract is that `path` names an array field\n // of `data` (or one not yet present, read as `[]`).\n return (getAt(this.data, path) ?? []) as readonly unknown[];\n }\n\n /** Append an item to the array at `path` (a blank item is derived when omitted). */\n append(path: FormPath, value?: unknown): void {\n const current = this.#arrayAt(path);\n const item = value !== undefined ? value : this.#defaultItem(path);\n this.data = setAt(this.data, path, [...current, item]);\n this.#markDirty(path);\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /**\n * Insert an item into the array at `path` at `index` (clamped to the\n * array's bounds; a non-finite `index` inserts at the end), shifting later\n * items — and their per-index touched flags / errors — up by one. A blank\n * item is derived when `value` is omitted.\n */\n insertItem(path: FormPath, index: number, value?: unknown): void {\n const current = this.#arrayAt(path);\n const at = Math.max(\n 0,\n Math.min(Number.isFinite(index) ? index : current.length, current.length),\n );\n const item = value !== undefined ? value : this.#defaultItem(path);\n this.data = setAt(this.data, path, [...current.slice(0, at), item, ...current.slice(at)]);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.insertAt(at));\n }\n\n /**\n * Replace the item at `index` in the array at `path` in place. Drops that\n * index's own touched flag / errors (they described the old value); other\n * indices are untouched.\n */\n replaceItem(path: FormPath, index: number, value: unknown): void {\n const current = this.#arrayAt(path);\n if (!inRange(index, current.length)) return;\n const next = [...current];\n next[index] = value;\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.replaceAt(index));\n }\n\n /** Remove every item from the array at `path`, dropping all of its per-index state. */\n clearItems(path: FormPath): void {\n this.data = setAt(this.data, path, []);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.clearAll);\n }\n\n /**\n * Remove the item at `index` from the array at `path`, shifting later\n * items' per-index touched flags / errors down by one so they keep\n * following their row.\n */\n removeItem(path: FormPath, index: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(index, current.length)) return;\n this.data = setAt(\n this.data,\n path,\n current.filter((_, i) => i !== index),\n );\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.removeAt(index));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Swap two items in the array at `path`, swapping their per-index touched flags / errors too. */\n swap(path: FormPath, indexA: number, indexB: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(indexA, current.length) || !inRange(indexB, current.length) || indexA === indexB) {\n return;\n }\n const next = [...current];\n [next[indexA], next[indexB]] = [next[indexB], next[indexA]];\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.swapAt(indexA, indexB));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Move the item at `from` to `to` within the array at `path`, carrying its per-index state along. */\n move(path: FormPath, from: number, to: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(from, current.length) || to < 0 || to >= current.length || from === to) return;\n const next = [...current];\n const [item] = next.splice(from, 1);\n next.splice(to, 0, item);\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.moveAt(from, to));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /**\n * Remap the per-index touched flags and errors under the array at `path`\n * through `map`, so state recorded against a row follows it through an\n * index-shifting mutation. See {@link Path.IndexMap}.\n */\n #remapArrayIndices(path: FormPath, map: Path.IndexMap): void {\n const sourced = this.#sourcedSnapshot();\n this.#sourced = {\n client: Errors.remapIndices(sourced.client, path, map),\n server: Errors.remapIndices(sourced.server, path, map),\n };\n this.#touched = Path.remapKeyedRecord(this.#touched, path, map);\n }\n\n /** Revert `data` to the values sent by the last submission (no-op before any submit). */\n revertToLastSubmit(): void {\n if (this.#lastSubmitted === undefined) return;\n // `#lastSubmitted` is `$state`; reading it yields a proxy `structuredClone`\n // rejects — snapshot first (same rule as `#detachedData`).\n // SAFETY: `#lastSubmitted` holds an `A`, and `$state.snapshot` of form data\n // (plain, structured-cloneable values) is a deep copy of the same shape.\n this.data = structuredClone($state.snapshot(this.#lastSubmitted)) as A;\n this.#previousData = this.#detachedData();\n this.#sourced = emptySourced;\n }\n\n /**\n * Derive a blank item for the array field at `path`, at any nesting depth\n * (e.g. `authors[0].emails`) — by walking the bound schema's AST there when\n * one is available, falling back to a top-level {@link FormOptions.fields}\n * lookup, else `\"\"`.\n */\n #defaultItem(path: FormPath) {\n if (this.#options.schema) return Field.defaultItemAt(this.#options.schema, path);\n const fields = this.#options.fields;\n if (fields && path.length === 1 && Predicate.isString(path[0])) {\n const def = fields[path[0]];\n if (def && Field.isArrayFieldDef(def)) return Field.defaultValue(def.itemSchema);\n }\n return \"\";\n }\n\n /** Submit via the enhanced node, if one is mounted and not already in flight. */\n #autoSubmit(): void {\n if (this.submitting || this.#autoSubmitNode === undefined) return;\n void this.#runSubmit(this.#autoSubmitNode);\n }\n\n /** Debounced (in `onChange`) whole-form validation, auto-submitting when valid. */\n #scheduleValidation(): void {\n const run = (): void => {\n this.validate();\n if (this.#mode.autoSubmit && this.valid && this.#hasUnsubmittedChanges()) this.#autoSubmit();\n };\n const ms = this.#mode.debounce;\n if (ms !== null && ms > 0) {\n clearTimeout(this.#validateTimer);\n this.#validateTimer = setTimeout(run, ms);\n } else {\n run();\n }\n }\n\n #recomputeValid(): void {\n this.valid = !hasErrors(mergeSourced(this.#sourced));\n }\n\n /**\n * A plain (non-`$state`-proxy) snapshot of `#sourced`. `clearSourceAt` /\n * `setSourceAt` / `replaceAt` in the framework-neutral core `structuredClone`\n * their `errors` argument — which throws `DataCloneError` on a live Svelte\n * `$state` proxy — so anywhere `#sourced` (or one of its two branches) feeds\n * one of those calls, snapshot it first.\n */\n #sourcedSnapshot(): Errors.SourcedErrors {\n // SAFETY: `#sourced` holds a `SourcedErrors`, and `$state.snapshot` of that\n // plain tree is a deep copy of the same shape.\n return $state.snapshot(this.#sourced) as Errors.SourcedErrors;\n }\n\n /** Whether {@link Mode.ParsedMode.revalidate} names this field trigger. */\n #revalidatesOn(trigger: FieldTrigger): boolean {\n if (this.#mode.revalidate === \"never\") return false;\n return this.#mode.revalidate === \"onChange\" ? trigger === \"change\" : trigger === \"blur\";\n }\n\n /** Interrupt (fire-and-forget) any fiber running under `key`, if one is in flight. */\n #interruptFiber(key: string): void {\n const previous = this.#fibers[key];\n if (previous) Effect.runFork(Fiber.interrupt(previous));\n delete this.#fibers[key];\n }\n\n /**\n * Fork `effect` under `key` as an interrupt-on-supersede fiber, toggling\n * `#validating[key]` for its duration: any fiber already running under\n * `key` is interrupted first (this — not a timer — is what gives \"latest\n * wins\"; see {@link Validate.runAsyncFieldValidator}). The old and new\n * fiber's `#validating` writes are guarded by a per-key generation counter\n * — `Effect.runFork(Fiber.interrupt(previous))` is fire-and-forget, so the\n * *old* fiber's `Effect.ensuring` finalizer can run after the *new* one has\n * already flipped `#validating[key]` back to `true`; without the guard that\n * finalizer would clear it while the new run is still genuinely in flight.\n */\n #runValidatorFiber(key: string, effect: Effect.Effect<unknown, never, R>): void {\n this.#interruptFiber(key);\n const generation = (this.#validatingGeneration[key] ?? 0) + 1;\n this.#validatingGeneration[key] = generation;\n this.#validating = { ...this.#validating, [key]: true };\n\n const program = effect.pipe(\n Effect.ensuring(\n Effect.sync(() => {\n if (this.#validatingGeneration[key] === generation) {\n this.#validating = { ...this.#validating, [key]: false };\n }\n }),\n ),\n );\n\n this.#fibers[key] = this.#runtime.runFork(program);\n }\n\n /**\n * Handle a field's value actually changing: the field's server-sourced\n * error (if any) is superseded — clear it — then run whichever validators\n * apply for the `\"change\"` trigger.\n */\n #onFieldChanged(path: FormPath): void {\n this.#sourced = clearSourceAt(this.#sourcedSnapshot(), \"server\", path);\n this.#runFieldTrigger(path, \"change\");\n this.#recomputeValid();\n }\n\n /** Handle a field losing focus: run whichever validators apply for the `\"blur\"` trigger. */\n #onFieldBlurred(path: FormPath): void {\n this.#runFieldTrigger(path, \"blur\");\n this.#recomputeValid();\n }\n\n /**\n * Run `path`'s sync + async validators for `trigger` (resolved by {@link\n * Validate.resolveFieldTrigger}). When neither is registered, falls back to\n * a whole-schema {@link validateField} re-run once the form has been\n * posted and {@link Mode.ParsedMode.revalidate} names this trigger — the\n * \"fixing a field clears its error without a resubmit\" behaviour.\n */\n #runFieldTrigger(path: FormPath, trigger: FieldTrigger): void {\n const validators = this.#fieldValidators[Path.toString(path)];\n const value = getAt(this.data, path);\n const resolved = Validate.resolveFieldTrigger(validators, trigger);\n\n if (resolved.sync) {\n this.#applySyncFieldValidator(path, resolved.sync, value);\n } else if (this.#shouldFallBackToRevalidate(trigger)) {\n this.#applyRevalidateFallback(path);\n }\n\n if (resolved.async) {\n this.#hasValidated = true;\n this.#runAsyncFieldValidator(path, trigger, resolved.async, value, resolved.asyncDebounceMs);\n }\n }\n\n /** Run a field's registered sync validator and set its client messages. */\n #applySyncFieldValidator(\n path: FormPath,\n validator: Validate.SyncFieldValidator<unknown>,\n value: unknown,\n ): void {\n this.#setClientMessagesAt(path, Validate.runSyncFieldValidator(validator, value));\n this.#hasValidated = true;\n }\n\n /** Whether, absent a registered validator, a whole-schema fallback revalidation applies for `trigger`. */\n #shouldFallBackToRevalidate(trigger: FieldTrigger): boolean {\n return this.posted && this.#revalidatesOn(trigger) && this.#options.schema !== undefined;\n }\n\n /** Re-run the whole-schema client validation and adopt just `path`'s messages. */\n #applyRevalidateFallback(path: FormPath): void {\n this.#sourced = setSourceAt(this.#sourcedSnapshot(), \"client\", path, this.validateField(path));\n this.#hasValidated = true;\n }\n\n /** Replace `path`'s client messages with `issues`' messages (folding {@link FormIssue}s at the field's own path). */\n #setClientMessagesAt(path: FormPath, issues: ReadonlyArray<FormIssue>): void {\n this.#sourced = setSourceAt(\n this.#sourcedSnapshot(),\n \"client\",\n path,\n issues.map((issue) => issue.message),\n );\n }\n\n /**\n * Run `path`'s `trigger` async validator as an interrupt-on-supersede\n * fiber (via {@link FormStore.#runValidatorFiber}) — the result (unless\n * the fiber is itself later interrupted) replaces the field's client\n * messages.\n */\n #runAsyncFieldValidator(\n path: FormPath,\n trigger: FieldTrigger,\n validator: Validate.AsyncFieldValidator<unknown, R>,\n value: unknown,\n debounceMs: number,\n ): void {\n const fiberKey = `${Path.toString(path)}:${trigger}`;\n this.#runValidatorFiber(\n fiberKey,\n Validate.runAsyncFieldValidator(validator, value, debounceMs).pipe(\n Effect.tap((issues) =>\n Effect.sync(() => {\n this.#setClientMessagesAt(path, issues);\n this.#recomputeValid();\n }),\n ),\n ),\n );\n }\n\n /**\n * Run the builder's `refineEffect` refinements (client-side, when the form\n * was seeded from a {@link FormBuilder}) as a whole-form async validator —\n * interrupt-on-supersede, same as a field's async validator (see {@link\n * FormStore.#runValidatorFiber}). The previous run's issue *paths* are\n * cleared before the fresh ones are merged in, so a superseded run can't\n * leave stale messages behind without disturbing schema-derived client\n * errors set by {@link validate} in between.\n */\n #runRefineEffect(): void {\n const builder = this.#options.builder;\n if (!builder || Arr.isReadonlyArrayEmpty(FormBuilder.asyncRefinements(builder))) return;\n\n // The refinements are typed against the builder's *decoded* values, and\n // the server (`Form.fromBuilder`) runs them on decoded data; the live form\n // holds the *encoded* values, so decode first. Values that don't decode\n // run no refinements — exactly as on the server — and the schema's own\n // field errors (from `validate`) already report why; the run's empty\n // result still clears the previous run's refinement errors.\n if (Option.isNone(this.#decodeForRefinements)) return;\n const decoded = this.#decodeForRefinements.value(this.#detachedData());\n this.#runValidatorFiber(\n \"\",\n decoded.pipe(\n Effect.matchEffect({\n onFailure: (): Effect.Effect<ReadonlyArray<Issues.FormIssue>, never, R> =>\n Effect.succeed([]),\n onSuccess: (values) => FormBuilder.runAsyncRefinements(builder, values),\n }),\n Effect.tap((issues) =>\n Effect.sync(() => {\n const cleared = Arr.reduce(\n this.#lastRefinementPaths,\n this.#sourcedSnapshot().client,\n (client, path) => replaceAt(client, path, []),\n );\n const client = Errors.mergeErrors(cleared, Errors.issuesToErrors(issues));\n this.#lastRefinementPaths = issues.map((issue) => issue.path);\n this.#sourced = { ...this.#sourced, client };\n this.#recomputeValid();\n }),\n ),\n ),\n );\n }\n\n /**\n * Reset `data`, `errors`, and `message` back to the tainted baseline. Pass\n * `values` to re-seed that baseline first (e.g. adopting a different\n * entity's values) instead of returning to the original seed.\n */\n reset(values?: A): void {\n if (values !== undefined) this.#initial = structuredClone(values);\n Arr.forEach(R.keys(this.#fibers), (key) => this.#interruptFiber(key));\n this.data = structuredClone(this.#initial);\n this.#previousData = this.#detachedData();\n this.#sourced = emptySourced;\n this.message = undefined;\n this.valid = false;\n this.posted = false;\n this.#touched = {};\n this.#dirtied = {};\n this.#lastSubmitted = undefined;\n this.#hasValidated = false;\n this.#validating = {};\n this.#lastRefinementPaths = [];\n this.submissionAttempts = 0;\n this.#lastSubmitOutcome = undefined;\n }\n\n /** A serialisable snapshot for SvelteKit's `snapshot` capture/restore. */\n capture(): FormState<A, Message> {\n return {\n id: this.id,\n valid: this.valid,\n posted: this.posted,\n data: this.#detachedData(),\n errors: untypedErrors(this.errors),\n message: this.message,\n constraints: this.constraints,\n };\n }\n\n /**\n * An independent deep copy of `data`, detached from the live `$state`\n * proxy. `structuredClone` can't clone the proxy directly (Node's\n * structured-clone algorithm rejects it); take a plain snapshot first, per\n * Svelte's own guidance for this exact case.\n */\n #detachedData(): A {\n // SAFETY: `data` holds an `A`, and `$state.snapshot` of form data (plain,\n // structured-cloneable values) is a deep copy of the same shape.\n return structuredClone($state.snapshot(this.data)) as A;\n }\n\n /** Restore from a {@link capture} snapshot (its merged tree becomes the server tree; the client tree clears). */\n restore(snapshot: FormState<A, Message>): void {\n this.data = structuredClone(snapshot.data);\n this.#previousData = this.#detachedData();\n this.#sourced = { client: noErrors, server: snapshot.errors };\n this.message = snapshot.message;\n this.valid = snapshot.valid;\n this.posted = snapshot.posted;\n this.constraints = snapshot.constraints ?? {};\n this.#hasValidated = snapshot.posted;\n }\n\n /**\n * Apply server-sourced `data` + `errors` directly, bypassing a submit\n * round-trip — for glue code that repopulates the store from a derived\n * result it received some other way (e.g.\n * {@link module:effect-forms-svelte/SvelteKit}'s `applyActionResult`, from\n * a SvelteKit form action's failure). Sets `posted` so\n * {@link shouldShowError} shows the new errors immediately; `valid` is\n * always `false` (the caller only reaches for this on a failure result).\n */\n setServerState(data: A, errors: FormErrors): void {\n this.data = data;\n this.#previousData = structuredClone(data);\n this.#sourced = { client: noErrors, server: errors };\n this.valid = false;\n this.posted = true;\n this.#hasValidated = true;\n }\n\n #applyUpdate(update: FormUpdate<A, Message>, result: FormActionResult<A, Message>): void {\n this.#options.onUpdate?.({ update, result });\n if (update.redirect !== undefined) return this.#applyRedirect(update.redirect);\n if (update.error !== undefined) return this.#applyError(update.error);\n if (update.data !== undefined) this.#applySubmittedData(update.data, result);\n\n // A submission result is authoritative: it superseded whatever the client\n // last computed, so the client tree clears and the response becomes the\n // new server tree (server errors are then cleared field-by-field as the\n // user edits / revalidates — see #onFieldChanged / #runFieldTrigger).\n this.#sourced = { client: noErrors, server: update.errors ?? noErrors };\n this.#hasValidated = true;\n this.message = update.message;\n if (update.valid !== undefined) this.valid = update.valid;\n this.#lastSubmitOutcome = this.valid ? \"success\" : \"failure\";\n this.posted = true;\n this.#options.onUpdated?.({\n form: { data: this.data, errors: untypedErrors(this.errors), valid: this.valid },\n });\n }\n\n #applyRedirect(location: string): void {\n this.#lastSubmitOutcome = \"success\";\n if (this.#options.onRedirect) this.#options.onRedirect({ location });\n else if (!Predicate.isUndefined(globalThis.window)) window.location.assign(location);\n }\n\n #applyError(error: string): void {\n this.#lastSubmitOutcome = \"failure\";\n this.#options.onError?.({ error });\n }\n\n /**\n * Adopt (or discard) the submission's echoed data via {@link\n * computeDataApplication} and rebase the tainted baseline. An\n * auto-submitting (live-save) form keeps its local data as the source of\n * truth: adopting the server's echo would discard keystrokes made during\n * the round-trip and, when the echo isn't byte-identical (a trimmed or\n * server-stamped field), retrigger the change watcher — an extra submit,\n * or an endless loop for a non-idempotent echo. On a successful submit the\n * tainted baseline still rebases to whatever `data` ends up holding, so\n * `isTainted` (and the `beforeunload` guard it drives) reflects what was\n * actually just saved, not the original seed.\n */\n #applySubmittedData(data: A, result: FormActionResult<A, Message>): void {\n // SAFETY: `#lastSubmitted` / `data` hold an `A`, and `$state.snapshot` of\n // form data (plain, structured-cloneable values) is a copy of the same shape.\n const submittedData = $state.snapshot(this.#lastSubmitted ?? this.data) as A;\n const applied = computeDataApplication({\n isSuccess: Predicate.isTagged(result, \"Success\"),\n appliedData: data,\n initial: this.#initial,\n // The snapshot actually sent (see `#runSubmit`), not the live `data` —\n // further edits may have landed while this request was in flight.\n // Detached (`$state.snapshot`) so the `structuredClone`s below never\n // receive a `$state` proxy via `applied.data` / `applied.rebaseTo`.\n submittedData,\n resetForm: this.#options.resetForm ?? false,\n autoSubmit: this.#mode.autoSubmit,\n });\n if (applied.data !== undefined) this.data = structuredClone(applied.data);\n if (applied.rebaseTo !== undefined) {\n this.#initial = structuredClone(applied.rebaseTo);\n this.#touched = {};\n }\n // Resync the field-tracking watcher's baseline to whatever `data` ends up\n // holding post-submit, so it doesn't re-diff (and re-run field validators\n // / re-clear server errors for) the submission's own echo.\n this.#previousData = this.#detachedData();\n }\n\n async #runSubmit(node: HTMLFormElement, submitter: HTMLElement | null = null): Promise<void> {\n // `new FormData(node, submitter)` folds the clicked button's name/value in,\n // matching the native no-JS POST; the auto-submit path calls this with no\n // submitter by definition, so it keeps its current (button-less) body.\n const formData = new FormData(node, submitter);\n let cancelled = false;\n let resolved: FormActionResult<A, Message> | undefined;\n await this.#options.onSubmit?.({\n data: this.data,\n formData,\n form: node,\n submitter,\n cancel: () => {\n cancelled = true;\n },\n resolve: (result) => {\n resolved = result;\n },\n });\n if (cancelled) return;\n\n // Snapshot what we're sending so `revertToLastSubmit` / `changedSinceSubmit`\n // measure against the submitted values.\n this.#lastSubmitted = this.#detachedData();\n this.submissionAttempts++;\n\n this.submitting = true;\n const delayMs = this.#options.delayMs ?? 500;\n const delayTimer = setTimeout(() => {\n this.delayed = true;\n }, delayMs);\n\n try {\n const submit = this.#options.submit ?? defaultSubmit;\n const action =\n node.action || (Predicate.isUndefined(globalThis.location) ? \"\" : location.href);\n let result: FormActionResult<A, Message>;\n if (resolved !== undefined) {\n result = resolved;\n } else if (this.#options.handler) {\n result = await this.#options.handler(this.data);\n } else {\n result = await submit(formData, action, node);\n }\n this.#options.onResult?.({ result });\n this.#applyUpdate(resultToUpdate(result), result);\n } catch (cause) {\n const message = Predicate.isError(cause) ? cause.message : \"Submission failed\";\n this.#lastSubmitOutcome = \"failure\";\n this.#options.onError?.({ error: message });\n } finally {\n clearTimeout(delayTimer);\n this.submitting = false;\n this.delayed = false;\n }\n }\n\n /**\n * Progressive-enhancement form action. Use as `<form use:form.enhance>` (or\n * spread {@link FormStore.props}). Intercepts submit, runs the `onSubmit`\n * handler, then sends the body and applies the result. Without JavaScript the\n * form posts normally.\n */\n enhance = (node: HTMLFormElement) => {\n const handleSubmit = (event: SubmitEvent): void => {\n event.preventDefault();\n void this.#runSubmit(node, event.submitter);\n };\n node.addEventListener(\"submit\", handleSubmit);\n\n const beforeUnload = (event: BeforeUnloadEvent): void => {\n if (this.#options.taintedMessage && this.isTainted) event.preventDefault();\n };\n if (this.#options.taintedMessage && !Predicate.isUndefined(globalThis.window)) {\n window.addEventListener(\"beforeunload\", beforeUnload);\n }\n\n // Remember the mounted node so `onChange` / `onBlur` auto-submit can post.\n this.#autoSubmitNode = node;\n\n this.#options.listeners?.onMount?.({ data: $state.snapshot(this.data) });\n\n // Two independent watchers over `data`, both rooted so they get explicit\n // teardown alongside the DOM listeners:\n // - the `onChange`-mode one re-validates (and maybe auto-submits) the\n // whole form, debounced by `mode.debounce`, and dispatches any\n // configured change listeners (independent of mode, so listeners fire\n // whichever mode the form validates in);\n // - the field-tracking one diffs against the last-seen snapshot and runs\n // per-field validators / clears superseded server errors for exactly\n // the paths that changed — independent of `mode.validation`, since a\n // field's own validators and the post-submit revalidate mode both\n // apply regardless of the whole-form trigger. Always on (not gated on\n // whether any validator is registered yet): `setFieldValidators` can\n // register one after mount (its documented use case — a field that\n // doesn't exist until an array item is appended), and a gate evaluated\n // only at mount time would silently never watch for it.\n const dispatchListeners = this.#buildDispatchListeners();\n let stopWatching: (() => void) | undefined;\n if (this.#mode.validation === \"onChange\" || dispatchListeners !== undefined) {\n let previous = $state.snapshot(this.data);\n stopWatching = $effect.root(() => {\n $effect(() => {\n // Establish a deep dependency on the data tree.\n const snapshot = $state.snapshot(this.data);\n if (this.#mode.validation === \"onChange\" && (this.isTainted || this.posted)) {\n this.#scheduleValidation();\n }\n if (dispatchListeners !== undefined)\n dispatchChange(previous, snapshot, dispatchListeners);\n previous = snapshot;\n });\n });\n }\n\n const stopFieldWatching = $effect.root(() => {\n $effect(() => {\n // SAFETY: `data` holds an `A`, and `$state.snapshot` of form data\n // (plain, structured-cloneable values) is a copy of the same shape.\n const snapshot = $state.snapshot(this.data) as A;\n const changed = taintedLeafPaths(computeTainted(this.#previousData, snapshot));\n this.#previousData = snapshot;\n Arr.forEach(changed, (path) => this.#onFieldChanged(path));\n });\n });\n\n return {\n destroy: () => {\n node.removeEventListener(\"submit\", handleSubmit);\n if (!Predicate.isUndefined(globalThis.window))\n window.removeEventListener(\"beforeunload\", beforeUnload);\n stopWatching?.();\n stopFieldWatching?.();\n clearTimeout(this.#validateTimer);\n Arr.forEach(R.values(this.#listenerTimers), (timer) => clearTimeout(timer));\n this.#listenerTimers = {};\n Arr.forEach(R.keys(this.#fibers), (key) => this.#interruptFiber(key));\n if (this.#autoSubmitNode === node) this.#autoSubmitNode = undefined;\n },\n };\n };\n\n /** Attachment form of {@link FormStore.enhance} for {@link FormStore.props}. */\n readonly #enhanceAttachment = (node: Element): (() => void) => {\n // SAFETY: this attachment is only reachable through `props`, which is\n // spread onto the `<form>` element (`<form {...form.props}>`).\n return this.enhance(node as HTMLFormElement).destroy;\n };\n\n /**\n * Build a {@link FormData} body from the current data (SPA / manual submit).\n *\n * Each value is encoded as `objectToFormData` encodes it: a `File` or\n * `Blob` is appended as a file and a `Date` as its UTC `YYYY-MM-DD`.\n */\n toFormData(): FormData {\n return objectToFormData(this.data);\n }\n}\n\n/** A schema plus its blank-form defaults — what {@link createForm} wraps `Form.make` over. */\nexport interface FormSeed<S extends Schema.Top> {\n readonly schema: S;\n readonly defaults: S[\"Type\"];\n readonly id?: string;\n}\n\n/** The built-schema type a {@link FormBuilder} of `TFields` / `R` collapses to. */\ntype BuiltSchema<TFields extends Field.FieldsRecord, R> = Schema.Codec<\n Field.DecodedFromFields<TFields>,\n Field.EncodedFromFields<TFields>,\n R\n>;\n\n/** Options for the {@link createForm} {@link FormBuilder} overload. */\nexport type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> = FormOptions<\n BuiltSchema<TFields, R>,\n Field.EncodedFromFields<TFields>,\n Message,\n R\n> & {\n /** Blank-form values. Defaults to a schema-derived encoded blank per field. */\n readonly defaults?: Field.EncodedFromFields<TFields>;\n /** Distinguishes multiple forms on a page. Defaults to `\"default\"`. */\n readonly id?: string;\n};\n\n/**\n * Create a reactive {@link FormStore}. Three sources are accepted:\n *\n * - a {@link FormBuilder} — the schema, blank defaults, and array-item defaults\n * are all derived from the builder (client validation, including\n * `refineEffect` refinements, is wired automatically);\n * - `{ schema, defaults }` — seed a blank form (wrapping the core's `Form.make`);\n * - a server-produced {@link FormState} — hydrate from it (schema in `options`).\n */\nexport function createForm<TFields extends Field.FieldsRecord, R, Message = unknown>(\n builder: FormBuilder.FormBuilder<TFields, R>,\n options?: FormBuilderOptions<TFields, R, Message>,\n): FormStore<BuiltSchema<TFields, R>, Field.EncodedFromFields<TFields>, Message, R>;\nexport function createForm<S extends Schema.Top, Message = unknown, R = never>(\n seed: FormSeed<S>,\n options?: FormOptions<S, S[\"Type\"], Message, R>,\n): FormStore<S, S[\"Type\"], Message, R>;\nexport function createForm<S extends Schema.Top, A = S[\"Type\"], Message = unknown, R = never>(\n initial: FormState<A, Message>,\n options: FormOptions<S, A, Message, R>,\n): FormStore<S, A, Message, R>;\nexport function createForm(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n source: FormBuilder.FormBuilder<any, any> | FormSeed<Schema.Top> | FormState<unknown, unknown>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: FormOptions<Schema.Top, any, any, any> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n defaults?: any;\n id?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): FormStore<Schema.Top, any, any, any> {\n if (FormBuilder.isFormBuilder(source)) {\n const schema: Schema.Top = FormBuilder.buildSchema(source);\n const builderOptions: Mutable<Form.FromBuilderOptions<Field.FieldsRecord>> = {};\n if (options.defaults !== undefined) builderOptions.defaults = options.defaults;\n if (options.id !== undefined) builderOptions.id = options.id;\n const form = Form.fromBuilder(source, builderOptions);\n return new FormStore(form.empty(), {\n ...options,\n schema,\n fields: source.fields,\n builder: source,\n });\n }\n if (\"defaults\" in source && \"schema\" in source) {\n const makeOptions: Mutable<Form.MakeFormOptions<Schema.Top>> = { defaults: source.defaults };\n if (source.id !== undefined) makeOptions.id = source.id;\n const state = makeForm(source.schema, makeOptions).empty();\n return new FormStore(state, { ...options, schema: source.schema });\n }\n // SAFETY: `source` is neither a builder (`isFormBuilder`) nor a seed (it\n // lacks `defaults` / `schema`), so it is the `FormState`-hydration overload's.\n return new FormStore(source as FormState<unknown, unknown>, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FormStore.svelte.js","names":["erased"],"sources":["../src/FormStore.svelte.ts"],"sourcesContent":["/**\n * The reactive Svelte 5 form store.\n *\n * {@link createForm} binds a schema to a reactive, rune-backed {@link FormStore}.\n * Give it either `{ schema, defaults }` (it wraps the core's `Form.make` to seed\n * an empty form) or a server-produced {@link FormState} to hydrate from. The\n * store exposes mutable `data` (bind inputs to it), typed `errors`\n * (`form.errors.name`), `submitting` / `delayed` / `message` / `valid`, tainted\n * tracking, and a spreadable {@link FormStore.props} for `<form {...form.props}>`\n * that wires progressive-enhancement submission (a Svelte attachment).\n *\n * Submission is handled one of two ways: an `onSubmit` handler the page\n * provides (full client-side / JS handling, with `cancel()` and an optional\n * returned result), or — by default — a POST to the form's action (e.g. a\n * derived SvelteKit action) whose result is applied back to the store.\n *\n * ## Validation engine v2\n *\n * `errors` merges two parallel trees (see {@link Errors.SourcedErrors}):\n * `server` (the last submission's response) and `client` (browser-side\n * validation — the schema, per-field validators, a builder's `refineEffect`\n * refinements). A field's value changing clears *only that field's* server\n * error — a `State.withError` uniqueness failure the user hasn't touched\n * survives a client revalidation of some other field. `clientErrors` /\n * `serverErrors` expose the per-source trees for a UI that wants to style\n * them differently.\n *\n * `validators` (keyed by {@link Path.toString}) attach per-field sync/async\n * validators (the `FieldValidators` shape — TanStack Form's `onChange(Async)`\n * / `onBlur(Async)` vocabulary). Async validators run as Effect fibers: a new\n * value interrupts the field's in-flight fiber before forking the next run —\n * debounce + \"latest wins\" cancellation fall out of that alone (see\n * {@link Validate.runAsyncFieldValidator}). `isValidating` /\n * {@link FormStore.isValidatingAt} reflect any fiber still running; supply\n * `layer` when a validator (or a builder's `refineEffect`) needs services.\n *\n * `Mode.RevalidateMode` (`options.mode.revalidate`, default `\"onChange\"`)\n * governs what happens *after* a failed post in `onSubmit` mode: fixing a\n * field re-validates it (clearing its error) without a resubmit.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/FormStore\n */\n\n// fallow-ignore-file unused-class-member -- `FormStore` / `FieldHandle` members\n// are the published store API, read by consumers' Svelte templates and\n// scripts; nothing inside this repo is meant to call them.\nimport {\n Constraints,\n Errors,\n Field,\n Form,\n FormBuilder,\n Issues,\n Mode,\n Path,\n Result,\n State,\n Validate,\n} from \"@thomasfosterau/effect-forms\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Effect from \"effect/Effect\";\nimport * as Fiber from \"effect/Fiber\";\nimport * as Layer from \"effect/Layer\";\nimport * as ManagedRuntime from \"effect/ManagedRuntime\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as R from \"effect/Record\";\nimport type * as Schema from \"effect/Schema\";\nimport type * as SchemaIssue from \"effect/SchemaIssue\";\nimport * as SchemaParser from \"effect/SchemaParser\";\nimport { createAttachmentKey } from \"svelte/attachments\";\nimport { validateData, validateField } from \"./ClientValidate.ts\";\nimport {\n computeDataApplication,\n type FormUpdate,\n objectToFormData,\n parseActionResult,\n resultToUpdate,\n} from \"./EnhanceCore.ts\";\nimport { dispatchChange, type FieldListener, type FormListeners } from \"./Listeners.ts\";\nimport {\n computeTainted,\n isAnyTainted,\n isTaintedAt,\n taintedLeafPaths,\n type TaintedFields,\n} from \"./Tainted.ts\";\n\n// Terse local aliases onto the namespaced core exports (the public API is\n// `import { Form, State, … } from \"@thomasfosterau/effect-forms\"`).\ntype FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;\ntype FormErrors = Errors.FormErrors;\ntype FormPath = Issues.FormPath;\ntype FormIssue = Issues.FormIssue;\ntype FormState<A, Message = unknown> = State.FormState<A, Message>;\ntype TypedFormErrors<A> = Errors.TypedFormErrors<A>;\ntype InputConstraint = Constraints.InputConstraint;\ntype InputConstraints = Constraints.InputConstraints;\ntype LeafFormatter = Issues.LeafFormatter;\n/** How a form validates (and optionally auto-submits, and revalidates after a post) as the user types. */\nexport type FormMode = Mode.FormMode;\n/** A field's sync / async validators — see {@link module:effect-forms/Validate}. */\nexport type FieldValidators<A, R = never> = Validate.FieldValidators<A, R>;\nconst { clearSourceAt, emptySourced, hasErrors, mergeSourced, noErrors, replaceAt, setSourceAt } =\n Errors;\nconst { getAt, setAt } = Path;\nconst makeForm = Form.make;\n\n/** `T` with its `readonly` modifiers dropped, for building an options object field by field. */\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/** View a runtime {@link FormErrors} tree through the data-shaped {@link TypedFormErrors} lens. */\nconst typedErrors = <A>(errors: FormErrors): TypedFormErrors<A> => {\n // SAFETY: `TypedFormErrors<A>` is a compile-time projection of `FormErrors`\n // onto `A`'s field names (the same runtime tree), so no value is reinterpreted.\n return errors as never;\n};\n\n/** Forget the data-shaped typing of a {@link TypedFormErrors} tree, back to the runtime {@link FormErrors}. */\nconst untypedErrors = <A>(errors: TypedFormErrors<A>): FormErrors => {\n // SAFETY: every `TypedFormErrors<A>` this store hands out was produced by\n // `typedErrors` from a `FormErrors` tree, so the runtime value is one.\n return errors as FormErrors;\n};\n\n/** Per-field state exposed on a {@link FieldHandle}, mirroring TanStack Form's field meta. */\nexport interface FieldMeta {\n /** Changed at some point, or blurred (TanStack: `isTouched = isDirty || isBlurred`). */\n readonly isTouched: boolean;\n /** Has lost focus at least once — distinct from {@link isTouched}. */\n readonly isBlurred: boolean;\n /**\n * Was changed away from its default value at some point, via `field.value =\n * …` (or another {@link FormStore} write method). Persistent: unlike {@link\n * isDefaultValue}, this does not flip back to `false` if the value is later\n * reverted to the default — only {@link FormStore.reset} clears it.\n */\n readonly isDirty: boolean;\n /** Whether the current value is (still, or again) equal to the field's default. */\n readonly isDefaultValue: boolean;\n}\n\n/**\n * A typed, reactive handle to one field, returned by {@link FormStore.field}.\n * Bundles everything a single input needs — `bind:value={field.value}`,\n * `name={field.name}`, `onblur={field.handleBlur}`, `{...field.constraints}`,\n * `field.visibleErrors`, `field.meta` — instead of wiring each by hand against\n * an untyped {@link FormPath}. See {@link Path.FieldPath} for how the path is\n * type-checked.\n */\nexport class FieldHandle<V> {\n readonly #store: FormStore<Schema.Top, object>;\n readonly #path: FormPath;\n /** The path in flat field-name notation — use as the input's `name` attribute. */\n readonly name: string;\n\n constructor(store: FormStore<Schema.Top, object>, path: FormPath) {\n this.#store = store;\n this.#path = path;\n this.name = Path.toString(path);\n }\n\n /** The field's current value. Assignable — `bind:value={field.value}` works. */\n get value(): V {\n // SAFETY: a `FieldHandle<V>` is only constructed by `FormStore.field<P>`,\n // whose `V` is `Path.PathValue<A, P>` — the static type at `#path` in the\n // store's data `A`.\n return this.#store.valueAt(this.#path) as V;\n }\n\n set value(next: V) {\n this.#store.setValue(this.#path, next);\n }\n\n /** All messages currently attached to this field. */\n get errors(): readonly string[] {\n return this.#store.errorsAt(this.#path);\n }\n\n /** This field's messages, gated by the form's validation mode (see {@link FormStore.shouldShowError}). */\n get visibleErrors(): readonly string[] {\n return this.#store.visibleErrorsAt(this.#path);\n }\n\n /** Native validation attributes for this field — spread onto the `<input>`. */\n get constraints(): InputConstraint {\n return this.#store.constraintAt(this.#path);\n }\n\n /** This field's {@link FieldMeta}. */\n get meta(): FieldMeta {\n const isBlurred = this.#store.isTouchedAt(this.#path);\n const isDirty = this.#store.isDirtyAt(this.#path);\n return {\n isTouched: isBlurred || isDirty,\n isBlurred,\n isDirty,\n isDefaultValue: this.#store.isDefaultValueAt(this.#path),\n };\n }\n\n /** Mark this field blurred — see {@link FormStore.handleBlur}. */\n handleBlur = (): void => {\n this.#store.handleBlur(this.#path);\n };\n}\n\n/** Transport: turn a submission into a {@link FormActionResult}. Defaults to `fetch`. */\nexport type SubmitFn<A, Message> = (\n body: FormData,\n action: string,\n form: HTMLFormElement,\n) => Promise<FormActionResult<A, Message>>;\n\n/** The submission passed to an {@link FormOptions.onSubmit} handler. */\nexport interface Submission<A, Message> {\n /** The current (reactive) form data. */\n readonly data: A;\n /** The multipart body that would be posted. */\n readonly formData: FormData;\n /** The form element being submitted. */\n readonly form: HTMLFormElement;\n /**\n * The submit button that triggered this submission (its `name`/`value` is\n * already folded into {@link Submission.formData}, matching native\n * `new FormData(form, submitter)` semantics), or `null` for an auto-submit\n * (no button was clicked).\n */\n readonly submitter: HTMLElement | null;\n /** Abort the submission entirely (no network request, no result applied). */\n readonly cancel: () => void;\n /**\n * Resolve the submission with a result computed in JS (e.g. via an API\n * client), instead of letting the default network POST run. The result is\n * applied to the store exactly as a server response would be.\n */\n readonly resolve: (result: FormActionResult<A, Message>) => void;\n}\n\n/** Options for {@link createForm}. */\nexport interface FormOptions<S extends Schema.Top, A, Message, R = never> {\n /** The schema to validate against in the browser (defaults to the bound schema). */\n readonly schema?: S;\n /**\n * When the form validates as the user types (and whether it auto-submits) —\n * see {@link module:effect-forms/Mode}. Defaults to `onSubmit`. `onChange` /\n * `onBlur` auto-validate the form; use {@link FormStore.shouldShowError} /\n * {@link FormStore.visibleErrorsAt} to gate display per field.\n */\n readonly mode?: FormMode;\n /**\n * The builder's field record, set automatically when {@link createForm} is\n * given a {@link FormBuilder}. A fallback for deriving a blank array item\n * when no `schema` is bound — with a schema, {@link FormStore.append} /\n * {@link FormStore.insertItem} resolve the item (at any nesting depth) by\n * walking the schema's AST instead.\n */\n readonly fields?: Field.FieldsRecord;\n /**\n * The builder itself, set automatically when {@link createForm} is given a\n * {@link FormBuilder}. Its `refineEffect` refinements run client-side as a\n * whole-form async validator whenever {@link FormStore.validate} runs, on the\n * form data decoded through the builder's schema (as on the server); while\n * the data doesn't decode they don't run, and the field errors say why.\n */\n readonly builder?: FormBuilder.FormBuilder<Field.FieldsRecord, R>;\n /** The action URL the form posts to (defaults to the current page's action). */\n readonly action?: string;\n /** Reset `data` back to the initial values after a successful submit. */\n readonly resetForm?: boolean;\n /** Warn (via `beforeunload`) when navigating away with unsaved changes. */\n readonly taintedMessage?: string | false;\n /** Milliseconds before an in-flight submission flips `delayed` to `true`. */\n readonly delayMs?: number;\n /** Override the message rendering used by client-side validation. */\n readonly validationFormat?: LeafFormatter;\n /** Override how a submission is sent (default: `fetch` to the form's action). */\n readonly submit?: SubmitFn<A, Message>;\n /**\n * Per-field sync/async validators, keyed by {@link Path.toString} (e.g.\n * `\"author.name\"`, `\"tags[0]\"`). See {@link FormStore.setFieldValidators} to\n * register these dynamically instead (e.g. once an array item exists).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly validators?: Record<string, FieldValidators<any, R>>;\n /**\n * Services an async field validator or a builder's `refineEffect`\n * refinement needs on its `R` channel. A `ManagedRuntime` is built from\n * this once per store and reused for every client-side Effect run.\n */\n readonly layer?: Layer.Layer<R>;\n /**\n * Handle the submission entirely in JS: given the current data, return the\n * {@link FormActionResult} to apply (e.g. by calling an API client). When set,\n * this runs instead of the network POST — the \"handled using JS on the page\"\n * path. `onSubmit`'s `cancel()` / `resolve()` still take precedence.\n */\n readonly handler?: (\n data: A,\n ) => FormActionResult<A, Message> | Promise<FormActionResult<A, Message>>;\n /**\n * Handle the submission in JS on the page. Receives the data + form, can\n * `cancel()` it or `resolve(result)` it directly; if it neither cancels nor\n * resolves, the default network POST runs.\n */\n readonly onSubmit?: (input: Submission<A, Message>) => void | Promise<void>;\n /** Called with the raw {@link FormActionResult} once the response is parsed. */\n readonly onResult?: (input: { readonly result: FormActionResult<A, Message> }) => void;\n /** Called with the computed {@link FormUpdate} before it is applied. */\n readonly onUpdate?: (input: {\n readonly update: FormUpdate<A, Message>;\n readonly result: FormActionResult<A, Message>;\n }) => void;\n /** Called after the update has been applied to the store. */\n readonly onUpdated?: (input: {\n readonly form: { readonly data: A; readonly errors: FormErrors; readonly valid: boolean };\n }) => void;\n /** Called on an unexpected (non-user-fixable) error. */\n readonly onError?: (input: { readonly error: string }) => void;\n /** Called on a redirect result (default: `window.location.assign`). */\n readonly onRedirect?: (input: { readonly location: string }) => void;\n /**\n * Field- and form-level listeners (onMount / onChange / onBlur) — see\n * {@link module:effect-forms-svelte/Listeners}. Fire independent of\n * `mode`'s validation, and tear down with {@link FormStore.enhance}'s\n * `destroy`.\n */\n readonly listeners?: FormListeners;\n}\n\nconst defaultSubmit = async <A, Message>(\n body: FormData,\n action: string,\n): Promise<FormActionResult<A, Message>> => {\n const response = await fetch(action, {\n method: \"POST\",\n body,\n headers: { accept: \"application/json\" },\n });\n const json: Promise<unknown> = response.json();\n return json.then(\n (parsed) => parseActionResult<A, Message>(parsed, response.status),\n (): FormActionResult<A, Message> => ({\n _tag: \"Error\",\n status: response.status,\n message: response.statusText || \"Request failed\",\n }),\n );\n};\n\n/**\n * Erase a store's type parameters for {@link FieldHandle}, which only calls\n * its path-addressed methods.\n */\nconst eraseStore = <S extends Schema.Top, A, Message, R>(\n store: FormStore<S, A, Message, R>,\n): FormStore<Schema.Top, object> => {\n const erased: object = store;\n // SAFETY: `FieldHandle` only calls the store's path-addressed methods\n // (`valueAt`, `setValue`, `errorsAt`, `handleBlur`, …), whose signatures take\n // a `FormPath` and `unknown` values whatever `S` / `A` / `Message` / `R` are,\n // so erasing those parameters loses nothing it relies on.\n return erased as FormStore<Schema.Top, object>;\n};\n\n/** Whether `index` is a valid position in an array of `length`. */\nconst inRange = (index: number, length: number): boolean => index >= 0 && index < length;\n\n/** A field trigger a validator or the revalidate mode reacts to. */\ntype FieldTrigger = \"change\" | \"blur\";\n\n/** A reactive form, returned by {@link createForm}. */\nexport class FormStore<S extends Schema.Top, A = S[\"Type\"], Message = unknown, R = never> {\n /** The reactive form data — bind inputs to its fields. */\n // SAFETY: the constructor assigns `data` before it returns, and nothing\n // reads it earlier, so it is never observed as `undefined`.\n data: A = $state<A>() as A;\n /** The current status message, if any. */\n message: Message | undefined = $state();\n /** Native input constraints derived server-side. */\n constraints: InputConstraints = $state({});\n /** Whether a submission is in flight. */\n submitting = $state(false);\n /** Whether the in-flight submission has passed `delayMs`. */\n delayed = $state(false);\n /** Whether the current state came from a submission. */\n posted = $state(false);\n /** Whether the last validation/submit was valid. */\n valid = $state(false);\n /** How many times {@link FormStore.enhance}'s submit handler has actually attempted a submission. */\n submissionAttempts = $state(0);\n\n /** The values sent by the last submission (for revert / changed-since-submit). */\n #lastSubmitted: A | undefined = $state();\n /**\n * Per-field blurred flags, keyed by {@link Path.toString}. Drives `onBlur`\n * display (via {@link shouldShowError}) and {@link FieldMeta.isBlurred}.\n */\n #touched: Partial<Record<string, boolean>> = $state({});\n /**\n * Per-field persistent dirty flags, keyed by {@link Path.toString}. Set once\n * a field's value is changed away from its default via {@link setValue} or\n * an array mutator (`append` / `removeItem` / `swap` / `move`), and never\n * cleared except by {@link reset} — unlike {@link taintedAt}, a revert to the\n * default value does not clear it. Backs {@link FieldMeta.isDirty}. Only\n * tracks writes made through those methods — mutating `data` directly\n * (`bind:value={form.data.x}`, bypassing {@link FormStore.field}) never marks\n * a field dirty.\n */\n #dirtied: Partial<Record<string, boolean>> = $state({});\n /** Parallel error trees — see {@link Errors.SourcedErrors}. `errors` is their merged view. */\n #sourced: Errors.SourcedErrors = $state(emptySourced);\n /** Whether *any* validation (whole-form or per-field) has run yet — see {@link canSubmit}. */\n #hasValidated = $state(false);\n /** Per-field / whole-form (\"\") async-validation-in-flight flags, keyed by `\"<path>:<trigger>\"` or `\"\"`. */\n #validating: Partial<Record<string, boolean>> = $state({});\n /** Per-key run counter guarding `#validating` writes — see {@link FormStore.#runValidatorFiber}. */\n #validatingGeneration: Record<string, number> = {};\n /** The outcome of the last completed submission, for {@link isSubmitSuccessful}. */\n #lastSubmitOutcome: \"success\" | \"failure\" | undefined = $state(undefined);\n\n readonly id: string;\n /** The tainted baseline `tainted`/`isTainted` compare against — rebased on every successful submit. */\n #initial: A;\n readonly #options: FormOptions<S, A, Message, R>;\n readonly #mode: Mode.ParsedMode;\n readonly #attachmentKey = createAttachmentKey();\n readonly #runtime: ManagedRuntime.ManagedRuntime<R, never>;\n #autoSubmitNode: HTMLFormElement | undefined;\n #validateTimer: ReturnType<typeof setTimeout> | undefined;\n /** Per-field-listener debounce timers, keyed by `\"change:<path>\"` / `\"blur:<path>\"`. */\n #listenerTimers: Record<string, ReturnType<typeof setTimeout>> = {};\n /** Per-field validators, keyed by {@link Path.toString}; seeded from `options.validators`. */\n #fieldValidators: Record<string, FieldValidators<unknown, R>>;\n /** In-flight async-validator fibers, keyed by `\"<path>:<trigger>\"` (fields) or `\"\"` (whole-form `refineEffect`). */\n #fibers: Record<string, Fiber.Fiber<unknown>> = {};\n /** The error paths the last `refineEffect` run set, so the next run can clear exactly those before merging fresh ones. */\n #lastRefinementPaths: FormPath[] = [];\n /**\n * The decoder that turns the form data into the builder's decoded values\n * before its `refineEffect` refinements see them — `None` unless the store\n * was seeded from a {@link FormBuilder}.\n */\n readonly #decodeForRefinements: Option.Option<\n (input: A) => Effect.Effect<Field.DecodedFromFields<Field.FieldsRecord>, SchemaIssue.Issue, R>\n >;\n /** The data snapshot the field-tracking watcher last diffed against. */\n #previousData: A;\n\n /** The current nested error tree (server errors merged with client errors), typed to the form data's shape. */\n readonly errors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(mergeSourced(this.#sourced)),\n );\n /** Client-side-sourced errors only (schema, per-field validators, `refineEffect`). */\n readonly clientErrors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(this.#sourced.client),\n );\n /** Server-sourced errors only (the last submission's response). */\n readonly serverErrors: TypedFormErrors<A> = $derived.by(() =>\n typedErrors<A>(this.#sourced.server),\n );\n /** Whether any async field validator or `refineEffect` run is currently in flight. */\n readonly isValidating: boolean = $derived(Arr.some(R.values(this.#validating), Boolean));\n /** Whether the form has ever been submitted (attempted). */\n readonly isSubmitted: boolean = $derived(this.submissionAttempts > 0);\n /** Whether the most recently *completed* submission succeeded. `false` before any submit completes. */\n readonly isSubmitSuccessful: boolean = $derived(this.#lastSubmitOutcome === \"success\");\n /**\n * Whether the submit button should be enabled: not mid-submit, not\n * mid-validation, and either nothing has been validated yet (a pristine\n * form isn't presumptively invalid — the server gets the first look) or the\n * last validation run said valid.\n */\n readonly canSubmit: boolean = $derived(\n !this.submitting && !this.isValidating && (!this.#hasValidated || this.valid),\n );\n\n /** The tainted-field tree, re-derived as `data` changes. */\n readonly tainted: TaintedFields = $derived.by(() => computeTainted(this.#initial, this.data));\n /** Whether any field has been changed since the form was seeded. */\n readonly isTainted: boolean = $derived(isAnyTainted(this.tainted));\n /** The tree of fields changed since the last submission (empty before any submit). */\n readonly changedSinceSubmit: TaintedFields = $derived.by(() =>\n this.#lastSubmitted === undefined ? {} : computeTainted(this.#lastSubmitted, this.data),\n );\n /** Whether anything has changed since the last submission. */\n readonly hasChangedSinceSubmit: boolean = $derived(isAnyTainted(this.changedSinceSubmit));\n\n constructor(initial: FormState<A, Message>, options: FormOptions<S, A, Message, R> = {}) {\n this.id = initial.id;\n this.#initial = structuredClone(initial.data);\n this.#previousData = structuredClone(initial.data);\n this.#options = options;\n this.#mode = Mode.parse(options.mode);\n // SAFETY: `Layer.empty` is `Layer<never>`; when `options.layer` is omitted the\n // caller is asserting `R` is `never` (no validator / refineEffect\n // refinement on this store demands a service) — the same \"caller\n // asserts\" contract `validateValueSync` documents for a sync-only schema.\n this.#runtime = ManagedRuntime.make(options.layer ?? (Layer.empty as Layer.Layer<R>));\n this.#fieldValidators = { ...options.validators };\n this.#decodeForRefinements = Option.map(Option.fromUndefinedOr(options.builder), (builder) =>\n SchemaParser.decodeUnknownEffect(FormBuilder.buildSchema(builder), { errors: \"all\" }),\n );\n this.data = structuredClone(initial.data);\n this.#sourced = { client: noErrors, server: initial.errors ?? noErrors };\n this.message = initial.message;\n // The `{ schema, defaults }` overload always arrives with `constraints`\n // already derived (via the core's `Form.make`); the `FormState`-hydration\n // overload's caller may not have supplied any, so derive from\n // `options.schema` here too — the two overloads must be symmetric.\n this.constraints =\n initial.constraints ??\n (options.schema ? Constraints.constraintsFromSchema(options.schema) : {});\n this.posted = initial.posted;\n this.valid = initial.valid;\n // `canSubmit` treats \"never validated\" as presumptively valid (a pristine\n // form isn't guilty until proven so); hydrating from a `posted` state\n // means something already *has* judged it, so `valid` must be trusted\n // from the start — otherwise a server-rejected `FormState` briefly (or\n // permanently, if nothing re-triggers validation) reports `canSubmit: true`.\n this.#hasValidated = initial.posted;\n }\n\n /**\n * Spreadable attributes for the `<form>` element: `<form {...form.props}>`.\n * Sets `method=\"post\"` (and `action`, if configured) and attaches the\n * progressive-enhancement submit handler via a Svelte attachment.\n */\n get props(): Record<string | symbol, unknown> {\n const action = this.#options.action;\n return action === undefined\n ? { method: \"post\", [this.#attachmentKey]: this.#enhanceAttachment }\n : { method: \"post\", action, [this.#attachmentKey]: this.#enhanceAttachment };\n }\n\n /** Whether the field at `path` (or anything beneath it) is tainted. */\n taintedAt(path: FormPath): boolean {\n return isTaintedAt(this.tainted, path);\n }\n\n /** The messages currently attached to `path` (for dynamic / nested paths). */\n errorsAt(path: FormPath): readonly string[] {\n return Errors.getError(untypedErrors(this.errors), path) ?? [];\n }\n\n /** Whether the async validator(s) at `path` are currently running. */\n isValidatingAt(path: FormPath): boolean {\n const key = Path.toString(path);\n return this.#validating[`${key}:change`] === true || this.#validating[`${key}:blur`] === true;\n }\n\n /**\n * Register (or replace) `path`'s validators — the option-driven equivalent\n * of attaching `validators` at {@link createForm} time, for fields that\n * don't exist until later (e.g. an appended array item).\n */\n setFieldValidators(path: FormPath, validators: FieldValidators<unknown, R>): void {\n this.#fieldValidators = { ...this.#fieldValidators, [Path.toString(path)]: validators };\n }\n\n /** Re-validate the whole form against the schema (if one was supplied), plus a builder's `refineEffect` refinements. */\n validate(): boolean {\n if (this.#options.schema) {\n const clientErrors = validateData(\n this.#options.schema,\n this.data,\n this.#options.validationFormat,\n );\n this.#sourced = { ...this.#sourced, client: clientErrors };\n this.#hasValidated = true;\n this.#recomputeValid();\n }\n this.#runRefineEffect();\n return this.valid;\n }\n\n /** Validate a single field against the schema, returning that field's messages. */\n validateField(path: FormPath): readonly string[] {\n if (!this.#options.schema) return this.errorsAt(path);\n return validateField(this.#options.schema, this.data, path, this.#options.validationFormat);\n }\n\n /** Set the value at `path` (path-addressed, immutable). Bind directly to `data` otherwise. */\n setValue(path: FormPath, value: unknown): void {\n this.data = setAt(this.data, path, value);\n this.#markDirty(path);\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Read the value at `path`. */\n valueAt(path: FormPath): unknown {\n return getAt(this.data, path);\n }\n\n /** Mark the field at `path` touched (defaults to `true`) — drives `onBlur` display. */\n setTouched(path: FormPath, touched = true): void {\n this.#touched = { ...this.#touched, [Path.toString(path)]: touched };\n }\n\n /** Whether the field at `path` has been touched. */\n isTouchedAt(path: FormPath): boolean {\n return this.#touched[Path.toString(path)] === true;\n }\n\n /**\n * Once a change away from the default at `path` is seen (per the current\n * `data`, against `taintedAt`), sticks until {@link reset}. Called after\n * `data` has already been updated, by every method that writes it\n * (`setValue` and the array mutators below) — writing `data` directly\n * (`bind:value={form.data.x}`) bypasses this, same as it bypasses `tainted`\n * recomputing off anything but `data` itself would.\n */\n #markDirty(path: FormPath): void {\n const key = Path.toString(path);\n if (!this.#dirtied[key] && this.taintedAt(path)) {\n this.#dirtied = { ...this.#dirtied, [key]: true };\n }\n }\n\n /** Whether the field at `path` was ever changed away from its default — see {@link FieldMeta.isDirty}. */\n isDirtyAt(path: FormPath): boolean {\n return this.#dirtied[Path.toString(path)] === true;\n }\n\n /** Whether the field at `path` currently holds its default value — the negation of {@link taintedAt}. */\n isDefaultValueAt(path: FormPath): boolean {\n return !this.taintedAt(path);\n }\n\n /** The native validation attributes for `path` (a leaf of {@link constraints}). */\n constraintAt(path: FormPath): InputConstraint {\n return Constraints.constraintAt(this.constraints, path);\n }\n\n /**\n * A typed, reactive handle to the field at `path` — see {@link FieldHandle}.\n * `path` is type-checked against the form data's shape (see {@link\n * Path.FieldPath}); a typo'd segment or wrong-shaped path is a compile error.\n */\n field<P extends Path.FieldPath<A>>(path: P): FieldHandle<Path.PathValue<A, P>> {\n return new FieldHandle(eraseStore(this), Path.fromString(path));\n }\n\n /**\n * Handle a field losing focus: mark it touched, run its `onBlur` /\n * `onBlurAsync` validators (or, once posted, the revalidate-on-blur mode),\n * fire that field's `onBlur` listener (if any, debounced per its config),\n * and — in `onBlur` validation mode — validate the whole form\n * (auto-submitting if configured and something changed since the last\n * submit).\n */\n handleBlur(path: FormPath): void {\n this.setTouched(path, true);\n this.#onFieldBlurred(path);\n const key = Path.toString(path);\n const field = this.#options.listeners?.fields?.[key];\n if (field?.onBlur) {\n // Capture the value at blur time, not at timer-fire time — otherwise a\n // debounced listener would report whatever the field holds *now*, not\n // what it held when it was blurred.\n const value = this.valueAt(path);\n this.#scheduleListener(`blur:${key}`, field.debounce, () => field.onBlur?.({ path, value }));\n }\n if (this.#mode.validation === \"onBlur\") {\n this.validate();\n if (this.#mode.autoSubmit && this.valid && this.#hasUnsubmittedChanges()) this.#autoSubmit();\n }\n }\n\n /** Run `run` now, or after `debounce` (per-key timer, replacing any pending run for that key). */\n #scheduleListener(key: string, debounce: Duration.Input | undefined, run: () => void): void {\n if (debounce === undefined) {\n run();\n return;\n }\n clearTimeout(this.#listenerTimers[key]);\n this.#listenerTimers[key] = setTimeout(() => {\n delete this.#listenerTimers[key];\n run();\n }, Duration.toMillis(debounce));\n }\n\n /**\n * The listeners table to dispatch change events against, with each field's\n * `onChange` wrapped in this store's own debounce timer — or `undefined` if\n * nothing is configured to consume a change (an `onMount`-only config, say),\n * so the deep watcher can skip diffing the data tree entirely. Built once\n * per {@link enhance} call rather than per change, since the listener table\n * itself doesn't change across the mount's lifetime.\n */\n #buildDispatchListeners(): FormListeners | undefined {\n const listeners = this.#options.listeners;\n if (!listeners || (listeners.onChange === undefined && !listeners.fields)) return undefined;\n if (!listeners.fields) return listeners;\n const fields = R.fromEntries(\n Arr.map(R.toEntries(listeners.fields), ([key, field]): [string, FieldListener] => [\n key,\n field.onChange\n ? {\n ...field,\n onChange: (input) =>\n this.#scheduleListener(`change:${key}`, field.debounce, () =>\n field.onChange?.(input),\n ),\n }\n : field,\n ]),\n );\n return { ...listeners, fields };\n }\n\n /**\n * Whether there are edits worth auto-submitting: before any submit, any\n * change from the seeded values; afterwards, any change since the last\n * submission. Stops `autoSubmit` from re-posting server-echoed data in a loop.\n */\n #hasUnsubmittedChanges(): boolean {\n return this.#lastSubmitted === undefined ? this.isTainted : this.hasChangedSinceSubmit;\n }\n\n /**\n * Whether an error at `path` should be shown yet, per the validation mode:\n * always after a submit; in `onChange` once the field is tainted; in `onBlur`\n * once it is touched; never in `onSubmit` before posting.\n */\n shouldShowError(path: FormPath): boolean {\n if (this.posted) return true;\n if (this.#mode.validation === \"onChange\") return this.taintedAt(path);\n if (this.#mode.validation === \"onBlur\") return this.isTouchedAt(path);\n return false;\n }\n\n /** The messages at `path`, but only when {@link shouldShowError} allows it. */\n visibleErrorsAt(path: FormPath): readonly string[] {\n return this.shouldShowError(path) ? this.errorsAt(path) : [];\n }\n\n /** Read the array at `path` (empty when absent). */\n #arrayAt(path: FormPath): readonly unknown[] {\n // SAFETY: the array mutators' contract is that `path` names an array field\n // of `data` (or one not yet present, read as `[]`).\n return (getAt(this.data, path) ?? []) as readonly unknown[];\n }\n\n /** Append an item to the array at `path` (a blank item is derived when omitted). */\n append(path: FormPath, value?: unknown): void {\n const current = this.#arrayAt(path);\n const item = value !== undefined ? value : this.#defaultItem(path);\n this.data = setAt(this.data, path, [...current, item]);\n this.#markDirty(path);\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /**\n * Insert an item into the array at `path` at `index` (clamped to the\n * array's bounds; a non-finite `index` inserts at the end), shifting later\n * items — and their per-index touched flags / errors — up by one. A blank\n * item is derived when `value` is omitted.\n */\n insertItem(path: FormPath, index: number, value?: unknown): void {\n const current = this.#arrayAt(path);\n const at = Math.max(\n 0,\n Math.min(Number.isFinite(index) ? index : current.length, current.length),\n );\n const item = value !== undefined ? value : this.#defaultItem(path);\n this.data = setAt(this.data, path, [...current.slice(0, at), item, ...current.slice(at)]);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.insertAt(at));\n }\n\n /**\n * Replace the item at `index` in the array at `path` in place. Drops that\n * index's own touched flag / errors (they described the old value); other\n * indices are untouched.\n */\n replaceItem(path: FormPath, index: number, value: unknown): void {\n const current = this.#arrayAt(path);\n if (!inRange(index, current.length)) return;\n const next = [...current];\n next[index] = value;\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.replaceAt(index));\n }\n\n /** Remove every item from the array at `path`, dropping all of its per-index state. */\n clearItems(path: FormPath): void {\n this.data = setAt(this.data, path, []);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.clearAll);\n }\n\n /**\n * Remove the item at `index` from the array at `path`, shifting later\n * items' per-index touched flags / errors down by one so they keep\n * following their row.\n */\n removeItem(path: FormPath, index: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(index, current.length)) return;\n this.data = setAt(\n this.data,\n path,\n current.filter((_, i) => i !== index),\n );\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.removeAt(index));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Swap two items in the array at `path`, swapping their per-index touched flags / errors too. */\n swap(path: FormPath, indexA: number, indexB: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(indexA, current.length) || !inRange(indexB, current.length) || indexA === indexB) {\n return;\n }\n const next = [...current];\n [next[indexA], next[indexB]] = [next[indexB], next[indexA]];\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.swapAt(indexA, indexB));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /** Move the item at `from` to `to` within the array at `path`, carrying its per-index state along. */\n move(path: FormPath, from: number, to: number): void {\n const current = this.#arrayAt(path);\n if (!inRange(from, current.length) || to < 0 || to >= current.length || from === to) return;\n const next = [...current];\n const [item] = next.splice(from, 1);\n next.splice(to, 0, item);\n this.data = setAt(this.data, path, next);\n this.#markDirty(path);\n this.#remapArrayIndices(path, Path.moveAt(from, to));\n this.#previousData = this.#detachedData();\n this.#onFieldChanged(path);\n }\n\n /**\n * Remap the per-index touched flags and errors under the array at `path`\n * through `map`, so state recorded against a row follows it through an\n * index-shifting mutation. See {@link Path.IndexMap}.\n */\n #remapArrayIndices(path: FormPath, map: Path.IndexMap): void {\n const sourced = this.#sourcedSnapshot();\n this.#sourced = {\n client: Errors.remapIndices(sourced.client, path, map),\n server: Errors.remapIndices(sourced.server, path, map),\n };\n this.#touched = Path.remapKeyedRecord(this.#touched, path, map);\n }\n\n /** Revert `data` to the values sent by the last submission (no-op before any submit). */\n revertToLastSubmit(): void {\n if (this.#lastSubmitted === undefined) return;\n // `#lastSubmitted` is `$state`; reading it yields a proxy `structuredClone`\n // rejects — snapshot first (same rule as `#detachedData`).\n // SAFETY: `#lastSubmitted` holds an `A`, and `$state.snapshot` of form data\n // (plain, structured-cloneable values) is a deep copy of the same shape.\n this.data = structuredClone($state.snapshot(this.#lastSubmitted)) as A;\n this.#previousData = this.#detachedData();\n this.#sourced = emptySourced;\n }\n\n /**\n * Derive a blank item for the array field at `path`, at any nesting depth\n * (e.g. `authors[0].emails`) — by walking the bound schema's AST there when\n * one is available, falling back to a top-level {@link FormOptions.fields}\n * lookup, else `\"\"`.\n */\n #defaultItem(path: FormPath) {\n if (this.#options.schema) return Field.defaultItemAt(this.#options.schema, path);\n const fields = this.#options.fields;\n if (fields && path.length === 1 && Predicate.isString(path[0])) {\n const def = fields[path[0]];\n if (def && Field.isArrayFieldDef(def)) return Field.defaultValue(def.itemSchema);\n }\n return \"\";\n }\n\n /** Submit via the enhanced node, if one is mounted and not already in flight. */\n #autoSubmit(): void {\n if (this.submitting || this.#autoSubmitNode === undefined) return;\n void this.#runSubmit(this.#autoSubmitNode);\n }\n\n /** Debounced (in `onChange`) whole-form validation, auto-submitting when valid. */\n #scheduleValidation(): void {\n const run = (): void => {\n this.validate();\n if (this.#mode.autoSubmit && this.valid && this.#hasUnsubmittedChanges()) this.#autoSubmit();\n };\n const ms = this.#mode.debounce;\n if (ms !== null && ms > 0) {\n clearTimeout(this.#validateTimer);\n this.#validateTimer = setTimeout(run, ms);\n } else {\n run();\n }\n }\n\n #recomputeValid(): void {\n this.valid = !hasErrors(mergeSourced(this.#sourced));\n }\n\n /**\n * A plain (non-`$state`-proxy) snapshot of `#sourced`. `clearSourceAt` /\n * `setSourceAt` / `replaceAt` in the framework-neutral core `structuredClone`\n * their `errors` argument — which throws `DataCloneError` on a live Svelte\n * `$state` proxy — so anywhere `#sourced` (or one of its two branches) feeds\n * one of those calls, snapshot it first.\n */\n #sourcedSnapshot(): Errors.SourcedErrors {\n // `$state.snapshot` of that plain tree is a deep copy of the same shape.\n return $state.snapshot(this.#sourced);\n }\n\n /** Whether {@link Mode.ParsedMode.revalidate} names this field trigger. */\n #revalidatesOn(trigger: FieldTrigger): boolean {\n if (this.#mode.revalidate === \"never\") return false;\n return this.#mode.revalidate === \"onChange\" ? trigger === \"change\" : trigger === \"blur\";\n }\n\n /** Interrupt (fire-and-forget) any fiber running under `key`, if one is in flight. */\n #interruptFiber(key: string): void {\n const previous = this.#fibers[key];\n if (previous) Effect.runFork(Fiber.interrupt(previous));\n delete this.#fibers[key];\n }\n\n /**\n * Fork `effect` under `key` as an interrupt-on-supersede fiber, toggling\n * `#validating[key]` for its duration: any fiber already running under\n * `key` is interrupted first (this — not a timer — is what gives \"latest\n * wins\"; see {@link Validate.runAsyncFieldValidator}). The old and new\n * fiber's `#validating` writes are guarded by a per-key generation counter\n * — `Effect.runFork(Fiber.interrupt(previous))` is fire-and-forget, so the\n * *old* fiber's `Effect.ensuring` finalizer can run after the *new* one has\n * already flipped `#validating[key]` back to `true`; without the guard that\n * finalizer would clear it while the new run is still genuinely in flight.\n */\n #runValidatorFiber(key: string, effect: Effect.Effect<unknown, never, R>): void {\n this.#interruptFiber(key);\n const generation = (this.#validatingGeneration[key] ?? 0) + 1;\n this.#validatingGeneration[key] = generation;\n this.#validating = { ...this.#validating, [key]: true };\n\n const program = effect.pipe(\n Effect.ensuring(\n Effect.sync(() => {\n if (this.#validatingGeneration[key] === generation) {\n this.#validating = { ...this.#validating, [key]: false };\n }\n }),\n ),\n );\n\n this.#fibers[key] = this.#runtime.runFork(program);\n }\n\n /**\n * Handle a field's value actually changing: the field's server-sourced\n * error (if any) is superseded — clear it — then run whichever validators\n * apply for the `\"change\"` trigger.\n */\n #onFieldChanged(path: FormPath): void {\n this.#sourced = clearSourceAt(this.#sourcedSnapshot(), \"server\", path);\n this.#runFieldTrigger(path, \"change\");\n this.#recomputeValid();\n }\n\n /** Handle a field losing focus: run whichever validators apply for the `\"blur\"` trigger. */\n #onFieldBlurred(path: FormPath): void {\n this.#runFieldTrigger(path, \"blur\");\n this.#recomputeValid();\n }\n\n /**\n * Run `path`'s sync + async validators for `trigger` (resolved by {@link\n * Validate.resolveFieldTrigger}). When neither is registered, falls back to\n * a whole-schema {@link validateField} re-run once the form has been\n * posted and {@link Mode.ParsedMode.revalidate} names this trigger — the\n * \"fixing a field clears its error without a resubmit\" behaviour.\n */\n #runFieldTrigger(path: FormPath, trigger: FieldTrigger): void {\n const validators = this.#fieldValidators[Path.toString(path)];\n const value = getAt(this.data, path);\n const resolved = Validate.resolveFieldTrigger(validators, trigger);\n\n if (resolved.sync) {\n this.#applySyncFieldValidator(path, resolved.sync, value);\n } else if (this.#shouldFallBackToRevalidate(trigger)) {\n this.#applyRevalidateFallback(path);\n }\n\n if (resolved.async) {\n this.#hasValidated = true;\n this.#runAsyncFieldValidator(path, trigger, resolved.async, value, resolved.asyncDebounceMs);\n }\n }\n\n /** Run a field's registered sync validator and set its client messages. */\n #applySyncFieldValidator(\n path: FormPath,\n validator: Validate.SyncFieldValidator<unknown>,\n value: unknown,\n ): void {\n this.#setClientMessagesAt(path, Validate.runSyncFieldValidator(validator, value));\n this.#hasValidated = true;\n }\n\n /** Whether, absent a registered validator, a whole-schema fallback revalidation applies for `trigger`. */\n #shouldFallBackToRevalidate(trigger: FieldTrigger): boolean {\n return this.posted && this.#revalidatesOn(trigger) && this.#options.schema !== undefined;\n }\n\n /** Re-run the whole-schema client validation and adopt just `path`'s messages. */\n #applyRevalidateFallback(path: FormPath): void {\n this.#sourced = setSourceAt(this.#sourcedSnapshot(), \"client\", path, this.validateField(path));\n this.#hasValidated = true;\n }\n\n /** Replace `path`'s client messages with `issues`' messages (folding {@link FormIssue}s at the field's own path). */\n #setClientMessagesAt(path: FormPath, issues: ReadonlyArray<FormIssue>): void {\n this.#sourced = setSourceAt(\n this.#sourcedSnapshot(),\n \"client\",\n path,\n issues.map((issue) => issue.message),\n );\n }\n\n /**\n * Run `path`'s `trigger` async validator as an interrupt-on-supersede\n * fiber (via {@link FormStore.#runValidatorFiber}) — the result (unless\n * the fiber is itself later interrupted) replaces the field's client\n * messages.\n */\n #runAsyncFieldValidator(\n path: FormPath,\n trigger: FieldTrigger,\n validator: Validate.AsyncFieldValidator<unknown, R>,\n value: unknown,\n debounceMs: number,\n ): void {\n const fiberKey = `${Path.toString(path)}:${trigger}`;\n this.#runValidatorFiber(\n fiberKey,\n Validate.runAsyncFieldValidator(validator, value, debounceMs).pipe(\n Effect.tap((issues) =>\n Effect.sync(() => {\n this.#setClientMessagesAt(path, issues);\n this.#recomputeValid();\n }),\n ),\n ),\n );\n }\n\n /**\n * Run the builder's `refineEffect` refinements (client-side, when the form\n * was seeded from a {@link FormBuilder}) as a whole-form async validator —\n * interrupt-on-supersede, same as a field's async validator (see {@link\n * FormStore.#runValidatorFiber}). The previous run's issue *paths* are\n * cleared before the fresh ones are merged in, so a superseded run can't\n * leave stale messages behind without disturbing schema-derived client\n * errors set by {@link validate} in between.\n */\n #runRefineEffect(): void {\n const builder = this.#options.builder;\n if (!builder || Arr.isReadonlyArrayEmpty(FormBuilder.asyncRefinements(builder))) return;\n\n // The refinements are typed against the builder's *decoded* values, and\n // the server (`Form.fromBuilder`) runs them on decoded data; the live form\n // holds the *encoded* values, so decode first. Values that don't decode\n // run no refinements — exactly as on the server — and the schema's own\n // field errors (from `validate`) already report why; the run's empty\n // result still clears the previous run's refinement errors.\n if (Option.isNone(this.#decodeForRefinements)) return;\n const decoded = this.#decodeForRefinements.value(this.#detachedData());\n this.#runValidatorFiber(\n \"\",\n decoded.pipe(\n Effect.matchEffect({\n onFailure: (): Effect.Effect<ReadonlyArray<Issues.FormIssue>, never, R> =>\n Effect.succeed([]),\n onSuccess: (values) => FormBuilder.runAsyncRefinements(builder, values),\n }),\n Effect.tap((issues) =>\n Effect.sync(() => {\n const cleared = Arr.reduce(\n this.#lastRefinementPaths,\n this.#sourcedSnapshot().client,\n (client, path) => replaceAt(client, path, []),\n );\n const client = Errors.mergeErrors(cleared, Errors.issuesToErrors(issues));\n this.#lastRefinementPaths = issues.map((issue) => issue.path);\n this.#sourced = { ...this.#sourced, client };\n this.#recomputeValid();\n }),\n ),\n ),\n );\n }\n\n /**\n * Reset `data`, `errors`, and `message` back to the tainted baseline. Pass\n * `values` to re-seed that baseline first (e.g. adopting a different\n * entity's values) instead of returning to the original seed.\n */\n reset(values?: A): void {\n if (values !== undefined) this.#initial = structuredClone(values);\n Arr.forEach(R.keys(this.#fibers), (key) => this.#interruptFiber(key));\n this.data = structuredClone(this.#initial);\n this.#previousData = this.#detachedData();\n this.#sourced = emptySourced;\n this.message = undefined;\n this.valid = false;\n this.posted = false;\n this.#touched = {};\n this.#dirtied = {};\n this.#lastSubmitted = undefined;\n this.#hasValidated = false;\n this.#validating = {};\n this.#lastRefinementPaths = [];\n this.submissionAttempts = 0;\n this.#lastSubmitOutcome = undefined;\n }\n\n /** A serialisable snapshot for SvelteKit's `snapshot` capture/restore. */\n capture(): FormState<A, Message> {\n return {\n id: this.id,\n valid: this.valid,\n posted: this.posted,\n data: this.#detachedData(),\n errors: untypedErrors(this.errors),\n message: this.message,\n constraints: this.constraints,\n };\n }\n\n /**\n * An independent deep copy of `data`, detached from the live `$state`\n * proxy. `structuredClone` can't clone the proxy directly (Node's\n * structured-clone algorithm rejects it); take a plain snapshot first, per\n * Svelte's own guidance for this exact case.\n */\n #detachedData(): A {\n // SAFETY: `data` holds an `A`, and `$state.snapshot` of form data (plain,\n // structured-cloneable values) is a deep copy of the same shape.\n return structuredClone($state.snapshot(this.data)) as A;\n }\n\n /** Restore from a {@link capture} snapshot (its merged tree becomes the server tree; the client tree clears). */\n restore(snapshot: FormState<A, Message>): void {\n this.data = structuredClone(snapshot.data);\n this.#previousData = this.#detachedData();\n this.#sourced = { client: noErrors, server: snapshot.errors };\n this.message = snapshot.message;\n this.valid = snapshot.valid;\n this.posted = snapshot.posted;\n this.constraints = snapshot.constraints ?? {};\n this.#hasValidated = snapshot.posted;\n }\n\n /**\n * Apply server-sourced `data` + `errors` directly, bypassing a submit\n * round-trip — for glue code that repopulates the store from a derived\n * result it received some other way (e.g.\n * {@link module:effect-forms-svelte/SvelteKit}'s `applyActionResult`, from\n * a SvelteKit form action's failure). Sets `posted` so\n * {@link shouldShowError} shows the new errors immediately; `valid` is\n * always `false` (the caller only reaches for this on a failure result).\n */\n setServerState(data: A, errors: FormErrors): void {\n this.data = data;\n this.#previousData = structuredClone(data);\n this.#sourced = { client: noErrors, server: errors };\n this.valid = false;\n this.posted = true;\n this.#hasValidated = true;\n }\n\n #applyUpdate(update: FormUpdate<A, Message>, result: FormActionResult<A, Message>): void {\n this.#options.onUpdate?.({ update, result });\n if (update.redirect !== undefined) return this.#applyRedirect(update.redirect);\n if (update.error !== undefined) return this.#applyError(update.error);\n if (update.data !== undefined) this.#applySubmittedData(update.data, result);\n\n // A submission result is authoritative: it superseded whatever the client\n // last computed, so the client tree clears and the response becomes the\n // new server tree (server errors are then cleared field-by-field as the\n // user edits / revalidates — see #onFieldChanged / #runFieldTrigger).\n this.#sourced = { client: noErrors, server: update.errors ?? noErrors };\n this.#hasValidated = true;\n this.message = update.message;\n if (update.valid !== undefined) this.valid = update.valid;\n this.#lastSubmitOutcome = this.valid ? \"success\" : \"failure\";\n this.posted = true;\n this.#options.onUpdated?.({\n form: { data: this.data, errors: untypedErrors(this.errors), valid: this.valid },\n });\n }\n\n #applyRedirect(location: string): void {\n this.#lastSubmitOutcome = \"success\";\n if (this.#options.onRedirect) this.#options.onRedirect({ location });\n else if (!Predicate.isUndefined(globalThis.window)) window.location.assign(location);\n }\n\n #applyError(error: string): void {\n this.#lastSubmitOutcome = \"failure\";\n this.#options.onError?.({ error });\n }\n\n /**\n * Adopt (or discard) the submission's echoed data via {@link\n * computeDataApplication} and rebase the tainted baseline. An\n * auto-submitting (live-save) form keeps its local data as the source of\n * truth: adopting the server's echo would discard keystrokes made during\n * the round-trip and, when the echo isn't byte-identical (a trimmed or\n * server-stamped field), retrigger the change watcher — an extra submit,\n * or an endless loop for a non-idempotent echo. On a successful submit the\n * tainted baseline still rebases to whatever `data` ends up holding, so\n * `isTainted` (and the `beforeunload` guard it drives) reflects what was\n * actually just saved, not the original seed.\n */\n #applySubmittedData(data: A, result: FormActionResult<A, Message>): void {\n // SAFETY: `#lastSubmitted` / `data` hold an `A`, and `$state.snapshot` of\n // form data (plain, structured-cloneable values) is a copy of the same shape.\n const submittedData = $state.snapshot(this.#lastSubmitted ?? this.data) as A;\n const applied = computeDataApplication({\n isSuccess: Predicate.isTagged(result, \"Success\"),\n appliedData: data,\n initial: this.#initial,\n // The snapshot actually sent (see `#runSubmit`), not the live `data` —\n // further edits may have landed while this request was in flight.\n // Detached (`$state.snapshot`) so the `structuredClone`s below never\n // receive a `$state` proxy via `applied.data` / `applied.rebaseTo`.\n submittedData,\n resetForm: this.#options.resetForm ?? false,\n autoSubmit: this.#mode.autoSubmit,\n });\n if (applied.data !== undefined) this.data = structuredClone(applied.data);\n if (applied.rebaseTo !== undefined) {\n this.#initial = structuredClone(applied.rebaseTo);\n this.#touched = {};\n }\n // Resync the field-tracking watcher's baseline to whatever `data` ends up\n // holding post-submit, so it doesn't re-diff (and re-run field validators\n // / re-clear server errors for) the submission's own echo.\n this.#previousData = this.#detachedData();\n }\n\n async #runSubmit(node: HTMLFormElement, submitter: HTMLElement | null = null): Promise<void> {\n // `new FormData(node, submitter)` folds the clicked button's name/value in,\n // matching the native no-JS POST; the auto-submit path calls this with no\n // submitter by definition, so it keeps its current (button-less) body.\n const formData = new FormData(node, submitter);\n let cancelled = false;\n let resolved: FormActionResult<A, Message> | undefined;\n await this.#options.onSubmit?.({\n data: this.data,\n formData,\n form: node,\n submitter,\n cancel: () => {\n cancelled = true;\n },\n resolve: (result) => {\n resolved = result;\n },\n });\n if (cancelled) return;\n\n // Snapshot what we're sending so `revertToLastSubmit` / `changedSinceSubmit`\n // measure against the submitted values.\n this.#lastSubmitted = this.#detachedData();\n this.submissionAttempts++;\n\n this.submitting = true;\n const delayMs = this.#options.delayMs ?? 500;\n const delayTimer = setTimeout(() => {\n this.delayed = true;\n }, delayMs);\n\n try {\n const submit = this.#options.submit ?? defaultSubmit;\n const action =\n node.action || (Predicate.isUndefined(globalThis.location) ? \"\" : location.href);\n let result: FormActionResult<A, Message>;\n if (resolved !== undefined) {\n result = resolved;\n } else if (this.#options.handler) {\n result = await this.#options.handler(this.data);\n } else {\n result = await submit(formData, action, node);\n }\n this.#options.onResult?.({ result });\n this.#applyUpdate(resultToUpdate(result), result);\n } catch (cause) {\n const message = Predicate.isError(cause) ? cause.message : \"Submission failed\";\n this.#lastSubmitOutcome = \"failure\";\n this.#options.onError?.({ error: message });\n } finally {\n clearTimeout(delayTimer);\n this.submitting = false;\n this.delayed = false;\n }\n }\n\n /**\n * Progressive-enhancement form action. Use as `<form use:form.enhance>` (or\n * spread {@link FormStore.props}). Intercepts submit, runs the `onSubmit`\n * handler, then sends the body and applies the result. Without JavaScript the\n * form posts normally.\n */\n enhance = (node: HTMLFormElement) => {\n const handleSubmit = (event: SubmitEvent): void => {\n event.preventDefault();\n void this.#runSubmit(node, event.submitter);\n };\n node.addEventListener(\"submit\", handleSubmit);\n\n const beforeUnload = (event: BeforeUnloadEvent): void => {\n if (this.#options.taintedMessage && this.isTainted) event.preventDefault();\n };\n if (this.#options.taintedMessage && !Predicate.isUndefined(globalThis.window)) {\n window.addEventListener(\"beforeunload\", beforeUnload);\n }\n\n // Remember the mounted node so `onChange` / `onBlur` auto-submit can post.\n this.#autoSubmitNode = node;\n\n this.#options.listeners?.onMount?.({ data: $state.snapshot(this.data) });\n\n // Two independent watchers over `data`, both rooted so they get explicit\n // teardown alongside the DOM listeners:\n // - the `onChange`-mode one re-validates (and maybe auto-submits) the\n // whole form, debounced by `mode.debounce`, and dispatches any\n // configured change listeners (independent of mode, so listeners fire\n // whichever mode the form validates in);\n // - the field-tracking one diffs against the last-seen snapshot and runs\n // per-field validators / clears superseded server errors for exactly\n // the paths that changed — independent of `mode.validation`, since a\n // field's own validators and the post-submit revalidate mode both\n // apply regardless of the whole-form trigger. Always on (not gated on\n // whether any validator is registered yet): `setFieldValidators` can\n // register one after mount (its documented use case — a field that\n // doesn't exist until an array item is appended), and a gate evaluated\n // only at mount time would silently never watch for it.\n const dispatchListeners = this.#buildDispatchListeners();\n let stopWatching: (() => void) | undefined;\n if (this.#mode.validation === \"onChange\" || dispatchListeners !== undefined) {\n let previous = $state.snapshot(this.data);\n stopWatching = $effect.root(() => {\n $effect(() => {\n // Establish a deep dependency on the data tree.\n const snapshot = $state.snapshot(this.data);\n if (this.#mode.validation === \"onChange\" && (this.isTainted || this.posted)) {\n this.#scheduleValidation();\n }\n if (dispatchListeners !== undefined)\n dispatchChange(previous, snapshot, dispatchListeners);\n previous = snapshot;\n });\n });\n }\n\n const stopFieldWatching = $effect.root(() => {\n $effect(() => {\n // SAFETY: `data` holds an `A`, and `$state.snapshot` of form data\n // (plain, structured-cloneable values) is a copy of the same shape.\n const snapshot = $state.snapshot(this.data) as A;\n const changed = taintedLeafPaths(computeTainted(this.#previousData, snapshot));\n this.#previousData = snapshot;\n Arr.forEach(changed, (path) => this.#onFieldChanged(path));\n });\n });\n\n return {\n destroy: () => {\n node.removeEventListener(\"submit\", handleSubmit);\n if (!Predicate.isUndefined(globalThis.window))\n window.removeEventListener(\"beforeunload\", beforeUnload);\n stopWatching?.();\n stopFieldWatching?.();\n clearTimeout(this.#validateTimer);\n Arr.forEach(R.values(this.#listenerTimers), (timer) => clearTimeout(timer));\n this.#listenerTimers = {};\n Arr.forEach(R.keys(this.#fibers), (key) => this.#interruptFiber(key));\n if (this.#autoSubmitNode === node) this.#autoSubmitNode = undefined;\n },\n };\n };\n\n /** Attachment form of {@link FormStore.enhance} for {@link FormStore.props}. */\n readonly #enhanceAttachment = (node: Element): (() => void) => {\n // SAFETY: this attachment is only reachable through `props`, which is\n // spread onto the `<form>` element (`<form {...form.props}>`).\n return this.enhance(node as HTMLFormElement).destroy;\n };\n\n /**\n * Build a {@link FormData} body from the current data (SPA / manual submit).\n *\n * Each value is encoded as `objectToFormData` encodes it: a `File` or\n * `Blob` is appended as a file and a `Date` as its UTC `YYYY-MM-DD`.\n */\n toFormData(): FormData {\n return objectToFormData(this.data);\n }\n}\n\n/** A schema plus its blank-form defaults — what {@link createForm} wraps `Form.make` over. */\nexport interface FormSeed<S extends Schema.Top> {\n readonly schema: S;\n readonly defaults: S[\"Type\"];\n readonly id?: string;\n}\n\n/** The built-schema type a {@link FormBuilder} of `TFields` / `R` collapses to. */\ntype BuiltSchema<TFields extends Field.FieldsRecord, R> = Schema.Codec<\n Field.DecodedFromFields<TFields>,\n Field.EncodedFromFields<TFields>,\n R\n>;\n\n/** Options for the {@link createForm} {@link FormBuilder} overload. */\nexport type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> = FormOptions<\n BuiltSchema<TFields, R>,\n Field.EncodedFromFields<TFields>,\n Message,\n R\n> & {\n /** Blank-form values. Defaults to a schema-derived encoded blank per field. */\n readonly defaults?: Field.EncodedFromFields<TFields>;\n /** Distinguishes multiple forms on a page. Defaults to `\"default\"`. */\n readonly id?: string;\n};\n\n/**\n * Create a reactive {@link FormStore}. Three sources are accepted:\n *\n * - a {@link FormBuilder} — the schema, blank defaults, and array-item defaults\n * are all derived from the builder (client validation, including\n * `refineEffect` refinements, is wired automatically);\n * - `{ schema, defaults }` — seed a blank form (wrapping the core's `Form.make`);\n * - a server-produced {@link FormState} — hydrate from it (schema in `options`).\n */\nexport function createForm<TFields extends Field.FieldsRecord, R, Message = unknown>(\n builder: FormBuilder.FormBuilder<TFields, R>,\n options?: FormBuilderOptions<TFields, R, Message>,\n): FormStore<BuiltSchema<TFields, R>, Field.EncodedFromFields<TFields>, Message, R>;\nexport function createForm<S extends Schema.Top, Message = unknown, R = never>(\n seed: FormSeed<S>,\n options?: FormOptions<S, S[\"Type\"], Message, R>,\n): FormStore<S, S[\"Type\"], Message, R>;\nexport function createForm<S extends Schema.Top, A = S[\"Type\"], Message = unknown, R = never>(\n initial: FormState<A, Message>,\n options: FormOptions<S, A, Message, R>,\n): FormStore<S, A, Message, R>;\nexport function createForm(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n source: FormBuilder.FormBuilder<any, any> | FormSeed<Schema.Top> | FormState<unknown>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: FormOptions<Schema.Top, any, any, any> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n defaults?: any;\n id?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): FormStore<Schema.Top, any, any, any> {\n if (FormBuilder.isFormBuilder(source)) {\n const schema: Schema.Top = FormBuilder.buildSchema(source);\n const builderOptions: Mutable<Form.FromBuilderOptions<Field.FieldsRecord>> = {};\n if (options.defaults !== undefined) builderOptions.defaults = options.defaults;\n if (options.id !== undefined) builderOptions.id = options.id;\n const form = Form.fromBuilder(source, builderOptions);\n return new FormStore(form.empty(), {\n ...options,\n schema,\n fields: source.fields,\n builder: source,\n });\n }\n if (\"defaults\" in source && \"schema\" in source) {\n const makeOptions: Mutable<Form.MakeFormOptions<Schema.Top>> = { defaults: source.defaults };\n if (source.id !== undefined) makeOptions.id = source.id;\n const state = makeForm(source.schema, makeOptions).empty();\n return new FormStore(state, { ...options, schema: source.schema });\n }\n // SAFETY: `source` is neither a builder (`isFormBuilder`) nor a seed (it\n // lacks `defaults` / `schema`), so it is the `FormState`-hydration overload's.\n return new FormStore(source as FormState<unknown>, options);\n}\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { Issues } from "@thomasfosterau/effect-forms";
2
2
  import * as Duration from "effect/Duration";
3
- //#region src/Listeners.d.ts
4
3
  type FormPath = Issues.FormPath;
5
4
  /** One leaf that changed between two data snapshots. */
6
5
  export interface ListenerEvent {
@@ -52,6 +51,5 @@ export declare function changeEvents(before: unknown, after: unknown): ListenerE
52
51
  * is the caller's concern (see the module doc).
53
52
  */
54
53
  export declare function dispatchChange(before: unknown, after: unknown, listeners: FormListeners | undefined): void;
55
- //#endregion
56
54
  export {};
57
55
  //# sourceMappingURL=Listeners.d.ts.map
package/dist/Listeners.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { computeTainted, taintedLeafPaths } from "./Tainted.js";
2
2
  import { Path } from "@thomasfosterau/effect-forms";
3
3
  import * as Arr from "effect/Array";
4
- //#region src/Listeners.ts
4
+ // src/Listeners.ts
5
5
  /**
6
6
  * The leaf paths that changed from `before` to `after`, each with its new
7
7
  * value. Built on {@link computeTainted}, so the same rules apply: array
@@ -33,7 +33,6 @@ function dispatchChange(before, after, listeners) {
33
33
  });
34
34
  });
35
35
  }
36
- //#endregion
37
36
  export { changeEvents, dispatchChange };
38
37
 
39
38
  //# sourceMappingURL=Listeners.js.map
@@ -1,6 +1,5 @@
1
1
  import { FormStore } from "./FormStore.svelte.js";
2
2
  import { Result } from "@thomasfosterau/effect-forms";
3
- //#region src/SvelteKit.d.ts
4
3
  type FormActionData<A = unknown, Message = unknown> = Result.FormActionData<A, Message>;
5
4
  /**
6
5
  * The minimal view of SvelteKit's `ActionResult` that {@link applyActionResult}
@@ -19,6 +18,5 @@ export interface ServerActionResult {
19
18
  * call `applyAction`.
20
19
  */
21
20
  export declare function applyActionResult(form: FormStore<any, any, any>, result: ServerActionResult): boolean;
22
- //#endregion
23
21
  export {};
24
22
  //# sourceMappingURL=SvelteKit.d.ts.map
package/dist/SvelteKit.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as Predicate from "effect/Predicate";
2
- //#region src/SvelteKit.ts
2
+ // src/SvelteKit.ts
3
3
  const isFormActionData = (data) => Predicate.isObjectOrArray(data) && Predicate.hasProperty(data, "form") && Boolean(data.form);
4
4
  /**
5
5
  * Apply a derived form action's result to `form`. On a validation `failure`
@@ -15,7 +15,6 @@ function applyActionResult(form, result) {
15
15
  form.setServerState(state.data, state.errors);
16
16
  return true;
17
17
  }
18
- //#endregion
19
18
  export { applyActionResult };
20
19
 
21
20
  //# sourceMappingURL=SvelteKit.js.map
package/dist/Tainted.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Issues } from "@thomasfosterau/effect-forms";
2
- //#region src/Tainted.d.ts
3
2
  type FormPath = Issues.FormPath;
4
3
  /** A nested tree mirroring the form data, `true` where a leaf has changed. */
5
4
  export interface TaintedFields {
@@ -27,6 +26,5 @@ export declare function isTaintedAt(tainted: TaintedFields, path: FormPath): boo
27
26
  * which only says *something* changed).
28
27
  */
29
28
  export declare function taintedLeafPaths(tainted: TaintedFields): FormPath[];
30
- //#endregion
31
29
  export {};
32
30
  //# sourceMappingURL=Tainted.d.ts.map
package/dist/Tainted.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as Arr from "effect/Array";
2
2
  import * as Predicate from "effect/Predicate";
3
3
  import * as Record from "effect/Record";
4
- //#region src/Tainted.ts
4
+ // src/Tainted.ts
5
5
  /** A non-null object or array, indexable by its own string keys. */
6
6
  const isObject = (value) => Predicate.isObjectOrArray(value);
7
7
  /** The union of both containers' own enumerable keys, `a`'s first. */
@@ -63,7 +63,6 @@ function taintedLeafPaths(tainted) {
63
63
  });
64
64
  return walk(tainted, []);
65
65
  }
66
- //#endregion
67
66
  export { computeTainted, deepEqual, isAnyTainted, isTaintedAt, taintedLeafPaths };
68
67
 
69
68
  //# sourceMappingURL=Tainted.js.map
@@ -1,4 +1,4 @@
1
- //#region \0rolldown/runtime.js
1
+ // \0rolldown/runtime.js
2
2
  var __defProp = Object.defineProperty;
3
3
  var __exportAll = (all, no_symbols) => {
4
4
  let target = {};
@@ -9,5 +9,4 @@ var __exportAll = (all, no_symbols) => {
9
9
  if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
10
  return target;
11
11
  };
12
- //#endregion
13
12
  export { __exportAll };
@@ -1,5 +1,4 @@
1
1
  import * as Option from "effect/Option";
2
- //#region src/kit/ActionRedirect.d.ts
3
2
  /**
4
3
  * A user-fixable failure's message; `None` takes the hard-error path.
5
4
  *
@@ -18,5 +17,4 @@ export declare const userFixableMessage: (cause: unknown, fallback: string) => O
18
17
  * answering the path and query only (never an origin).
19
18
  */
20
19
  export declare const withFailureParams: (target: string, params: Readonly<Record<string, string>>) => string;
21
- //#endregion
22
20
  //# sourceMappingURL=ActionRedirect.d.ts.map
@@ -4,7 +4,7 @@ import * as Predicate from "effect/Predicate";
4
4
  import * as Record from "effect/Record";
5
5
  import * as HashSet from "effect/HashSet";
6
6
  import * as Option from "effect/Option";
7
- //#region src/kit/ActionRedirect.ts
7
+ // src/kit/ActionRedirect.ts
8
8
  /**
9
9
  * Failure routing for **action-only** routes: a route with no page of its
10
10
  * own (a restore, a confirm) always answers a submission with a redirect, so
@@ -48,7 +48,6 @@ const withFailureParams = (target, params) => {
48
48
  Arr.forEach(Record.toEntries(params), ([key, value]) => url.searchParams.set(key, value));
49
49
  return url.pathname + url.search;
50
50
  };
51
- //#endregion
52
51
  export { userFixableMessage, withFailureParams };
53
52
 
54
53
  //# sourceMappingURL=ActionRedirect.js.map
@@ -26,7 +26,7 @@ export type CommandFailure = RoutedFailure | MessageFailure;
26
26
  /** Construct a {@link RoutedFailure}. */
27
27
  export declare const routed: (status: number, errors: ReadonlyArray<ErrorObject>) => CommandFailure;
28
28
  /** Construct a {@link MessageFailure}. */
29
- export declare const message: (status: number, message: string) => CommandFailure;
29
+ export declare const message: (status: number, text: string) => CommandFailure;
30
30
  /** A classifier: `Some` for a failure the form can show, `None` for an unexpected one. */
31
31
  export type Classify = (cause: unknown) => Option.Option<CommandFailure>;
32
32
  /** The fallback message for an error object with neither `detail` nor `title`. */
@@ -47,6 +47,5 @@ export declare const detailOf: (error: ErrorObject | undefined, fallback?: strin
47
47
  * - anything else → `None` (unexpected).
48
48
  */
49
49
  export declare const jsonapiFailure: Classify;
50
- //#endregion
51
50
  export { CommandFailure_d_exports };
52
51
  //# sourceMappingURL=CommandFailure.d.ts.map
@@ -2,7 +2,7 @@ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
2
  import * as Arr from "effect/Array";
3
3
  import * as Predicate from "effect/Predicate";
4
4
  import * as Option from "effect/Option";
5
- //#region src/kit/CommandFailure.ts
5
+ // src/kit/CommandFailure.ts
6
6
  /**
7
7
  * Classifying what a form action's command failed with.
8
8
  *
@@ -36,10 +36,10 @@ const routed = (status, errors) => ({
36
36
  errors
37
37
  });
38
38
  /** Construct a {@link MessageFailure}. */
39
- const message = (status, message) => ({
39
+ const message = (status, text) => ({
40
40
  _tag: "Message",
41
41
  status,
42
- message
42
+ message: text
43
43
  });
44
44
  /** The fallback message for an error object with neither `detail` nor `title`. */
45
45
  const DEFAULT_MESSAGE = "Request could not be completed";
@@ -52,7 +52,7 @@ const fromJsonapiError = (cause) => Option.flatMap(statusOf(cause), (status) =>
52
52
  const fromErrorResponse = (cause) => Option.map(statusOf(cause), (status) => {
53
53
  const errors = Predicate.hasProperty(cause, "document") && Option.isOption(cause.document) ? Option.flatMap(cause.document, (document) => Predicate.hasProperty(document, "errors") && Array.isArray(document.errors) && Arr.isReadonlyArrayNonEmpty(document.errors) ? Option.some(document.errors) : Option.none()) : Option.none();
54
54
  return Option.match(errors, {
55
- onSome: (errors) => routed(status, errors),
55
+ onSome: (routedErrors) => routed(status, routedErrors),
56
56
  onNone: () => message(status, Predicate.hasProperty(cause, "message") && Predicate.isString(cause.message) ? cause.message : DEFAULT_MESSAGE)
57
57
  });
58
58
  });
@@ -75,7 +75,6 @@ const jsonapiFailure = (cause) => {
75
75
  if (Predicate.isTagged(cause, "ErrorResponse")) return fromErrorResponse(cause);
76
76
  return Predicate.isError(cause) ? Option.map(statusOf(cause), (status) => message(status, cause.message)) : Option.none();
77
77
  };
78
- //#endregion
79
78
  export { CommandFailure_exports, DEFAULT_MESSAGE, detailOf, jsonapiFailure, message, routed };
80
79
 
81
80
  //# sourceMappingURL=CommandFailure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommandFailure.js","names":[],"sources":["../../src/kit/CommandFailure.ts"],"sourcesContent":["/**\n * Classifying what a form action's command failed with.\n *\n * A command (an API client call, an Effect program) fails with whatever its\n * transport raises. The form layer only needs to know two things about it:\n * which JSON:API error objects it carried (so each can be routed onto a\n * field by its `source.pointer`), or, failing that, a status and a single\n * message for the whole form. {@link CommandFailure} is that view, and\n * {@link jsonapiFailure} is the default classifier.\n *\n * The error-object shape is `@thomasfosterau/effect-jsonapi`'s\n * `Document.ErrorObject` — an optional peer imported for its type only, so\n * nothing here depends on it at runtime and no application type leaks in.\n * Causes are recognised structurally by their `_tag`, so an application's\n * own `JsonapiError` with the same shape is recognised too.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/kit/CommandFailure\n */\n\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport type { Document } from \"@thomasfosterau/effect-jsonapi\";\n\n/** One JSON:API error object: `@thomasfosterau/effect-jsonapi`'s `Document.ErrorObject`. */\nexport type ErrorObject = typeof Document.ErrorObject.Type;\n\n/** A failure carrying JSON:API error objects, each routed to a field (or the root) by its pointer. */\nexport interface RoutedFailure {\n readonly _tag: \"Routed\";\n readonly status: number;\n readonly errors: ReadonlyArray<ErrorObject>;\n}\n\n/** A failure with no error objects to route: one whole-form message. */\nexport interface MessageFailure {\n readonly _tag: \"Message\";\n readonly status: number;\n readonly message: string;\n}\n\n/**\n * What a form action needs to know about a recognised command failure. An\n * unrecognised failure is not a `CommandFailure` at all: the classifier\n * answers `None` and the action treats it as unexpected.\n */\nexport type CommandFailure = RoutedFailure | MessageFailure;\n\n/** Construct a {@link RoutedFailure}. */\nexport const routed = (status: number, errors: ReadonlyArray<ErrorObject>): CommandFailure => ({\n _tag: \"Routed\",\n status,\n errors,\n});\n\n/** Construct a {@link MessageFailure}. */\nexport const message = (status: number, message: string): CommandFailure => ({\n _tag: \"Message\",\n status,\n message,\n});\n\n/** A classifier: `Some` for a failure the form can show, `None` for an unexpected one. */\nexport type Classify = (cause: unknown) => Option.Option<CommandFailure>;\n\n/** The fallback message for an error object with neither `detail` nor `title`. */\nexport const DEFAULT_MESSAGE = \"Request could not be completed\";\n\n/** An error object's human message: its `detail`, else its `title`, else `fallback`. */\nexport const detailOf = (error: ErrorObject | undefined, fallback = DEFAULT_MESSAGE): string =>\n error?.detail ?? error?.title ?? fallback;\n\nconst isStatus = (u: unknown): u is number => Predicate.isNumber(u) && Number.isInteger(u);\n\n/** A cause recognised by its `_tag` as effect-jsonapi's (or an application's) `JsonapiError`. */\ninterface JsonapiErrorLike {\n readonly _tag: \"JsonapiError\";\n}\n\n/** A cause recognised by its `_tag` as effect-jsonapi's client-side `ErrorResponse`. */\ninterface ErrorResponseLike {\n readonly _tag: \"ErrorResponse\";\n}\n\n/** The cause's `status`, when it carries an integer one. */\nconst statusOf = (cause: JsonapiErrorLike | ErrorResponseLike | Error): Option.Option<number> =>\n Predicate.hasProperty(cause, \"status\") && isStatus(cause.status)\n ? Option.some(cause.status)\n : Option.none();\n\n// `ServerErrors.JsonapiError` (effect-jsonapi) and the applications' own\n// `JsonapiError`: `{ _tag: \"JsonapiError\", status, errors }`.\nconst fromJsonapiError = (cause: JsonapiErrorLike): Option.Option<CommandFailure> =>\n Option.flatMap(statusOf(cause), (status) =>\n Predicate.hasProperty(cause, \"errors\") && Array.isArray(cause.errors)\n ? // SAFETY: a `JsonapiError`-tagged cause carries JSON:API error objects in\n // `errors` by that tag's contract (effect-jsonapi's `ServerErrors.JsonapiError`\n // and the applications' own); causes are recognised structurally, not decoded.\n Option.some(routed(status, cause.errors as ReadonlyArray<ErrorObject>))\n : Option.none(),\n );\n\n// `Client.ErrorResponse` (effect-jsonapi): `{ _tag: \"ErrorResponse\", status,\n// message, document: Option<ErrorDocument> }`. A decoded document routes its\n// error objects; a response with none is its message alone.\nconst fromErrorResponse = (cause: ErrorResponseLike): Option.Option<CommandFailure> =>\n Option.map(statusOf(cause), (status) => {\n const errors =\n Predicate.hasProperty(cause, \"document\") && Option.isOption(cause.document)\n ? Option.flatMap(cause.document, (document) =>\n Predicate.hasProperty(document, \"errors\") &&\n Array.isArray(document.errors) &&\n Arr.isReadonlyArrayNonEmpty(document.errors)\n ? // SAFETY: an `ErrorResponse`'s `document` is effect-jsonapi's decoded\n // `ErrorDocument`, whose `errors` are JSON:API error objects.\n Option.some(document.errors as ReadonlyArray<ErrorObject>)\n : Option.none(),\n )\n : Option.none<ReadonlyArray<ErrorObject>>();\n return Option.match(errors, {\n onSome: (errors) => routed(status, errors),\n onNone: () =>\n message(\n status,\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : DEFAULT_MESSAGE,\n ),\n });\n });\n\n/**\n * The default classifier:\n *\n * - a `JsonapiError`-tagged cause (`{ status, errors }`) → its error objects,\n * at its own status;\n * - an `ErrorResponse`-tagged cause (effect-jsonapi's client-side decoded\n * error) → its document's error objects, or its message when the response\n * carried no document;\n * - an `Error` carrying a numeric `status` (a transport's status error, e.g.\n * a 413 oversize upload the transport did not map to a document) → its\n * message, at its own status;\n * - anything else → `None` (unexpected).\n */\nexport const jsonapiFailure: Classify = (cause) => {\n if (!Predicate.isObjectOrArray(cause)) return Option.none();\n if (Predicate.isTagged(cause, \"JsonapiError\")) return fromJsonapiError(cause);\n if (Predicate.isTagged(cause, \"ErrorResponse\")) return fromErrorResponse(cause);\n return Predicate.isError(cause)\n ? Option.map(statusOf(cause), (status) => message(status, cause.message))\n : Option.none();\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,MAAa,UAAU,QAAgB,YAAwD;CAC7F,MAAM;CACN;CACA;AACF;;AAGA,MAAa,WAAW,QAAgB,aAAqC;CAC3E,MAAM;CACN;CACA;AACF;;AAMA,MAAa,kBAAkB;;AAG/B,MAAa,YAAY,OAAgC,WAAW,oBAClE,OAAO,UAAU,OAAO,SAAS;AAEnC,MAAM,YAAY,MAA4B,UAAU,SAAS,CAAC,KAAK,OAAO,UAAU,CAAC;;AAazF,MAAM,YAAY,UAChB,UAAU,YAAY,OAAO,QAAQ,KAAK,SAAS,MAAM,MAAM,IAC3D,OAAO,KAAK,MAAM,MAAM,IACxB,OAAO,KAAK;AAIlB,MAAM,oBAAoB,UACxB,OAAO,QAAQ,SAAS,KAAK,IAAI,WAC/B,UAAU,YAAY,OAAO,QAAQ,KAAK,MAAM,QAAQ,MAAM,MAAM,IAIhE,OAAO,KAAK,OAAO,QAAQ,MAAM,MAAoC,CAAC,IACtE,OAAO,KAAK,CAClB;AAKF,MAAM,qBAAqB,UACzB,OAAO,IAAI,SAAS,KAAK,IAAI,WAAW;CACtC,MAAM,SACJ,UAAU,YAAY,OAAO,UAAU,KAAK,OAAO,SAAS,MAAM,QAAQ,IACtE,OAAO,QAAQ,MAAM,WAAW,aAC9B,UAAU,YAAY,UAAU,QAAQ,KACxC,MAAM,QAAQ,SAAS,MAAM,KAC7B,IAAI,wBAAwB,SAAS,MAAM,IAGvC,OAAO,KAAK,SAAS,MAAoC,IACzD,OAAO,KAAK,CAClB,IACA,OAAO,KAAiC;CAC9C,OAAO,OAAO,MAAM,QAAQ;EAC1B,SAAS,WAAW,OAAO,QAAQ,MAAM;EACzC,cACE,QACE,QACA,UAAU,YAAY,OAAO,SAAS,KAAK,UAAU,SAAS,MAAM,OAAO,IACvE,MAAM,UACN,eACN;CACJ,CAAC;AACH,CAAC;;;;;;;;;;;;;;AAeH,MAAa,kBAA4B,UAAU;CACjD,IAAI,CAAC,UAAU,gBAAgB,KAAK,GAAG,OAAO,OAAO,KAAK;CAC1D,IAAI,UAAU,SAAS,OAAO,cAAc,GAAG,OAAO,iBAAiB,KAAK;CAC5E,IAAI,UAAU,SAAS,OAAO,eAAe,GAAG,OAAO,kBAAkB,KAAK;CAC9E,OAAO,UAAU,QAAQ,KAAK,IAC1B,OAAO,IAAI,SAAS,KAAK,IAAI,WAAW,QAAQ,QAAQ,MAAM,OAAO,CAAC,IACtE,OAAO,KAAK;AAClB"}
1
+ {"version":3,"file":"CommandFailure.js","names":[],"sources":["../../src/kit/CommandFailure.ts"],"sourcesContent":["/**\n * Classifying what a form action's command failed with.\n *\n * A command (an API client call, an Effect program) fails with whatever its\n * transport raises. The form layer only needs to know two things about it:\n * which JSON:API error objects it carried (so each can be routed onto a\n * field by its `source.pointer`), or, failing that, a status and a single\n * message for the whole form. {@link CommandFailure} is that view, and\n * {@link jsonapiFailure} is the default classifier.\n *\n * The error-object shape is `@thomasfosterau/effect-jsonapi`'s\n * `Document.ErrorObject` — an optional peer imported for its type only, so\n * nothing here depends on it at runtime and no application type leaks in.\n * Causes are recognised structurally by their `_tag`, so an application's\n * own `JsonapiError` with the same shape is recognised too.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/kit/CommandFailure\n */\n\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport type { Document } from \"@thomasfosterau/effect-jsonapi\";\n\n/** One JSON:API error object: `@thomasfosterau/effect-jsonapi`'s `Document.ErrorObject`. */\nexport type ErrorObject = typeof Document.ErrorObject.Type;\n\n/** A failure carrying JSON:API error objects, each routed to a field (or the root) by its pointer. */\nexport interface RoutedFailure {\n readonly _tag: \"Routed\";\n readonly status: number;\n readonly errors: ReadonlyArray<ErrorObject>;\n}\n\n/** A failure with no error objects to route: one whole-form message. */\nexport interface MessageFailure {\n readonly _tag: \"Message\";\n readonly status: number;\n readonly message: string;\n}\n\n/**\n * What a form action needs to know about a recognised command failure. An\n * unrecognised failure is not a `CommandFailure` at all: the classifier\n * answers `None` and the action treats it as unexpected.\n */\nexport type CommandFailure = RoutedFailure | MessageFailure;\n\n/** Construct a {@link RoutedFailure}. */\nexport const routed = (status: number, errors: ReadonlyArray<ErrorObject>): CommandFailure => ({\n _tag: \"Routed\",\n status,\n errors,\n});\n\n/** Construct a {@link MessageFailure}. */\nexport const message = (status: number, text: string): CommandFailure => ({\n _tag: \"Message\",\n status,\n message: text,\n});\n\n/** A classifier: `Some` for a failure the form can show, `None` for an unexpected one. */\nexport type Classify = (cause: unknown) => Option.Option<CommandFailure>;\n\n/** The fallback message for an error object with neither `detail` nor `title`. */\nexport const DEFAULT_MESSAGE = \"Request could not be completed\";\n\n/** An error object's human message: its `detail`, else its `title`, else `fallback`. */\nexport const detailOf = (error: ErrorObject | undefined, fallback = DEFAULT_MESSAGE): string =>\n error?.detail ?? error?.title ?? fallback;\n\nconst isStatus = (u: unknown): u is number => Predicate.isNumber(u) && Number.isInteger(u);\n\n/** A cause recognised by its `_tag` as effect-jsonapi's (or an application's) `JsonapiError`. */\ninterface JsonapiErrorLike {\n readonly _tag: \"JsonapiError\";\n}\n\n/** A cause recognised by its `_tag` as effect-jsonapi's client-side `ErrorResponse`. */\ninterface ErrorResponseLike {\n readonly _tag: \"ErrorResponse\";\n}\n\n/** The cause's `status`, when it carries an integer one. */\nconst statusOf = (cause: JsonapiErrorLike | ErrorResponseLike | Error): Option.Option<number> =>\n Predicate.hasProperty(cause, \"status\") && isStatus(cause.status)\n ? Option.some(cause.status)\n : Option.none();\n\n// `ServerErrors.JsonapiError` (effect-jsonapi) and the applications' own\n// `JsonapiError`: `{ _tag: \"JsonapiError\", status, errors }`.\nconst fromJsonapiError = (cause: JsonapiErrorLike): Option.Option<CommandFailure> =>\n Option.flatMap(statusOf(cause), (status) =>\n Predicate.hasProperty(cause, \"errors\") && Array.isArray(cause.errors)\n ? // SAFETY: a `JsonapiError`-tagged cause carries JSON:API error objects in\n // `errors` by that tag's contract (effect-jsonapi's `ServerErrors.JsonapiError`\n // and the applications' own); causes are recognised structurally, not decoded.\n Option.some(routed(status, cause.errors as ReadonlyArray<ErrorObject>))\n : Option.none(),\n );\n\n// `Client.ErrorResponse` (effect-jsonapi): `{ _tag: \"ErrorResponse\", status,\n// message, document: Option<ErrorDocument> }`. A decoded document routes its\n// error objects; a response with none is its message alone.\nconst fromErrorResponse = (cause: ErrorResponseLike): Option.Option<CommandFailure> =>\n Option.map(statusOf(cause), (status) => {\n const errors =\n Predicate.hasProperty(cause, \"document\") && Option.isOption(cause.document)\n ? Option.flatMap(cause.document, (document) =>\n Predicate.hasProperty(document, \"errors\") &&\n Array.isArray(document.errors) &&\n Arr.isReadonlyArrayNonEmpty(document.errors)\n ? // SAFETY: an `ErrorResponse`'s `document` is effect-jsonapi's decoded\n // `ErrorDocument`, whose `errors` are JSON:API error objects.\n Option.some(document.errors as ReadonlyArray<ErrorObject>)\n : Option.none(),\n )\n : Option.none<ReadonlyArray<ErrorObject>>();\n return Option.match(errors, {\n onSome: (routedErrors) => routed(status, routedErrors),\n onNone: () =>\n message(\n status,\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : DEFAULT_MESSAGE,\n ),\n });\n });\n\n/**\n * The default classifier:\n *\n * - a `JsonapiError`-tagged cause (`{ status, errors }`) → its error objects,\n * at its own status;\n * - an `ErrorResponse`-tagged cause (effect-jsonapi's client-side decoded\n * error) → its document's error objects, or its message when the response\n * carried no document;\n * - an `Error` carrying a numeric `status` (a transport's status error, e.g.\n * a 413 oversize upload the transport did not map to a document) → its\n * message, at its own status;\n * - anything else → `None` (unexpected).\n */\nexport const jsonapiFailure: Classify = (cause) => {\n if (!Predicate.isObjectOrArray(cause)) return Option.none();\n if (Predicate.isTagged(cause, \"JsonapiError\")) return fromJsonapiError(cause);\n if (Predicate.isTagged(cause, \"ErrorResponse\")) return fromErrorResponse(cause);\n return Predicate.isError(cause)\n ? Option.map(statusOf(cause), (status) => message(status, cause.message))\n : Option.none();\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,MAAa,UAAU,QAAgB,YAAwD;CAC7F,MAAM;CACN;CACA;AACF;;AAGA,MAAa,WAAW,QAAgB,UAAkC;CACxE,MAAM;CACN;CACA,SAAS;AACX;;AAMA,MAAa,kBAAkB;;AAG/B,MAAa,YAAY,OAAgC,WAAW,oBAClE,OAAO,UAAU,OAAO,SAAS;AAEnC,MAAM,YAAY,MAA4B,UAAU,SAAS,CAAC,KAAK,OAAO,UAAU,CAAC;;AAazF,MAAM,YAAY,UAChB,UAAU,YAAY,OAAO,QAAQ,KAAK,SAAS,MAAM,MAAM,IAC3D,OAAO,KAAK,MAAM,MAAM,IACxB,OAAO,KAAK;AAIlB,MAAM,oBAAoB,UACxB,OAAO,QAAQ,SAAS,KAAK,IAAI,WAC/B,UAAU,YAAY,OAAO,QAAQ,KAAK,MAAM,QAAQ,MAAM,MAAM,IAIhE,OAAO,KAAK,OAAO,QAAQ,MAAM,MAAoC,CAAC,IACtE,OAAO,KAAK,CAClB;AAKF,MAAM,qBAAqB,UACzB,OAAO,IAAI,SAAS,KAAK,IAAI,WAAW;CACtC,MAAM,SACJ,UAAU,YAAY,OAAO,UAAU,KAAK,OAAO,SAAS,MAAM,QAAQ,IACtE,OAAO,QAAQ,MAAM,WAAW,aAC9B,UAAU,YAAY,UAAU,QAAQ,KACxC,MAAM,QAAQ,SAAS,MAAM,KAC7B,IAAI,wBAAwB,SAAS,MAAM,IAGvC,OAAO,KAAK,SAAS,MAAoC,IACzD,OAAO,KAAK,CAClB,IACA,OAAO,KAAiC;CAC9C,OAAO,OAAO,MAAM,QAAQ;EAC1B,SAAS,iBAAiB,OAAO,QAAQ,YAAY;EACrD,cACE,QACE,QACA,UAAU,YAAY,OAAO,SAAS,KAAK,UAAU,SAAS,MAAM,OAAO,IACvE,MAAM,UACN,eACN;CACJ,CAAC;AACH,CAAC;;;;;;;;;;;;;;AAeH,MAAa,kBAA4B,UAAU;CACjD,IAAI,CAAC,UAAU,gBAAgB,KAAK,GAAG,OAAO,OAAO,KAAK;CAC1D,IAAI,UAAU,SAAS,OAAO,cAAc,GAAG,OAAO,iBAAiB,KAAK;CAC5E,IAAI,UAAU,SAAS,OAAO,eAAe,GAAG,OAAO,kBAAkB,KAAK;CAC9E,OAAO,UAAU,QAAQ,KAAK,IAC1B,OAAO,IAAI,SAAS,KAAK,IAAI,WAAW,QAAQ,QAAQ,MAAM,OAAO,CAAC,IACtE,OAAO,KAAK;AAClB"}
@@ -1,6 +1,5 @@
1
1
  import { FormStore } from "../FormStore.svelte.js";
2
2
  import { Attachment } from "svelte/attachments";
3
- //#region src/kit/Enhance.d.ts
4
3
  /** The spreadable `<form>` props: the POST method plus the enhance attachment under its symbol key. */
5
4
  export interface EnhancedFormProps {
6
5
  readonly method: "post";
@@ -26,5 +25,4 @@ export interface EnhancedFormProps {
26
25
  * ```
27
26
  */
28
27
  export declare function enhanceForm(form: FormStore<any, any, any>): EnhancedFormProps;
29
- //#endregion
30
28
  //# sourceMappingURL=Enhance.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { applyActionResult } from "../SvelteKit.js";
2
2
  import { createAttachmentKey } from "svelte/attachments";
3
3
  import { enhance } from "$app/forms";
4
- //#region src/kit/Enhance.ts
4
+ // src/kit/Enhance.ts
5
5
  /**
6
6
  * SvelteKit `$app/forms` glue for the form store. {@link enhanceForm}
7
7
  * returns spreadable `<form>` props — `<form {...enhanceForm(form)}>` — that
@@ -51,7 +51,6 @@ function enhanceForm(form) {
51
51
  }
52
52
  };
53
53
  }
54
- //#endregion
55
54
  export { enhanceForm };
56
55
 
57
56
  //# sourceMappingURL=Enhance.js.map
@@ -3,7 +3,6 @@ import { Form, Result, State } from "@thomasfosterau/effect-forms";
3
3
  import * as Schema from "effect/Schema";
4
4
  import * as Effect from "effect/Effect";
5
5
  import { RequestEvent } from "@sveltejs/kit";
6
- //#region src/kit/FormAction.d.ts
7
6
  type FormState<A, Message = unknown> = State.FormState<A, Message>;
8
7
  type FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;
9
8
  type FormActionData<A = unknown, Message = unknown> = Result.FormActionData<A, Message>;
@@ -127,6 +126,5 @@ export type ActionCommand<I, O> = (input: I, event: RequestEvent) => Promise<O>
127
126
  * ```
128
127
  */
129
128
  export declare function formAction<S extends Schema.Top, O, Message = unknown>(form: Form.Form<S, Message>, run: ActionCommand<S["Type"], O>, locationFor: (output: O, input: S["Type"]) => string, options?: FormActionOptions<S["Type"], O>): (event: RequestEvent) => Promise<FormActionData<S["Type"], unknown> | import("@sveltejs/kit").ActionFailure<FormActionData<S["Type"], unknown>>>;
130
- //#endregion
131
129
  export { Command$1 as Command };
132
130
  //# sourceMappingURL=FormAction.d.ts.map
@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
8
8
  import { fail, redirect } from "@sveltejs/kit";
9
9
  import * as Cause from "effect/Cause";
10
10
  import * as Exit from "effect/Exit";
11
- //#region src/kit/FormAction.ts
11
+ // src/kit/FormAction.ts
12
12
  /**
13
13
  * Derive a SvelteKit page form action from an `@thomasfosterau/effect-forms`
14
14
  * form plus the command it submits to.
@@ -115,6 +115,8 @@ async function runFormAction(form, request, run, locationFor, options = {}) {
115
115
  if (Exit.isSuccess(exit)) return exit.value;
116
116
  throw Cause.squash(exit.cause);
117
117
  }
118
+ /** Pin an action's return to {@link FormActionData}, so every branch types alike. */
119
+ const actionData = (data) => data;
118
120
  /**
119
121
  * Derive a SvelteKit page form **action** from `form` plus its command. The
120
122
  * handler validates the submission, runs the command on success, and maps
@@ -155,16 +157,14 @@ async function runFormAction(form, request, run, locationFor, options = {}) {
155
157
  function formAction(form, run, locationFor, options) {
156
158
  return async (event) => {
157
159
  const result = await runFormAction(form, event.request, (input) => run(input, event), locationFor, options);
158
- const data = (d) => d;
159
160
  return Match.valueTags(result, {
160
161
  Redirect: (redirectResult) => redirect(redirectResult.status, redirectResult.location),
161
- Failure: (failure) => fail(failure.status, data({ form: failure.state })),
162
- Error: (error) => fail(error.status, data({ message: error.message })),
163
- Success: (success) => data({ form: success.state })
162
+ Failure: (failure) => fail(failure.status, actionData({ form: failure.state })),
163
+ Error: (error) => fail(error.status, actionData({ message: error.message })),
164
+ Success: (success) => actionData({ form: success.state })
164
165
  });
165
166
  };
166
167
  }
167
- //#endregion
168
168
  export { formAction, idempotentCreate, runFormAction };
169
169
 
170
170
  //# sourceMappingURL=FormAction.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormAction.js","names":[],"sources":["../../src/kit/FormAction.ts"],"sourcesContent":["/**\n * Derive a SvelteKit page form action from an `@thomasfosterau/effect-forms`\n * form plus the command it submits to.\n *\n * {@link runFormAction} is the framework-neutral seam — validate the request\n * against the form's schema (every field error collected, posted values\n * retained), run the command on a valid submission, and describe the outcome\n * as a `Result.FormActionResult`. {@link formAction} maps that onto\n * SvelteKit's action protocol (`redirect()` / `fail()`), which the client\n * store applies back via `applyActionResult` / {@link enhanceForm}.\n *\n * A command is either a Promise-returning function (an API client call) or\n * an `Effect` with no requirements; a failure on either is classified by\n * {@link CommandFailure.jsonapiFailure} (or a caller's own classifier) and\n * routed onto the form's fields by each JSON:API error's `source.pointer`.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/kit/FormAction\n */\n\nimport { fail, redirect, type RequestEvent } from \"@sveltejs/kit\";\nimport * as Cause from \"effect/Cause\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport * as Match from \"effect/Match\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport type * as Schema from \"effect/Schema\";\nimport { type Form, Result, State } from \"@thomasfosterau/effect-forms\";\nimport {\n type Classify,\n type CommandFailure,\n DEFAULT_MESSAGE,\n detailOf,\n type ErrorObject,\n jsonapiFailure,\n} from \"./CommandFailure.ts\";\nimport { fieldFromPointer } from \"./Pointer.ts\";\n\ntype FormState<A, Message = unknown> = State.FormState<A, Message>;\ntype FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;\ntype FormActionData<A = unknown, Message = unknown> = Result.FormActionData<A, Message>;\n\n/**\n * A command: a Promise-returning function or one returning an `Effect` with\n * no requirements (provide them first, e.g. through a `ManagedRuntime`).\n */\nexport type Command<I, O> = (input: I) => Promise<O> | Effect.Effect<O, unknown>;\n\nconst toEffect = <O>(\n thunk: () => Promise<O> | Effect.Effect<O, unknown>,\n): Effect.Effect<O, unknown> =>\n Effect.suspend(() => {\n const out = thunk();\n return Effect.isEffect(out)\n ? out\n : Effect.tryPromise({ try: () => out, catch: (cause) => cause });\n });\n\n// ── Idempotent client-id create retry ──────────────────────────────────\n\nconst isConflict = (cause: unknown): boolean =>\n Predicate.isObjectOrArray(cause) &&\n Predicate.hasProperty(cause, \"status\") &&\n cause.status === 409 &&\n (Predicate.isTagged(cause, \"JsonapiError\") || Predicate.isTagged(cause, \"ErrorResponse\"));\n\nconst submittedId = (input: unknown): Option.Option<string> =>\n Predicate.isObjectOrArray(input) &&\n Predicate.hasProperty(input, \"id\") &&\n Predicate.isString(input.id) &&\n input.id !== \"\"\n ? Option.some(input.id)\n : Option.none();\n\nconst retryOwnConflict = <I, O>(\n create: Command<I, O>,\n fetchExisting: (id: string) => Promise<O> | Effect.Effect<O, unknown>,\n input: I,\n): Effect.Effect<O, unknown> =>\n toEffect(() => create(input)).pipe(\n Effect.catch((cause) =>\n Option.match(isConflict(cause) ? submittedId(input) : Option.none(), {\n onNone: () => Effect.fail(cause),\n // The re-fetch failing rethrows the ORIGINAL conflict, not its own error.\n onSome: (id) => toEffect(() => fetchExisting(id)).pipe(Effect.mapError(() => cause)),\n }),\n ),\n );\n\n/**\n * Wrap a client-id create command so a 409 conflict on the caller's OWN\n * submitted `id` resolves as success instead of a form error — the\n * double-submit of a create whose id the client generated.\n *\n * On any 409 (a `JsonapiError` / `ErrorResponse`-tagged cause), re-fetches\n * `input.id` via `fetchExisting` and returns that. This is deliberately\n * unconditional on *why* the 409 fired: the re-fetch is the proof, not the\n * status. A 409 from an unrelated precondition never left a row at\n * `input.id`, so the re-fetch fails and the original conflict rethrows\n * unchanged. An input with no `id`, or any other failure, rethrows as is.\n *\n * `input.id` is read structurally rather than through a constraint on\n * `Input`: a constraint here would be used only contravariantly (as\n * `create`'s parameter), which makes TypeScript infer `Input` from the\n * constraint instead of from the command's own input type.\n */\nexport function idempotentCreate<Input, Output>(\n create: (input: Input) => Promise<Output>,\n fetchExisting: (id: string) => Promise<Output>,\n): (input: Input) => Promise<Output> {\n return async (input) => {\n const exit = await Effect.runPromiseExit(retryOwnConflict(create, fetchExisting, input));\n if (Exit.isSuccess(exit)) return exit.value;\n throw Cause.squash(exit.cause);\n };\n}\n\n/**\n * Idempotent-retry wiring for a client-id create action: `clientId` gates the\n * wrap to commands whose input actually carries a client-generated `id` (an\n * entity form definition's own `clientId`, from `defineEntityForm`);\n * `fetchExisting` re-fetches the submitted id (see {@link idempotentCreate}).\n */\nexport interface IdempotentCreateOptions<O> {\n readonly clientId: boolean;\n readonly fetchExisting: (id: string) => Promise<O> | Effect.Effect<O, unknown>;\n}\n\n/** Options for {@link runFormAction} and {@link formAction}. */\nexport interface FormActionOptions<A, O> {\n /** Retry a client-id create's own 409 as success (see {@link IdempotentCreateOptions}). */\n readonly idempotent?: IdempotentCreateOptions<O> | undefined;\n /**\n * How a command failure is recognised. Defaults to\n * {@link CommandFailure.jsonapiFailure}; compose with it to add an\n * application's own failures.\n */\n readonly classify?: Classify | undefined;\n /**\n * Applied to every form state echoed back to the browser (an invalid\n * submission, a routed command failure) — e.g. blanking password fields\n * so a secret never rides a response body.\n */\n readonly redact?: ((state: FormState<A>) => FormState<A>) | undefined;\n /**\n * What happens to a command failure the classifier does not recognise:\n * `\"error\"` (the default) answers an `Error` result at 500 carrying the\n * cause's message; `\"throw\"` rethrows the cause, leaving it to SvelteKit's\n * `handleError`.\n */\n readonly unrecognised?: \"error\" | \"throw\" | undefined;\n}\n\n/** The field path an error object's pointer routes to, or `[]` (the root). */\nconst fieldPath = (error: ErrorObject): ReadonlyArray<string> =>\n Option.toArray(\n fieldFromPointer(\n error.source !== undefined && \"pointer\" in error.source ? error.source.pointer : undefined,\n ),\n );\n\n/**\n * Route a recognised command failure onto the form state. A root issue lands\n * at the empty path, which the nested error tree exposes as `_errors` — the\n * form-level message surface (`form.errors._errors`).\n */\nconst routeFailure = <A>(state: FormState<A>, failure: CommandFailure): FormState<A> =>\n Predicate.isTagged(failure, \"Routed\")\n ? failure.errors.reduce(\n (acc, error) => State.withError(acc, fieldPath(error), detailOf(error)),\n state,\n )\n : State.withError(state, [], failure.message);\n\n/**\n * Validate `request` against `form`, then:\n *\n * - invalid → a `Failure` carrying the form state (nested errors + posted\n * values), status 400, and the command never runs;\n * - valid → run the command (wrapped in the idempotent retry when\n * `options.idempotent.clientId` is true); on success a `Redirect` (303) to\n * `locationFor(output, input)`;\n * - a recognised command failure → a `Failure` at the failure's own status,\n * each JSON:API error routed onto its field (or the root);\n * - an unrecognised one → an `Error` result at 500 (or a rethrow, per\n * `options.unrecognised`).\n *\n * Posted values survive every failure, so the form re-renders what was typed.\n *\n * `locationFor` returns the redirect target as-is: `runFormAction` does not\n * check it. {@link formAction} hands it to SvelteKit's `redirect`, which\n * accepts only a same-origin location (see there).\n */\nexport async function runFormAction<S extends Schema.Top, O, Message = unknown>(\n form: Form.Form<S, Message>,\n request: Request,\n run: Command<S[\"Type\"], O>,\n locationFor: (output: O, input: S[\"Type\"]) => string,\n options: FormActionOptions<S[\"Type\"], O> = {},\n): Promise<FormActionResult<S[\"Type\"]>> {\n const redact = options.redact ?? ((state: FormState<S[\"Type\"]>) => state);\n const classify = options.classify ?? jsonapiFailure;\n // SAFETY: a posted form needs no decoding services at runtime; schemas\n // projected from elsewhere can still carry an unreduced `DecodingServices`\n // type, so only the requirements channel is narrowed here.\n const validate = form.validate(request) as Effect.Effect<FormState<S[\"Type\"], Message>>;\n const program = Effect.flatMap(\n validate,\n (state): Effect.Effect<FormActionResult<S[\"Type\"]>, unknown> => {\n if (!state.valid) return Effect.succeed(Result.failure(redact(state), 400));\n const idempotent = options.idempotent;\n const command =\n idempotent?.clientId === true\n ? retryOwnConflict(run, idempotent.fetchExisting, state.data)\n : toEffect(() => run(state.data));\n return Effect.matchCauseEffect(command, {\n onSuccess: (output) =>\n Effect.sync(() => Result.redirect(locationFor(output, state.data), 303)),\n onFailure: (commandCause) =>\n Effect.suspend(() => {\n const cause = Cause.squash(commandCause);\n return Option.match(classify(cause), {\n onSome: (failure) =>\n Effect.sync(() =>\n Result.failure(redact(routeFailure(state, failure)), failure.status),\n ),\n onNone: () =>\n options.unrecognised === \"throw\"\n ? Effect.fail(cause)\n : Effect.succeed(\n Result.errorResult(\n Predicate.isError(cause) ? cause.message : DEFAULT_MESSAGE,\n 500,\n ),\n ),\n });\n }),\n });\n },\n );\n // The Promise boundary: an unrecognised failure under `\"throw\"` (or a defect\n // anywhere above) rejects with the squashed cause, as a thrown value would.\n const exit = await Effect.runPromiseExit(program);\n if (Exit.isSuccess(exit)) return exit.value;\n throw Cause.squash(exit.cause);\n}\n\n/**\n * A command for {@link formAction}: like {@link Command}, and also handed the\n * `RequestEvent` (for `locals`, `cookies`, `fetch`).\n */\nexport type ActionCommand<I, O> = (\n input: I,\n event: RequestEvent,\n) => Promise<O> | Effect.Effect<O, unknown>;\n\n/**\n * Derive a SvelteKit page form **action** from `form` plus its command. The\n * handler validates the submission, runs the command on success, and maps\n * the outcome onto SvelteKit's action protocol:\n *\n * - success → `redirect(303, locationFor(output, input))`;\n * - an invalid submission or a recognised command failure →\n * `fail(status, { form })`, carrying the (redacted) state;\n * - an unrecognised failure → `fail(500, { message })` (or a rethrow).\n *\n * Wire it into `export const actions`; the client `createForm` store applies\n * the returned `form` via {@link enhanceForm} (or `applyActionResult`).\n *\n * `locationFor` must return a same-origin path, such as\n * `/organisations/${id}`. SvelteKit 3's `redirect` rejects an external\n * location unless it is passed `{ external }`, and `formAction` never passes\n * it, so a `locationFor` that returns an absolute URL on another origin (or a\n * protocol-relative `//host/...`) makes the action throw an `Error` instead of\n * redirecting, after the command has already run. To send the user off-site,\n * redirect to a same-origin route that performs the external redirect itself.\n *\n * @example\n * ```ts\n * // src/routes/organisations/create/+page.server.ts\n * import { formAction } from \"@thomasfosterau/effect-forms-svelte/kit\";\n * import { organisationForm } from \"#lib/forms\";\n * import { api } from \"#lib/server/api\";\n *\n * export const actions = {\n * default: formAction(\n * organisationForm,\n * (input, { locals }) => api(locals).organisations.create(input),\n * (id) => `/organisations/${id}`,\n * ),\n * };\n * ```\n */\nexport function formAction<S extends Schema.Top, O, Message = unknown>(\n form: Form.Form<S, Message>,\n run: ActionCommand<S[\"Type\"], O>,\n locationFor: (output: O, input: S[\"Type\"]) => string,\n options?: FormActionOptions<S[\"Type\"], O>,\n) {\n return async (event: RequestEvent) => {\n const result = await runFormAction(\n form,\n event.request,\n (input) => run(input, event),\n locationFor,\n options,\n );\n const data = (d: FormActionData<S[\"Type\"]>): FormActionData<S[\"Type\"]> => d;\n return Match.valueTags(result, {\n Redirect: (redirectResult) => redirect(redirectResult.status, redirectResult.location),\n Failure: (failure) => fail(failure.status, data({ form: failure.state })),\n Error: (error) => fail(error.status, data({ message: error.message })),\n // `runFormAction` answers success with a redirect; a success result\n // carrying state is echoed back as-is.\n Success: (success) => data({ form: success.state }),\n });\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FormAction.js","names":[],"sources":["../../src/kit/FormAction.ts"],"sourcesContent":["/**\n * Derive a SvelteKit page form action from an `@thomasfosterau/effect-forms`\n * form plus the command it submits to.\n *\n * {@link runFormAction} is the framework-neutral seam — validate the request\n * against the form's schema (every field error collected, posted values\n * retained), run the command on a valid submission, and describe the outcome\n * as a `Result.FormActionResult`. {@link formAction} maps that onto\n * SvelteKit's action protocol (`redirect()` / `fail()`), which the client\n * store applies back via `applyActionResult` / {@link enhanceForm}.\n *\n * A command is either a Promise-returning function (an API client call) or\n * an `Effect` with no requirements; a failure on either is classified by\n * {@link CommandFailure.jsonapiFailure} (or a caller's own classifier) and\n * routed onto the form's fields by each JSON:API error's `source.pointer`.\n *\n * @copyright Thomas Foster\n * @module effect-forms-svelte/kit/FormAction\n */\n\nimport { fail, redirect, type RequestEvent } from \"@sveltejs/kit\";\nimport * as Cause from \"effect/Cause\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport * as Match from \"effect/Match\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport type * as Schema from \"effect/Schema\";\nimport { type Form, Result, State } from \"@thomasfosterau/effect-forms\";\nimport {\n type Classify,\n type CommandFailure,\n DEFAULT_MESSAGE,\n detailOf,\n type ErrorObject,\n jsonapiFailure,\n} from \"./CommandFailure.ts\";\nimport { fieldFromPointer } from \"./Pointer.ts\";\n\ntype FormState<A, Message = unknown> = State.FormState<A, Message>;\ntype FormActionResult<A, Message = unknown> = Result.FormActionResult<A, Message>;\ntype FormActionData<A = unknown, Message = unknown> = Result.FormActionData<A, Message>;\n\n/**\n * A command: a Promise-returning function or one returning an `Effect` with\n * no requirements (provide them first, e.g. through a `ManagedRuntime`).\n */\nexport type Command<I, O> = (input: I) => Promise<O> | Effect.Effect<O, unknown>;\n\nconst toEffect = <O>(\n thunk: () => Promise<O> | Effect.Effect<O, unknown>,\n): Effect.Effect<O, unknown> =>\n Effect.suspend(() => {\n const out = thunk();\n return Effect.isEffect(out)\n ? out\n : Effect.tryPromise({ try: () => out, catch: (cause) => cause });\n });\n\n// ── Idempotent client-id create retry ──────────────────────────────────\n\nconst isConflict = (cause: unknown): boolean =>\n Predicate.isObjectOrArray(cause) &&\n Predicate.hasProperty(cause, \"status\") &&\n cause.status === 409 &&\n (Predicate.isTagged(cause, \"JsonapiError\") || Predicate.isTagged(cause, \"ErrorResponse\"));\n\nconst submittedId = (input: unknown): Option.Option<string> =>\n Predicate.isObjectOrArray(input) &&\n Predicate.hasProperty(input, \"id\") &&\n Predicate.isString(input.id) &&\n input.id !== \"\"\n ? Option.some(input.id)\n : Option.none();\n\nconst retryOwnConflict = <I, O>(\n create: Command<I, O>,\n fetchExisting: (id: string) => Promise<O> | Effect.Effect<O, unknown>,\n input: I,\n): Effect.Effect<O, unknown> =>\n toEffect(() => create(input)).pipe(\n Effect.catch((cause) =>\n Option.match(isConflict(cause) ? submittedId(input) : Option.none(), {\n onNone: () => Effect.fail(cause),\n // The re-fetch failing rethrows the ORIGINAL conflict, not its own error.\n onSome: (id) => toEffect(() => fetchExisting(id)).pipe(Effect.mapError(() => cause)),\n }),\n ),\n );\n\n/**\n * Wrap a client-id create command so a 409 conflict on the caller's OWN\n * submitted `id` resolves as success instead of a form error — the\n * double-submit of a create whose id the client generated.\n *\n * On any 409 (a `JsonapiError` / `ErrorResponse`-tagged cause), re-fetches\n * `input.id` via `fetchExisting` and returns that. This is deliberately\n * unconditional on *why* the 409 fired: the re-fetch is the proof, not the\n * status. A 409 from an unrelated precondition never left a row at\n * `input.id`, so the re-fetch fails and the original conflict rethrows\n * unchanged. An input with no `id`, or any other failure, rethrows as is.\n *\n * `input.id` is read structurally rather than through a constraint on\n * `Input`: a constraint here would be used only contravariantly (as\n * `create`'s parameter), which makes TypeScript infer `Input` from the\n * constraint instead of from the command's own input type.\n */\nexport function idempotentCreate<Input, Output>(\n create: (input: Input) => Promise<Output>,\n fetchExisting: (id: string) => Promise<Output>,\n): (input: Input) => Promise<Output> {\n return async (input) => {\n const exit = await Effect.runPromiseExit(retryOwnConflict(create, fetchExisting, input));\n if (Exit.isSuccess(exit)) return exit.value;\n throw Cause.squash(exit.cause);\n };\n}\n\n/**\n * Idempotent-retry wiring for a client-id create action: `clientId` gates the\n * wrap to commands whose input actually carries a client-generated `id` (an\n * entity form definition's own `clientId`, from `defineEntityForm`);\n * `fetchExisting` re-fetches the submitted id (see {@link idempotentCreate}).\n */\nexport interface IdempotentCreateOptions<O> {\n readonly clientId: boolean;\n readonly fetchExisting: (id: string) => Promise<O> | Effect.Effect<O, unknown>;\n}\n\n/** Options for {@link runFormAction} and {@link formAction}. */\nexport interface FormActionOptions<A, O> {\n /** Retry a client-id create's own 409 as success (see {@link IdempotentCreateOptions}). */\n readonly idempotent?: IdempotentCreateOptions<O> | undefined;\n /**\n * How a command failure is recognised. Defaults to\n * {@link CommandFailure.jsonapiFailure}; compose with it to add an\n * application's own failures.\n */\n readonly classify?: Classify | undefined;\n /**\n * Applied to every form state echoed back to the browser (an invalid\n * submission, a routed command failure) — e.g. blanking password fields\n * so a secret never rides a response body.\n */\n readonly redact?: ((state: FormState<A>) => FormState<A>) | undefined;\n /**\n * What happens to a command failure the classifier does not recognise:\n * `\"error\"` (the default) answers an `Error` result at 500 carrying the\n * cause's message; `\"throw\"` rethrows the cause, leaving it to SvelteKit's\n * `handleError`.\n */\n readonly unrecognised?: \"error\" | \"throw\" | undefined;\n}\n\n/** The field path an error object's pointer routes to, or `[]` (the root). */\nconst fieldPath = (error: ErrorObject): ReadonlyArray<string> =>\n Option.toArray(\n fieldFromPointer(\n error.source !== undefined && \"pointer\" in error.source ? error.source.pointer : undefined,\n ),\n );\n\n/**\n * Route a recognised command failure onto the form state. A root issue lands\n * at the empty path, which the nested error tree exposes as `_errors` — the\n * form-level message surface (`form.errors._errors`).\n */\nconst routeFailure = <A>(state: FormState<A>, failure: CommandFailure): FormState<A> =>\n Predicate.isTagged(failure, \"Routed\")\n ? failure.errors.reduce(\n (acc, error) => State.withError(acc, fieldPath(error), detailOf(error)),\n state,\n )\n : State.withError(state, [], failure.message);\n\n/**\n * Validate `request` against `form`, then:\n *\n * - invalid → a `Failure` carrying the form state (nested errors + posted\n * values), status 400, and the command never runs;\n * - valid → run the command (wrapped in the idempotent retry when\n * `options.idempotent.clientId` is true); on success a `Redirect` (303) to\n * `locationFor(output, input)`;\n * - a recognised command failure → a `Failure` at the failure's own status,\n * each JSON:API error routed onto its field (or the root);\n * - an unrecognised one → an `Error` result at 500 (or a rethrow, per\n * `options.unrecognised`).\n *\n * Posted values survive every failure, so the form re-renders what was typed.\n *\n * `locationFor` returns the redirect target as-is: `runFormAction` does not\n * check it. {@link formAction} hands it to SvelteKit's `redirect`, which\n * accepts only a same-origin location (see there).\n */\nexport async function runFormAction<S extends Schema.Top, O, Message = unknown>(\n form: Form.Form<S, Message>,\n request: Request,\n run: Command<S[\"Type\"], O>,\n locationFor: (output: O, input: S[\"Type\"]) => string,\n options: FormActionOptions<S[\"Type\"], O> = {},\n): Promise<FormActionResult<S[\"Type\"]>> {\n const redact = options.redact ?? ((state: FormState<S[\"Type\"]>) => state);\n const classify = options.classify ?? jsonapiFailure;\n // SAFETY: a posted form needs no decoding services at runtime; schemas\n // projected from elsewhere can still carry an unreduced `DecodingServices`\n // type, so only the requirements channel is narrowed here.\n const validate = form.validate(request) as Effect.Effect<FormState<S[\"Type\"], Message>>;\n const program = Effect.flatMap(\n validate,\n (state): Effect.Effect<FormActionResult<S[\"Type\"]>, unknown> => {\n if (!state.valid) return Effect.succeed(Result.failure(redact(state), 400));\n const idempotent = options.idempotent;\n const command =\n idempotent?.clientId === true\n ? retryOwnConflict(run, idempotent.fetchExisting, state.data)\n : toEffect(() => run(state.data));\n return Effect.matchCauseEffect(command, {\n onSuccess: (output) =>\n Effect.sync(() => Result.redirect(locationFor(output, state.data), 303)),\n onFailure: (commandCause) =>\n Effect.suspend(() => {\n const cause = Cause.squash(commandCause);\n return Option.match(classify(cause), {\n onSome: (failure) =>\n Effect.sync(() =>\n Result.failure(redact(routeFailure(state, failure)), failure.status),\n ),\n onNone: () =>\n options.unrecognised === \"throw\"\n ? Effect.fail(cause)\n : Effect.succeed(\n Result.errorResult(\n Predicate.isError(cause) ? cause.message : DEFAULT_MESSAGE,\n 500,\n ),\n ),\n });\n }),\n });\n },\n );\n // The Promise boundary: an unrecognised failure under `\"throw\"` (or a defect\n // anywhere above) rejects with the squashed cause, as a thrown value would.\n const exit = await Effect.runPromiseExit(program);\n if (Exit.isSuccess(exit)) return exit.value;\n throw Cause.squash(exit.cause);\n}\n\n/**\n * A command for {@link formAction}: like {@link Command}, and also handed the\n * `RequestEvent` (for `locals`, `cookies`, `fetch`).\n */\nexport type ActionCommand<I, O> = (\n input: I,\n event: RequestEvent,\n) => Promise<O> | Effect.Effect<O, unknown>;\n\n/** Pin an action's return to {@link FormActionData}, so every branch types alike. */\nconst actionData = <A>(data: FormActionData<A>): FormActionData<A> => data;\n\n/**\n * Derive a SvelteKit page form **action** from `form` plus its command. The\n * handler validates the submission, runs the command on success, and maps\n * the outcome onto SvelteKit's action protocol:\n *\n * - success → `redirect(303, locationFor(output, input))`;\n * - an invalid submission or a recognised command failure →\n * `fail(status, { form })`, carrying the (redacted) state;\n * - an unrecognised failure → `fail(500, { message })` (or a rethrow).\n *\n * Wire it into `export const actions`; the client `createForm` store applies\n * the returned `form` via {@link enhanceForm} (or `applyActionResult`).\n *\n * `locationFor` must return a same-origin path, such as\n * `/organisations/${id}`. SvelteKit 3's `redirect` rejects an external\n * location unless it is passed `{ external }`, and `formAction` never passes\n * it, so a `locationFor` that returns an absolute URL on another origin (or a\n * protocol-relative `//host/...`) makes the action throw an `Error` instead of\n * redirecting, after the command has already run. To send the user off-site,\n * redirect to a same-origin route that performs the external redirect itself.\n *\n * @example\n * ```ts\n * // src/routes/organisations/create/+page.server.ts\n * import { formAction } from \"@thomasfosterau/effect-forms-svelte/kit\";\n * import { organisationForm } from \"#lib/forms\";\n * import { api } from \"#lib/server/api\";\n *\n * export const actions = {\n * default: formAction(\n * organisationForm,\n * (input, { locals }) => api(locals).organisations.create(input),\n * (id) => `/organisations/${id}`,\n * ),\n * };\n * ```\n */\nexport function formAction<S extends Schema.Top, O, Message = unknown>(\n form: Form.Form<S, Message>,\n run: ActionCommand<S[\"Type\"], O>,\n locationFor: (output: O, input: S[\"Type\"]) => string,\n options?: FormActionOptions<S[\"Type\"], O>,\n) {\n return async (event: RequestEvent) => {\n const result = await runFormAction(\n form,\n event.request,\n (input) => run(input, event),\n locationFor,\n options,\n );\n return Match.valueTags(result, {\n Redirect: (redirectResult) => redirect(redirectResult.status, redirectResult.location),\n Failure: (failure) => fail(failure.status, actionData<S[\"Type\"]>({ form: failure.state })),\n Error: (error) => fail(error.status, actionData<S[\"Type\"]>({ message: error.message })),\n // `runFormAction` answers success with a redirect; a success result\n // carrying state is echoed back as-is.\n Success: (success) => actionData<S[\"Type\"]>({ form: success.state }),\n });\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,3 @@
1
- //#region src/kit/FormStateReaders.d.ts
2
1
  /**
3
2
  * Total readers over the form state a failed form action hands back on
4
3
  * `PageProps["form"]`.
@@ -54,5 +53,4 @@ export declare const rootIssuesOf: (state: ActionFormState | undefined) => Reado
54
53
  * the user sees what they sent.
55
54
  */
56
55
  export declare const valueOf: (state: ActionFormState | undefined, field: string, fallback?: string) => string;
57
- //#endregion
58
56
  //# sourceMappingURL=FormStateReaders.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import * as Predicate from "effect/Predicate";
2
- //#region src/kit/FormStateReaders.ts
2
+ // src/kit/FormStateReaders.ts
3
3
  /**
4
4
  * Total readers over the form state a failed form action hands back on
5
5
  * `PageProps["form"]`.
@@ -58,7 +58,6 @@ const valueOf = (state, field, fallback = "") => {
58
58
  if (value === void 0 || value === null) return fallback;
59
59
  return Predicate.isString(value) ? value : String(value);
60
60
  };
61
- //#endregion
62
61
  export { formStateOf, issuesFor, rootIssuesOf, valueOf };
63
62
 
64
63
  //# sourceMappingURL=FormStateReaders.js.map
@@ -1,5 +1,4 @@
1
1
  import * as Option from "effect/Option";
2
- //#region src/kit/Pointer.d.ts
3
2
  /**
4
3
  * The field a JSON Pointer of the form `/data/attributes/<field>…` names.
5
4
  * Deeper segments are ignored: the form issue layer keys by the top-level
@@ -19,5 +18,4 @@ import * as Option from "effect/Option";
19
18
  * ```
20
19
  */
21
20
  export declare const fieldFromPointer: (pointer: string | undefined) => Option.Option<string>;
22
- //#endregion
23
21
  //# sourceMappingURL=Pointer.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import * as Option from "effect/Option";
2
- //#region src/kit/Pointer.ts
2
+ // src/kit/Pointer.ts
3
3
  /**
4
4
  * JSON Pointer → form field routing for a JSON:API error object's
5
5
  * `source.pointer`.
@@ -32,7 +32,6 @@ const fieldFromPointer = (pointer) => {
32
32
  if (data !== "data" || attributes !== "attributes") return Option.none();
33
33
  return field === void 0 || field === "" ? Option.none() : Option.some(field);
34
34
  };
35
- //#endregion
36
35
  export { fieldFromPointer };
37
36
 
38
37
  //# sourceMappingURL=Pointer.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thomasfosterau/effect-forms-svelte",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Svelte 5 (runes) bindings for @thomasfosterau/effect-forms — a reactive form store, an enhance action, tainted tracking, and client-side validation.",
5
5
  "keywords": [
6
6
  "effect",
@@ -49,16 +49,16 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@thomasfosterau/effect-forms": "^0.3.1"
52
+ "@thomasfosterau/effect-forms": "^0.3.2"
53
53
  },
54
54
  "devDependencies": {
55
- "@sveltejs/kit": "3.1.0",
56
- "@thomasfosterau/effect-jsonapi": "^0.17.0"
55
+ "@sveltejs/kit": "3.1.1",
56
+ "@thomasfosterau/effect-jsonapi": "^0.20.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@sveltejs/kit": "^3.0.0",
60
60
  "@thomasfosterau/effect-jsonapi": ">=0.16.0",
61
- "effect": ">=4.0.0",
61
+ "effect": "^4.0.0",
62
62
  "svelte": "^5.40.0"
63
63
  },
64
64
  "peerDependenciesMeta": {