You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Svelte-Command-Form allows you to have easy to use forms with commands instead of remote forms. Is this redundant? Maybe. However, you may not want to use an HTML form everytime. The API is greatly influenced by SvelteKit-Superforms, so if you are used to that you shouldn't have a problem here.
4
4
5
+
Whenever possible you should use the SvelteKit provided `form` remote function since commands will fail in non-JS environments, but there may be cases where that is not practical or you just like the ease of interacting with an object instead of form data.
6
+
5
7
## Features
6
8
7
-
-**Schema-agnostic validation** – Works with any library that implements the Standard Schema v1 interface (Zod, Valibot, TypeBox, custom validators, …).
8
-
-**Command-first workflow** – Wire forms directly to your remote command ([`command` from `$app/server`](https://kit.svelte.dev/docs/load#command-functions)), and let the helper manage submission, success, and error hooks.
9
+
-**Schema-agnostic validation** – Works with any library that implements the [Standard Schema V1]("https://standardschema.dev/") interface. If you are unsure if your schema validation library is compatible see the list of [compatible libraries](https://standardschema.dev/#what-schema-libraries-implement-the-spec).
10
+
-**Command-first workflow** – Wire forms directly to your remote command ([`command` from `$app/server`](https://svelte.dev/docs/kit/remote-functions#command)), and let the helper manage submission, success, and error hooks.
9
11
-**Typed form state** – `form`, `errors`, and `issues` are all strongly typed from your schema, so your component code stays in sync with validation rules.
10
12
-**Friendly + raw errors** – Surface user-friendly `errors` for rendering, while also exposing the untouched validator `issues` array for logging/analytics.
11
13
-**Helpers for remote inputs** – Includes `normalizeFiles` for bundling file uploads and `standardValidate` for reusing schema validation outside the form class.
12
14
15
+
> Standard validate was yoinked straight from the `StandardSchema` GitHub
16
+
13
17
## Installation
14
18
15
19
```bash
@@ -102,59 +106,285 @@ const form = new CommandForm(userSchema, { command: saveUser });
Optional initial values. Returning a functions lets you compute defaults per form instance and/or when computed values change, like when using `$derived()`
140
+
141
+
> You must set default values here if you are using them, default values are not able to be extracted from a `StandardSchemaV1`
142
+
143
+
**Example:**
144
+
145
+
```html
146
+
<scriptlang="ts">
147
+
let { data } =$props();
148
+
let { name } =$derived(data);
149
+
150
+
constcmd=newCommandForm(schema, {
151
+
// if you do not use a function to get the value of name here
-`result` – Getter exposing the last command result (or `null`).
187
+
Optional SvelteKit invalidation targets. Can be set to a single string, a string[] for multiple targets, or a literal of `all` to run `invalidateAll()`
122
188
123
-
#### Methods
189
+
> This only runs on successful form submissions
124
190
125
-
-`set(values, clear?)` – Merge values into the form. Pass `true` to replace instead of merge.
126
-
-`reset()` – Restore the form to its initial state.
127
-
-`validate()` – Runs schema validation without submitting, updating both `errors` and `issues`.
128
-
-`submit()` – Parses the schema, calls hooks, executes the configured command, manages invalidation, and populates error state on failure.
129
-
-`getErrors()` / `getIssues()` – Accessor helpers useful outside of `$state` reactivity (e.g., from tests).
130
-
-`addError({path: string, message: string})` - Allows you to set an error on the form programatically (client side only)
191
+
**Example:**
192
+
193
+
```html
194
+
<scriptlang="ts">
195
+
constcmd=newCommandForm(schema, {
196
+
invalidate:'user:details'// invalidates routes with depends("user:details") set
197
+
// ...other options
198
+
});
199
+
</script>
200
+
```
131
201
132
-
### `standardValidate(schema, input)`
202
+
---
133
203
134
-
A small helper that runs the Standard Schema `validate` function, awaits async results, and throws `SchemaValidationError` when issues are returned. Use it to share validation logic between the form and other server utilities.
204
+
#### `options.reset`
135
205
136
-
### `SchemaValidationError`
206
+
Allows you to select if the form should be reset. By default, the form never resets. This accepts a value of `onSuccess` | `onError` or `always`
137
207
138
-
Custom error class wrapping the exact `issues` array returned by your schema. Catch it to reuse `transformIssues` or custom logging.
208
+
**Example:**
139
209
140
-
### `normalizeFiles(files: File[])`
210
+
```html
211
+
<scriptlang="ts">
212
+
constcmd=newCommandForm(schema, {
213
+
reset:'always'// the form will reset after submission no matter what
214
+
// ...other options
215
+
});
216
+
</script>
217
+
```
141
218
142
-
Utility that converts a `File[]` into JSON-friendly objects `{ name, type, size, bytes }`, making it easy to send uploads through command functions.
219
+
---
220
+
221
+
#### `options.preprocess()`
222
+
223
+
Allows you to preprocess any data you have set when the form is submitted. This will run prior to any parsing on the client. For example if you would need to convert an input of type 'date' to an ISO string on the client before submitting. If this is a promise, it will be awaited before continuing.
Runs if the form is submitted and returns sucessfully. You will have access to the returned value from the `command` that is ran. This can also be a promise.
243
+
244
+
```html
245
+
<scriptlang="ts">
246
+
constcmd=newCommandForm(schema, {
247
+
onSuccess: (response) => {
248
+
toast.success(`${response.name} has been updated!`);
Runs if the command fails and an error is returned.
262
+
263
+
```html
264
+
<scriptlang="ts">
265
+
constcmd=newCommandForm(schema, {
266
+
onError: (error) => {
267
+
toast.error('Oops! Something went wrong!');
268
+
console.error(error);
269
+
}
270
+
// ... other options
271
+
});
272
+
</script>
273
+
```
274
+
275
+
---
276
+
277
+
### Methods & Values
278
+
279
+
When you create a `new CommandForm` you get access to several methods and values that will help you manage your form state, submit, reset, and/or display errors.
280
+
281
+
In the following examples we will be using the following command form.
282
+
283
+
```html
284
+
<scriptlang="ts">
285
+
constcmd=newCommandForm(schema, {
286
+
initial: {
287
+
name:'Ada Lovelace',
288
+
age:'30'
289
+
}
290
+
});
291
+
</script>
292
+
```
293
+
294
+
#### `.form`
295
+
296
+
Gives you access to the data within the form. Useful when binding to inputs.
297
+
298
+
```svelte
299
+
<input placeholder="What is your name?" bind:value={cmd.form.name} />
300
+
```
301
+
302
+
---
303
+
304
+
#### `.set(values, clear?: boolean )`
305
+
306
+
Allows you to programatically merge form field values in bulk or add other values. If you set clear to true, it will replace all values instead of merging them in.
307
+
308
+
```typescript
309
+
set({ name: 'Linus Torvalds' });
310
+
311
+
// cmd.form will now be {name: "Linus Torvalds", age: 30}
312
+
313
+
set({ name: 'Linus Sebastian' }, true);
314
+
315
+
// cmd.form will now be {name: "Linus Sebastian"}
316
+
```
317
+
318
+
---
319
+
320
+
#### `.reset()`
321
+
322
+
Resets the form to the initial values that were passed in when it was instantiated.
323
+
324
+
> Note: If you are using an accessor function inside of `options.initial` it will reset to the newest available value instead of what it was when you instantiated it.
325
+
326
+
---
327
+
328
+
#### `.validate()`
329
+
330
+
Runs the parser and populates any errors. Useful if you want to display errors in realtime as the user is filling out the form. It will also clear any errors as they are corrected each time it is run.
331
+
332
+
> If you are using `options.preprocess` this is not ran during `validate()` however if you are using a schema library preprocessor such as `zod.preprocess` it should be ran within the parse.
Returns a boolean indicatiing whether the form is in flight or not. Useful for setting disabled states or showing loading spinners while the data is processed.
Returns back an easily accessible object with any validation errors. See [Errors](#errors) for more information on how to render.
362
+
363
+
#### `issues`
364
+
365
+
Returns back the raw validation issues. See [Issues](#issues) for more information.
366
+
367
+
---
143
368
144
369
## Handling file uploads
145
370
146
-
SvelteKit command functions currently expect JSON-serializable payloads, so `File` objects cannot be passed directly from the client to a command. Use the provided `normalizeFiles` helper to convert browser `File` instances into serializable blobs inside the `onSubmit` hook (so the parsed data that reaches your command already contains normalized entries):
371
+
SvelteKit command functions currently expect JSON-serializable payloads, so `File` objects cannot be passed directly from
372
+
the client to a command.
373
+
374
+
Use the provided `normalizeFiles` helper to convert browser
375
+
`File` instances into serializable blobs inside the `onSubmit` hook (so the parsed
376
+
data that reaches your command already contains normalized entries):
Both the Zod and Valibot schemas above can be adapted to accept either `File[]` (for client-side validation) or this normalized structure if you prefer validating the serialized payload on the server.
182
412
183
-
## Initial values and schema defaults
184
-
185
-
Standard Schema v1 does **not** provide a cross-library location for default values. A Zod or Valibot schema may specify defaults internally, but those defaults are not discoverable through the shared `~standard` interface. If there is an easy way to do this feel free to submit a PR. Because of that, `CommandForm` cannot pull defaults from your schema automatically. Instead, pass defaults via `options.initial`:
`initial` can also be a function if you need to recompute defaults per instantiation (`initial: () => ({ createdAt: new Date().toISOString() }))` or if you are using a `$derived()`. Any keys not provided remain `undefined` (or `null` if you explicitly set them) until the user interacts with them or you call `form.set`. If your schema rejects `undefined`/`null`, make it nullable (`z.string().nullable()`, `z.array(...).optional()`, etc.) or seed the field via `initial`.
0 commit comments