@thomasfosterau/effect-forms-svelte 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +137 -23
  2. package/dist/ClientValidate.d.ts +3 -3
  3. package/dist/ClientValidate.js +1 -2
  4. package/dist/ClientValidate.js.map +1 -1
  5. package/dist/EnhanceCore.d.ts +41 -10
  6. package/dist/EnhanceCore.js +107 -40
  7. package/dist/EnhanceCore.js.map +1 -1
  8. package/dist/EntityForm.d.ts +17 -18
  9. package/dist/EntityForm.js +28 -28
  10. package/dist/EntityForm.js.map +1 -1
  11. package/dist/FormStore.svelte.d.ts +25 -18
  12. package/dist/FormStore.svelte.js +95 -62
  13. package/dist/FormStore.svelte.js.map +1 -1
  14. package/dist/Listeners.d.ts +6 -6
  15. package/dist/Listeners.js +3 -2
  16. package/dist/Listeners.js.map +1 -1
  17. package/dist/SvelteKit.d.ts +6 -4
  18. package/dist/SvelteKit.js +5 -2
  19. package/dist/SvelteKit.js.map +1 -1
  20. package/dist/Tainted.d.ts +7 -7
  21. package/dist/Tainted.js +27 -39
  22. package/dist/Tainted.js.map +1 -1
  23. package/dist/_virtual/_rolldown/runtime.js +13 -0
  24. package/dist/kit/ActionRedirect.d.ts +22 -0
  25. package/dist/kit/ActionRedirect.js +54 -0
  26. package/dist/kit/ActionRedirect.js.map +1 -0
  27. package/dist/kit/CommandFailure.d.ts +52 -0
  28. package/dist/kit/CommandFailure.js +81 -0
  29. package/dist/kit/CommandFailure.js.map +1 -0
  30. package/dist/kit/Enhance.d.ts +30 -0
  31. package/dist/kit/Enhance.js +57 -0
  32. package/dist/kit/Enhance.js.map +1 -0
  33. package/dist/kit/FormAction.d.ts +132 -0
  34. package/dist/kit/FormAction.js +170 -0
  35. package/dist/kit/FormAction.js.map +1 -0
  36. package/dist/kit/FormStateReaders.d.ts +58 -0
  37. package/dist/kit/FormStateReaders.js +64 -0
  38. package/dist/kit/FormStateReaders.js.map +1 -0
  39. package/dist/kit/Pointer.d.ts +23 -0
  40. package/dist/kit/Pointer.js +38 -0
  41. package/dist/kit/Pointer.js.map +1 -0
  42. package/dist/kit/index.d.ts +7 -0
  43. package/dist/kit/index.js +7 -0
  44. package/package.json +23 -4
@@ -2,10 +2,10 @@ import { Command } from "@thomasfosterau/effect-forms";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { Component } from "svelte";
4
4
  //#region src/EntityForm.d.ts
5
- /** A render-time message thunk — pass the Paraglide function itself, uncalled. */
6
- type MessageThunk = () => string;
5
+ /** A render-time message thunk — pass the message function itself, uncalled. */
6
+ export type MessageThunk = () => string;
7
7
  /** Presentation options for a scalar text entry. */
