Skip to content

Commit 435223b

Browse files
committed
updated server handling and added steps for how to throw server errors
1 parent c7799ea commit 435223b

File tree

7 files changed

+67
-30
lines changed

7 files changed

+67
-30
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pnpm add @akcodeworks/svelte-command-form
2222
npm install @akcodeworks/svelte-command-form
2323
```
2424

25-
## Quick start
25+
## How to Use
2626

2727
```html
2828
<script lang="ts">
@@ -418,11 +418,36 @@ When validation fails, `CommandForm`:
418418
2. Converts issues into `errors` (per field) via `transformIssues`.
419419
3. Stores the raw issue array in `issues` for programmatic access.
420420

421-
If the command throws an `HttpError` from SvelteKit, the helper looks for `err.body.issues` and merges them into the same structures. Any other error is forwarded to `onError` after clearing submission state. You can handle validation errors to populate this in your `hooks.server.ts`
421+
If you need to manually set an error follow these steps.
422+
423+
1. Setup your hooks.server.ts file and add an error handler.
424+
425+
```typescript
426+
export const handleError: HandleServerError = async ({ error }) => {
427+
// Note: If you don't want bad actors seeing your validation issues, you can do an auth check here before returning
428+
if (error instanceof SchemaValidationError) return error as SchemaValidationError;
429+
};
430+
```
431+
432+
2. Throw a new `SchemaValidationError` inside of the command.
433+
434+
```typescript
435+
export const test = command(schema, async (data) => {
436+
const user = await db.user.findFirst({ where: { email: data.email } });
437+
if (!user)
438+
throw new SchemaValidationError([
439+
{ path: ['email'], message: 'Name is invalid server error!!' }
440+
]);
441+
});
442+
```
443+
444+
In the above example this will populate the `form.errors.email.message` field so you can display the error to the user on the client.
445+
446+
> If you do not add the custom error handler in step 1, you will not get any issues back. This is a SvelteKit design principle when dealing with Remote Functions!
422447
423448
## Manual Errors
424449

425-
You can add errors manually by using the `addErrors` method on the client or by throwing a `new SchemaValidationError` inside of the remote function.
450+
You can add errors manually by using the `addErrors` method (client only) or by throwing a `new SchemaValidationError`.
426451

427452
```typescript
428453
// server add error
@@ -447,6 +472,8 @@ const someFunc = command(schema, async (data) => {
447472
<button onclick="{addError}">Add an Error</button>
448473
```
449474

475+
> addError() does NOT throw an error, you will have to do that once you call it. If you want to throw an error, throw a new `SchemaValidationError`
476+
450477
## Contributing
451478

452479
Feel free to contribute by opening a PR with a detailed description of why you are wanting to change what you are changing. If it can be tested with Vitest, that is preferred.

src/hooks.server.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// import { ServerValidationError } from "$lib/helpers/server-validation-error.ts";
2+
// import type { SchemaValidationError } from "$lib/index.ts";
3+
// import type { HandleServerError, HandleValidationError, RequestEvent } from "@sveltejs/kit";
4+
// import { sequence } from "@sveltejs/kit/hooks";
5+
6+
7+
import { SchemaValidationError } from "$lib/index.ts";
8+
import type { HandleServerError } from "@sveltejs/kit";
9+
10+
11+
12+
export const handleError: HandleServerError = async ({ error }) => {
13+
if (error instanceof SchemaValidationError) return error as SchemaValidationError
14+
};

src/lib/command-form/command-form.svelte.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export class CommandForm<Schema extends StandardSchemaV1, TOut> {
103103

104104
const res = await this.options.command(parsed);
105105
this._result = res;
106+
106107
this.issues = null;
107108
await this.options.onSuccess?.(res);
108109

@@ -127,10 +128,15 @@ export class CommandForm<Schema extends StandardSchemaV1, TOut> {
127128
return null;
128129
}
129130

131+
132+
130133
if (isHttpError(err)) {
131-
const httpError = err as HttpError & {
132-
body: { issues?: Record<string, { message: string }> };
133-
};
134+
const httpError = err as HttpError & { body: any };
135+
if (Array.isArray(httpError.body?.issues) && httpError.body.issues.every((i: any) => 'path' in i && 'message' in i)) {
136+
this.setErrorsFromIssues(httpError.body.issues);
137+
await this.options.onError?.(err);
138+
return null;
139+
}
134140
const transformed = httpError.body.issues;
135141
if (transformed) {
136142
this.setErrorsFromRecord(transformed);

src/lib/helpers/standard-validate.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ export class SchemaValidationError extends Error {
1212
}
1313
}
1414

15-
16-
1715
export async function standardValidate<T extends StandardSchemaV1>(
1816
schema: T,
1917
input: StandardSchemaV1.InferInput<T>

src/routes/+page.svelte

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
<script lang="ts">
2-
import { CommandForm } from '$lib/command-form/command-form.svelte.ts';
2+
import { CommandForm } from '$lib/command-form/command-form.svelte.js';
33
import { test } from './test.remote.ts';
44
import { schema, TestEnum } from './test.schema.ts';
55
66
const f = new CommandForm(schema, {
77
command: test,
8-
initial: {
9-
hobbies: ['coding'],
10-
status: TestEnum.ONE
11-
},
128
onSubmit: async (data) => {
139
console.log(data);
1410
},
1511
onSuccess: async (res) => {
1612
console.log('success');
17-
console.log(res.success);
13+
// console.log(res.success);
1814
},
1915
onError: async (err) => {
20-
console.log('error', err);
16+
console.error('error', err);
2117
}
2218
});
23-
24-
$inspect(f.errors);
2519
</script>
2620

2721
<input type="text" placeholder="Name" bind:value={f.form.name} />

src/routes/test.remote.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { command } from "$app/server";
22
import { schema } from "./test.schema.ts";
3+
import { SchemaValidationError } from "$lib/index.ts";
34

45

6+
export const test = command(schema, async () => {
57

6-
7-
export const test = command(schema, async (d) => {
8-
console.log("running comand")
9-
console.log(d)
10-
return { success: true };
8+
throw new SchemaValidationError([{ path: ['email'], message: 'Name is invalid server error!!' }])
119
})

src/routes/test.schema.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ export enum TestEnum {
44
}
55
const schema = z.object({
66
name: z.string().min(2, "Name is required"),
7-
age: z.number().min(0, "Age must be a positive number"),
8-
address: z.object({
9-
street: z.string().min(5, "Street is required"),
10-
city: z.string().min(2, "City is required"),
11-
zip: z.string().min(5, "ZIP code is required")
12-
}),
13-
hobbies: z.array(z.string()).min(1, "At least one hobby is required"),
14-
status: z.enum(TestEnum)
7+
// age: z.number().min(0, "Age must be a positive number"),
8+
// address: z.object({
9+
// street: z.string().min(5, "Street is required"),
10+
// city: z.string().min(2, "City is required"),
11+
// zip: z.string().min(5, "ZIP code is required")
12+
// }),
13+
// hobbies: z.array(z.string()).min(1, "At least one hobby is required"),
14+
// status: z.enum(TestEnum)
1515
})
1616

1717
export { schema }

0 commit comments

Comments
 (0)