8
- interface TextEntryOptions {
8
+ export interface TextEntryOptions {
9
9
  readonly label: MessageThunk;
10
10
  /** Presentation `type` attribute (`url`, `date`). Omitted → text input. */
11
11
  readonly type?: "text" | "url" | "date";
@@ -16,14 +16,14 @@ interface TextEntryOptions {
16
16
  readonly placeholder?: MessageThunk;
17
17
  }
18
18
  /** A scalar field rendered by the shared text/textarea primitives. */
19
- interface ScalarEntry {
19
+ export interface ScalarEntry {
20
20
  readonly _tag: "scalar";
21
21
  readonly key: string;
22
22
  readonly multiline: boolean;
23
23
  readonly options: TextEntryOptions;
24
24
  }
25
25
  /** Presentation options for a `<select>` entry over a literal-union (`enum` kind) field. */
26
- interface SelectEntryOptions {
26
+ export interface SelectEntryOptions {
27
27
  readonly label: MessageThunk;
28
28
  readonly required?: boolean;
29
29
  /**
@@ -40,23 +40,23 @@ interface SelectEntryOptions {
40
40
  * per-option labels — `defineEntityForm` validates the field is `enum`-kind
41
41
  * and that every derived value has a label.
42
42
  */
43
- interface SelectEntry {
43
+ export interface SelectEntry {
44
44
  readonly _tag: "select";
45
45
  readonly key: string;
46
46
  readonly options: SelectEntryOptions;
47
47
  }
48
48
  /** Options for a widget entry (picker / rows list). */
49
- interface WidgetEntryOptions {
49
+ export interface WidgetEntryOptions {
50
50
  /** Gate the submit button while this field's value is empty (required pickers). */
51
51
  readonly gate?: boolean;
52
52
  }
53
53
  /**
54
54
  * A bespoke widget field. The renderer bridges the store to the widget through
55
- * the stage-1 wrapper contract — `name`, `value`, `onChange`, `errors` — which
55
+ * the widget contract — `name`, `value`, `onChange`, `errors` — which
56
56
  * are **reserved keys**: the bridge is spread last, so a `props()` thunk or a
57
57
  * load-supplied field prop can never silently sever the store binding.
58
58
  */
59
- interface WidgetEntry {
59
+ export interface WidgetEntry {
60
60
  readonly _tag: "widget";
61
61
  readonly key: string;
62
62
  /** `"value"` bridges a string FK; `"rows"` bridges a controlled row list. */
@@ -66,18 +66,18 @@ interface WidgetEntry {
66
66
  readonly options: WidgetEntryOptions;
67
67
  }
68
68
  /** One ordered entry in an entity form definition. */
69
- type EntityFormEntry = ScalarEntry | WidgetEntry | SelectEntry;
69
+ export type EntityFormEntry = ScalarEntry | WidgetEntry | SelectEntry;
70
70
  /** The per-mode submit labels. */
71
- interface SubmitMessages {
71
+ export interface SubmitMessages {
72
72
  readonly create: MessageThunk;
73
73
  readonly update: MessageThunk;
74
74
  }
75
75
  /** The runtime shape `defineEntityForm` accepts for a command struct. */
76
- interface CommandStructLike {
76
+ export interface CommandStructLike {
77
77
  readonly fields: Record<string, Schema.Top>;
78
78
  }
79
79
  /** The value `defineEntityForm` returns — everything one renderer needs. */
80
- interface EntityFormDefinition {
80
+ export interface EntityFormDefinition {
81
81
  readonly plan: Command.CommandPlan;
82
82
  readonly entries: readonly EntityFormEntry[];
83
83
  readonly createInput: Schema.Top;
@@ -95,9 +95,9 @@ interface EntityFormDefinition {
95
95
  readonly fieldFor: (key: string) => Command.FieldPlan;
96
96
  }
97
97
  /** The writable command keys a definition may declare (the `id` key is renderer-owned). */
98
- type CommandKeys<C extends CommandStructLike, U extends CommandStructLike> = Exclude<Extract<keyof C["fields"] | keyof U["fields"], string>, "id">;
98
+ export type CommandKeys<C extends CommandStructLike, U extends CommandStructLike> = Exclude<Extract<keyof C["fields"] | keyof U["fields"], string>, "id">;
99
99
  /** The entry builders handed to the definition callback, key-typed to the command pair. */
100
- interface EntryBuilders<K extends string> {
100
+ export interface EntryBuilders<K extends string> {
101
101
  /** A scalar field rendered by the shared `TextInputField`. */
102
102
  text(key: K, options: TextEntryOptions): EntityFormEntry;
103
103
  /** A scalar field rendered by the shared `TextAreaInputField`. */
@@ -122,7 +122,7 @@ interface EntryBuilders<K extends string> {
122
122
  rows<P extends Record<string, unknown>>(key: K, component: Component<never>, props: () => P, options?: WidgetEntryOptions): EntityFormEntry;
123
123
  }
124
124
  /** Everything `defineEntityForm` needs beyond the entries. */
125
- interface DefineEntityFormOptions<C extends CommandStructLike, U extends CommandStructLike> {
125
+ export interface DefineEntityFormOptions<C extends CommandStructLike, U extends CommandStructLike> {
126
126
  readonly create: C;
127
127
  readonly update: U;
128
128
  readonly submit: SubmitMessages;
@@ -152,7 +152,6 @@ interface DefineEntityFormOptions<C extends CommandStructLike, U extends Command
152
152
  * the derivable command fields exactly (exhaustiveness) or name an unknown
153
153
  * key — a command-struct change must surface here, at definition time.
154
154
  */
155
- declare const defineEntityForm: <C extends CommandStructLike, U extends CommandStructLike>(options: DefineEntityFormOptions<C, U>, build: (f: EntryBuilders<CommandKeys<C, U>>) => readonly EntityFormEntry[]) => EntityFormDefinition;
155
+ export declare const defineEntityForm: <C extends CommandStructLike, U extends CommandStructLike>(options: DefineEntityFormOptions<C, U>, build: (f: EntryBuilders<CommandKeys<C, U>>) => readonly EntityFormEntry[]) => EntityFormDefinition;
156
156
  //#endregion
157
- export { CommandKeys, CommandStructLike, DefineEntityFormOptions, EntityFormDefinition, EntityFormEntry, EntryBuilders, MessageThunk, ScalarEntry, SelectEntry, SelectEntryOptions, SubmitMessages, TextEntryOptions, WidgetEntry, WidgetEntryOptions, defineEntityForm };
158
157
  //# sourceMappingURL=EntityForm.d.ts.map
@@ -1,4 +1,10 @@
1
1
  import { Command } from "@thomasfosterau/effect-forms";
2
+ import * as Arr from "effect/Array";
3
+ import * as Predicate from "effect/Predicate";
4
+ import * as Record from "effect/Record";
5
+ import * as HashMap from "effect/HashMap";
6
+ import * as HashSet from "effect/HashSet";
7
+ import * as Option from "effect/Option";
2
8
  import * as Schema from "effect/Schema";
3
9
  //#region src/EntityForm.ts
4
10
  const builders = {
@@ -37,15 +43,22 @@ const builders = {
37
43
  })
38
44
  };
39
45
  /**
46
+ * A definition-time error: the entries don't match the command structs. Thrown
47
+ * synchronously from `defineEntityForm` (and `fieldFor`), never as an Effect
48
+ * failure — a definition module that doesn't match its commands must fail at
49
+ * import.
50
+ */
51
+ var EntityFormDefinitionError = class extends Schema.TaggedError()("EntityFormDefinitionError", { message: Schema.String }) {};
52
+ /**
40
53
  * Validate an `f.select` entry against its plan field at definition time: the
41
54
  * field must be `enum`-kind (the only kind `Command.plan` derives option
42
55
  * values for), and every derived value must have a matching `optionLabels`
43
56
  * thunk.
44
57
  */
45
58
  const validateSelectEntry = (entry, field) => {
46
- if (field.kind !== "enum") throw new Error(`defineEntityForm: entry "${entry.key}" is f.select but its command field is "${field.kind}"-kind, not enum`);
59
+ if (field.kind !== "enum") throw new EntityFormDefinitionError({ message: `defineEntityForm: entry "${entry.key}" is f.select but its command field is "${field.kind}"-kind, not enum` });
47
60
  const missingLabels = (field.options ?? []).filter((value) => !Object.prototype.hasOwnProperty.call(entry.options.optionLabels, value));
48
- if (missingLabels.length > 0) throw new Error(`defineEntityForm: entry "${entry.key}" is missing optionLabels for: ${missingLabels.join(", ")}`);
61
+ if (Arr.isReadonlyArrayNonEmpty(missingLabels)) throw new EntityFormDefinitionError({ message: `defineEntityForm: entry "${entry.key}" is missing optionLabels for: ${missingLabels.join(", ")}` });
49
62
  };
50
63
  /**
51
64
  * Build an entity-form definition from a command-struct pair and an ordered
@@ -54,30 +67,21 @@ const validateSelectEntry = (entry, field) => {
54
67
  * key — a command-struct change must surface here, at definition time.
55
68
  */
56
69
  const defineEntityForm = (options, build) => {
57
- const omitted = new Set(options.omit ?? []);
58
- const withoutOmitted = (struct) => {
59
- if (omitted.size === 0) return struct;
60
- const fields = {};
61
- for (const key in struct.fields) if (!omitted.has(key)) fields[key] = struct.fields[key];
62
- return { fields };
63
- };
70
+ const omitted = HashSet.fromIterable(options.omit ?? []);
71
+ const withoutOmitted = (struct) => HashSet.size(omitted) === 0 ? struct : { fields: Record.filter(struct.fields, (_, key) => !HashSet.has(omitted, key)) };
64
72
  const plan = Command.plan(withoutOmitted(options.create), withoutOmitted(options.update));
65
73
  const entries = build(builders);
66
- const byKey = new Map(plan.fields.map((field) => [field.key, field]));
67
- const declared = /* @__PURE__ */ new Set();
68
- for (const entry of entries) {
69
- const field = byKey.get(entry.key);
70
- if (field === void 0) throw new Error(`defineEntityForm: entry "${entry.key}" is not a field of either command struct`);
71
- if (declared.has(entry.key)) throw new Error(`defineEntityForm: entry "${entry.key}" is declared twice`);
72
- declared.add(entry.key);
73
- if (entry._tag === "select") validateSelectEntry(entry, field);
74
- }
75
- const missing = plan.fields.filter((field) => !declared.has(field.key)).map((field) => field.key);
76
- if (missing.length > 0) throw new Error(`defineEntityForm: command fields not declared by any entry: ${missing.join(", ")} — declare or explicitly widget them`);
77
- const entriesFor = (mode) => entries.filter((entry) => {
78
- const field = byKey.get(entry.key);
79
- return field !== void 0 && (mode === "create" ? field.inCreate : field.inUpdate);
74
+ const byKey = HashMap.fromIterable(Arr.map(plan.fields, (field) => [field.key, field]));
75
+ const declared = Arr.reduce(entries, HashSet.empty(), (declared, entry) => {
76
+ const field = HashMap.get(byKey, entry.key);
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` });
79
+ if (Predicate.isTagged(entry, "select")) validateSelectEntry(entry, field.value);
80
+ return HashSet.add(declared, entry.key);
80
81
  });
82
+ const missing = Arr.map(Arr.filter(plan.fields, (field) => !HashSet.has(declared, field.key)), (field) => field.key);
83
+ if (Arr.isReadonlyArrayNonEmpty(missing)) throw new EntityFormDefinitionError({ message: `defineEntityForm: command fields not declared by any entry: ${missing.join(", ")} — declare or explicitly widget them` });
84
+ const entriesFor = (mode) => Arr.filter(entries, (entry) => Option.exists(HashMap.get(byKey, entry.key), (field) => mode === "create" ? field.inCreate : field.inUpdate));
81
85
  return {
82
86
  plan,
83
87
  entries,
@@ -90,11 +94,7 @@ const defineEntityForm = (options, build) => {
90
94
  submit: options.submit,
91
95
  clientId: plan.clientId,
92
96
  entriesFor,
93
- fieldFor: (key) => {
94
- const field = byKey.get(key);
95
- if (field === void 0) throw new Error(`EntityFormDefinition: unknown field "${key}"`);
96
- return field;
97
- }
97
+ fieldFor: (key) => Option.getOrThrowWith(HashMap.get(byKey, key), () => new EntityFormDefinitionError({ message: `EntityFormDefinition: unknown field "${key}"` }))
98
98
  };
99
99
  };
100
100
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"EntityForm.js","names":[],"sources":["../src/EntityForm.ts"],"sourcesContent":["/**\n * @copyright Thomas Foster\n *\n * The typed entity-form definition builder — the composition layer of the\n * schema-driven form phase. `defineEntityForm(commands, (f) => [...])` pairs a\n * `Command.plan` derivation (form-input schemas, blank defaults, tri-state\n * adapters — see `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 * Paraglide message function references) resolved at render time, never at\n * module scope — module-scope resolution would freeze the import-time locale.\n *\n * @module effect-forms-svelte/EntityForm\n */\n\nimport type { Component } from \"svelte\";\nimport { Command } from \"@thomasfosterau/effect-forms\";\nimport * as Schema from \"effect/Schema\";\n\n/** A render-time message thunk — pass the Paraglide 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 stage-1 wrapper 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 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 component: component as Component<any>,\n props,\n options,\n }),\n};\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 Error(\n `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 (missingLabels.length > 0) {\n throw new Error(\n `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 = new Set<string>(options.omit ?? []);\n const withoutOmitted = (struct: CommandStructLike): CommandStructLike => {\n if (omitted.size === 0) return struct;\n const fields: Record<string, Schema.Top> = {};\n for (const key in struct.fields) if (!omitted.has(key)) fields[key] = struct.fields[key];\n return { fields };\n };\n const plan = Command.plan(withoutOmitted(options.create), withoutOmitted(options.update));\n const entries = build(builders as EntryBuilders<CommandKeys<C, U>>);\n\n const byKey = new Map<string, Command.FieldPlan>(plan.fields.map((field) => [field.key, field]));\n const declared = new Set<string>();\n for (const entry of entries) {\n const field = byKey.get(entry.key);\n if (field === undefined) {\n throw new Error(\n `defineEntityForm: entry \"${entry.key}\" is not a field of either command struct`,\n );\n }\n if (declared.has(entry.key)) {\n throw new Error(`defineEntityForm: entry \"${entry.key}\" is declared twice`);\n }\n declared.add(entry.key);\n if (entry._tag === \"select\") validateSelectEntry(entry, field);\n }\n const missing = plan.fields.filter((field) => !declared.has(field.key)).map((field) => field.key);\n if (missing.length > 0) {\n throw new Error(\n `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 entries.filter((entry) => {\n const field = byKey.get(entry.key);\n return field !== undefined && (mode === \"create\" ? field.inCreate : field.inUpdate);\n });\n\n return {\n plan,\n entries,\n createInput: Schema.Struct(\n Command.formInputFields(plan, \"create\", options.inputOverrides?.create) as Record<\n string,\n never\n >,\n ),\n updateInput: Schema.Struct(\n Command.formInputFields(plan, \"update\", options.inputOverrides?.update) as Record<\n string,\n never\n >,\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 const field = byKey.get(key);\n if (field === undefined) throw new Error(`EntityFormDefinition: unknown field \"${key}\"`);\n return field;\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>(), (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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"}
@@ -14,11 +14,11 @@ type InputConstraint = Constraints.InputConstraint;
14
14
  type InputConstraints = Constraints.InputConstraints;
15
15
  type LeafFormatter = Issues.LeafFormatter;
16
16
  /** How a form validates (and optionally auto-submits, and revalidates after a post) as the user types. */
17
- type FormMode = Mode.FormMode;
17
+ export type FormMode = Mode.FormMode;
18
18
  /** A field's sync / async validators — see {@link module:effect-forms/Validate}. */
19
- type FieldValidators<A, R = never> = Validate.FieldValidators<A, R>;
19
+ export type FieldValidators<A, R = never> = Validate.FieldValidators<A, R>;
20
20
  /** Per-field state exposed on a {@link FieldHandle}, mirroring TanStack Form's field meta. */
21
- interface FieldMeta {
21
+ export interface FieldMeta {
22
22
  /** Changed at some point, or blurred (TanStack: `isTouched = isDirty || isBlurred`). */
23
23
  readonly isTouched: boolean;
24
24
  /** Has lost focus at least once — distinct from {@link isTouched}. */
@@ -41,7 +41,7 @@ interface FieldMeta {
41
41
  * an untyped {@link FormPath}. See {@link Path.FieldPath} for how the path is
42
42
  * type-checked.
43
43
  */
44
- declare class FieldHandle<V> {
44
+ export declare class FieldHandle<V> {
45
45
  #private;
46
46
  /** The path in flat field-name notation — use as the input's `name` attribute. */
47
47
  readonly name: string;
@@ -61,9 +61,9 @@ declare class FieldHandle<V> {
61
61
  handleBlur: () => void;
62
62
  }
63
63
  /** Transport: turn a submission into a {@link FormActionResult}. Defaults to `fetch`. */
64
- type SubmitFn<A, Message> = (body: FormData, action: string, form: HTMLFormElement) => Promise<FormActionResult<A, Message>>;
64
+ export type SubmitFn<A, Message> = (body: FormData, action: string, form: HTMLFormElement) => Promise<FormActionResult<A, Message>>;
65
65
  /** The submission passed to an {@link FormOptions.onSubmit} handler. */
66
- interface Submission<A, Message> {
66
+ export interface Submission<A, Message> {
67
67
  /** The current (reactive) form data. */
68
68
  readonly data: A;
69
69
  /** The multipart body that would be posted. */
@@ -87,7 +87,7 @@ interface Submission<A, Message> {
87
87
  readonly resolve: (result: FormActionResult<A, Message>) => void;
88
88
  }
89
89
  /** Options for {@link createForm}. */
90
- interface FormOptions<S extends Schema.Top, A, Message, R = never> {
90
+ export interface FormOptions<S extends Schema.Top, A, Message, R = never> {
91
91
  /** The schema to validate against in the browser (defaults to the bound schema). */
92
92
  readonly schema?: S;
93
93
  /**
@@ -108,7 +108,9 @@ interface FormOptions<S extends Schema.Top, A, Message, R = never> {
108
108
  /**
109
109
  * The builder itself, set automatically when {@link createForm} is given a
110
110
  * {@link FormBuilder}. Its `refineEffect` refinements run client-side as a
111
- * whole-form async validator whenever {@link FormStore.validate} runs.
111
+ * whole-form async validator whenever {@link FormStore.validate} runs, on the
112
+ * form data decoded through the builder's schema (as on the server); while
113
+ * the data doesn't decode they don't run, and the field errors say why.
112
114
  */
113
115
  readonly builder?: FormBuilder.FormBuilder<Field.FieldsRecord, R>;
114
116
  /** The action URL the form posts to (defaults to the current page's action). */
@@ -182,7 +184,7 @@ interface FormOptions<S extends Schema.Top, A, Message, R = never> {
182
184
  readonly listeners?: FormListeners;
183
185
  }
184
186
  /** A reactive form, returned by {@link createForm}. */
185
- declare class FormStore<S extends Schema.Top, A = S["Type"], Message = unknown, R = never> {
187
+ export declare class FormStore<S extends Schema.Top, A = S["Type"], Message = unknown, R = never> {
186
188
  #private;
187
189
  /** The reactive form data — bind inputs to its fields. */
188
190
  data: A;
@@ -346,11 +348,16 @@ declare class FormStore<S extends Schema.Top, A = S["Type"], Message = unknown,
346
348
  enhance: (node: HTMLFormElement) => {
347
349
  destroy: () => void;
348
350
  };
349
- /** Build a {@link FormData} body from the current data (SPA / manual submit). */
351
+ /**
352
+ * Build a {@link FormData} body from the current data (SPA / manual submit).
353
+ *
354
+ * Each value is encoded as `objectToFormData` encodes it: a `File` or
355
+ * `Blob` is appended as a file and a `Date` as its UTC `YYYY-MM-DD`.
356
+ */
350
357
  toFormData(): FormData;
351
358
  }
352
- /** A schema plus its blank-form defaults — what {@link createForm} wraps `makeForm` over. */
353
- interface FormSeed<S extends Schema.Top> {
359
+ /** A schema plus its blank-form defaults — what {@link createForm} wraps `Form.make` over. */
360
+ export interface FormSeed<S extends Schema.Top> {
354
361
  readonly schema: S;
355
362
  readonly defaults: S["Type"];
356
363
  readonly id?: string;
@@ -358,7 +365,7 @@ interface FormSeed<S extends Schema.Top> {
358
365
  /** The built-schema type a {@link FormBuilder} of `TFields` / `R` collapses to. */
359
366
  type BuiltSchema<TFields extends Field.FieldsRecord, R> = Schema.Codec<Field.DecodedFromFields<TFields>, Field.EncodedFromFields<TFields>, R>;
360
367
  /** Options for the {@link createForm} {@link FormBuilder} overload. */
361
- type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> = FormOptions<BuiltSchema<TFields, R>, Field.EncodedFromFields<TFields>, Message, R> & {
368
+ export type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> = FormOptions<BuiltSchema<TFields, R>, Field.EncodedFromFields<TFields>, Message, R> & {
362
369
  /** Blank-form values. Defaults to a schema-derived encoded blank per field. */
363
370
  readonly defaults?: Field.EncodedFromFields<TFields>;
364
371
  /** Distinguishes multiple forms on a page. Defaults to `"default"`. */
@@ -370,12 +377,12 @@ type FormBuilderOptions<TFields extends Field.FieldsRecord, R, Message> = FormOp
370
377
  * - a {@link FormBuilder} — the schema, blank defaults, and array-item defaults
371
378
  * are all derived from the builder (client validation, including
372
379
  * `refineEffect` refinements, is wired automatically);
373
- * - `{ schema, defaults }` — seed a blank form (wrapping the core's `makeForm`);
380
+ * - `{ schema, defaults }` — seed a blank form (wrapping the core's `Form.make`);
374
381
  * - a server-produced {@link FormState} — hydrate from it (schema in `options`).
375
382
  */
376
- 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>;
377
- 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>;
378
- 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>;
383
+ 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
+ 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
+ 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>;
379
386
  //#endregion
380
- export { FieldHandle, FieldMeta, FieldValidators, FormBuilderOptions, FormMode, FormOptions, FormSeed, FormStore, Submission, SubmitFn, createForm };
387
+ export {};
381
388
  //# sourceMappingURL=FormStore.svelte.d.ts.map