Skip to content

Commit 2ebf48e

Browse files
committed
Proxy type fixes.
1 parent 4fa75ba commit 2ebf48e

File tree

8 files changed

+252
-47
lines changed

8 files changed

+252
-47
lines changed

1.0 RC release.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# 1.0.0-rc.1
2+
3+
After plenty of work, the first release candidate for **Superforms 1.0** is ready!
4+
5+
First a feature list, so you'll see what's new, then a comprehensive migration guide.
6+
7+
## New and updated features
8+
9+
### Automatic form id
10+
11+
Setting a form `id` for multiple forms is not required anymore when using `use:enhance`, unless the forms are using the same schema.
12+
13+
```diff
14+
const loginForm = await superValidate(loginSchema, {
15+
- id: 'loginForm'
16+
});
17+
18+
const registerForm = await superValidate(registerSchema, {
19+
- id: 'registerForm'
20+
});
21+
```
22+
23+
Without `use:enhance`, an id can be specified in the options or in a hidden form field called `__superform_id`.
24+
25+
For extra safety, a warning will be emitted if identical id's are detected.
26+
27+
### defaultValues
28+
29+
This method will provide you with the default values for a schema.
30+
31+
```ts
32+
import { defaultValues } from 'sveltekit-superforms/server'
33+
34+
const schema = z.object({ ... })
35+
const defaults = defaultValues(schema)
36+
```
37+
38+
This was previously an undocumented function called `defaultData`. If you've used it, rename it to `defaultValues`.
39+
40+
### superValidateSync
41+
42+
When using `superValidate` on the client, you previously had to use a `+page.ts` file to call `superValidate`, since it is asynchronous. But now you can import `superValidateSync` and use it in components directly. Can be very convenient in SPA:s.
43+
44+
```svelte
45+
<script lang="ts">
46+
import { schema } from '$lib/schemas';
47+
import { superValidateSync, superForm } from 'sveltekit-superforms/client';
48+
49+
const validation = superValidateSync(schema);
50+
const form = superForm(validation);
51+
</script>
52+
```
53+
54+
### String path accessors
55+
56+
When you wanted to set errors, use proxies and nested data in components, the array syntax was a bit clunky. It has now been replaced with a typesafe string path, so you can write it just as you would access an object property:
57+
58+
```diff
59+
import { setError } from 'sveltekit-superforms/server'
60+
61+
const i = 1;
62+
63+
- setError(form, ['tags', i, 'name'], 'Incorrect name');
64+
+ setError(form, `tags[${i}].name`, 'Incorrect name');
65+
```
66+
67+
```diff
68+
import { intProxy } from 'sveltekit-superforms/client'
69+
70+
const { form } = superForm(data.form);
71+
- const idProxy = intProxy(form, ['user', 'profile', 'id']);
72+
+ const idProxy = intProxy(form, 'user.profile.id');
73+
```
74+
75+
# Migration guide
76+
77+
Lists the breaking changes that you need to address to upgrade from v0.8.
78+
79+
The guide is written with the affected methods in the headlines, so you can scan through them and apply the changes if you're using them in your code.
80+
81+
## setError
82+
83+
The `setError` function doesn't handle form-level errors anymore, because it conflicts with client-side validation. Use refine/superRefine on the schema, or the `message` helper instead.
84+
85+
```ts
86+
const schema = z
87+
.object({
88+
password: z.string().min(8),
89+
confirmPassword: z.string()
90+
})
91+
.refine((data) => password == confirmPassword, `Passwords doesn't match.`);
92+
```
93+
94+
The above will be available on the client as `$errors._errors` as before, and will be removed (or added) upon client-side validation.
95+
96+
```ts
97+
const form = await superValidate(request, schema);
98+
99+
if (!form.valid) return fail(400, { form });
100+
101+
if (form.data.password != form.data.confirmPassword)
102+
return message(form, `Passwords doesn't match`, { status: 400 });
103+
```
104+
105+
Unlike form-level messages, `message` will persist until the next form submission.
106+
107+
## validate, setError, proxy methods (ending with `Proxy`)
108+
109+
`FieldPath` is gone - the above methods are now using a string accessor like `tags[2].id` instead of an array like `['tags', 2, 'id']`.
110+
111+
```diff
112+
- setError(form, ['tags', i, 'name'], 'Incorrect name');
113+
+ setError(form, `tags[${i}].name`, 'Incorrect name');
114+
```
115+
116+
This also applies to generic components, so you should change the type of the field prop, as described on the componentization page:
117+
118+
```svelte
119+
<script lang="ts">
120+
import type { z, AnyZodObject } from 'zod';
121+
import type { UnwrapEffects } from 'sveltekit-superforms';
122+
import type { SuperForm, StringPath } from 'sveltekit-superforms/client';
123+
import { formFieldProxy } from 'sveltekit-superforms/client';
124+
125+
type T = $$Generic<AnyZodObject>;
126+
127+
export let form: SuperForm<UnwrapEffects<T>, unknown>;
128+
export let field: string & StringPath<z.infer<UnwrapEffects<T>>>;
129+
130+
const { path, value, errors, constraints } = formFieldProxy(form, field);
131+
</script>
132+
```
133+
134+
## allErrors, firstError
135+
136+
The signature for `allErrors` and `firstError` has changed, to make it easier to group related messages:
137+
138+
```diff
139+
- { path: string[]; message: string[] }
140+
+ { path: string; messages: string[] }
141+
```
142+
143+
The path follows the same format as the string accessor path above.
144+
145+
```svelte
146+
{#if $allErrors.length}
147+
<ul>
148+
{#each $allErrors as error}
149+
<li>
150+
<b>{error.path}:</b>
151+
{error.messages}
152+
</li>
153+
{/each}
154+
</ul>
155+
{/if}
156+
```
157+
158+
## defaultData
159+
160+
The `defaultData` function is now called `defaultValues`.
161+
162+
```diff
163+
- import { defaultData } from 'sveltekit-superforms/server`
164+
+ import { defaultValues } from 'sveltekit-superforms/server`
165+
```
166+
167+
## meta
168+
169+
The virtually unused `meta` store has been removed. Use the Zod schema directly instead for reflection.
170+
171+
## Client options
172+
173+
The following `superForm` options have changed:
174+
175+
### resetForm
176+
177+
Resetting the form now works without `use:enhance`! Just set the option to `true` and it will work.
178+
179+
If you have used the function version of `resetForm`, `() => boolean`, it is now synchronous.
180+
181+
### errorSelector
182+
183+
The default `errorSelector` is now `[data-invalid],[aria-invalid="true"]`, so if you want to be more accessibility-friendly:
184+
185+
```diff
186+
<input
187+
name="name"
188+
bind:value={$form.name}
189+
- data-invalid={$errors.name}
190+
+ aria-invalid={$errors.name ? 'true' : undefined}
191+
/>
192+
```
193+
194+
## Server options
195+
196+
The following `superValidate` options have changed:
197+
198+
### noErrors
199+
200+
`noErrors` is removed from the options. Use `errors` instead to determine if errors should be added or not to the validation.
201+
202+
```ts
203+
// Add errors to an empty form
204+
const form = await superValidate(schema, { errors: true });
205+
```
206+
207+
The [changelog](https://github.com/ciscoheat/sveltekit-superforms/blob/main/CHANGELOG.md) has a full list of changes that aren't breaking. So please try this release candidate, and let me know in a [Github issue](https://github.com/ciscoheat/sveltekit-superforms/issues) or on [Discord](https://discord.gg/AptebvVuhB) if you find some problem. Thank you for helping out towards 1.0!

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- Explicitly setting a form `id` for multiple forms is not required anymore when using `use:enhance`, unless the forms are using the same schema. An id can be specified in the options or in a hidden form field called `__superform_id`.
1213
- `setError` doesn't handle form-level errors anymore, use refine/superRefine on the schema, or the `message` helper.
1314
- `FieldPath` is gone - the following methods are now using a string accessor like `tags[2].id` instead of an array like `['tags', 2, 'id']`: `validate`, `setError` and all proxy methods (ending with `Proxy`). This also applies to generic components.
14-
- The signature for `allErrors` and `firstError` have changed to `{ path: string[]; messages: string[] }`.
15+
- The signature for `allErrors` and `firstError` has changed to `{ path: string[]; messages: string[] }`.
1516
- The literal `"any"` is now an allowed value in `step` for constraints.
1617
- Multiple `regex` and `step` is now allowed in a schema. A warning will be emitted by default, that can be turned off.
1718
- The signature for `options.resetForm` has changed to `boolean | () => boolean` (it was async before).
1819
- The undocumented `defaultData` is now called `defaultValues`.
1920
- Added `[aria-invalid="true"]` to `errorSelector` option.
21+
- `options.resetForm` now works without `use:enhance`!
2022

2123
### Removed
2224

@@ -25,14 +27,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2527

2628
### Fixed
2729

28-
- Explicitly setting a form `id` for multiple forms is not required anymore when using `use:enhance`, unless the forms are using the same schema. An id can be specified in the options or in a hidden form field called `__superform_id`.
2930
- Fixed deprecation notices for `use:enhance`.
3031

3132
### Added
3233

33-
- Added `superValidateSync`, useful on the client for SPA:s.
34+
- Added `superValidateSync`, useful in components for SPA:s.
3435
- Added `defaultValues`, which takes a schema and returns the default values for it.
35-
- `options.resetForm` now works without `use:enhance`!
3636
- Support for `ZodPipeline`.
3737

3838
## [0.8.7] - 2023-05-22

src/lib/client/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export {
6565
defaultValues
6666
} from '../superValidate.js';
6767

68+
export {
69+
splitPath,
70+
type StringPath,
71+
type StringPathLeaves
72+
} from '../stringPath.js';
73+
6874
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6975
export type FormOptions<T extends ZodValidation<AnyZodObject>, M> = Partial<{
7076
id: string;
@@ -346,13 +352,13 @@ export function superForm<
346352
}
347353

348354
function Context_newEmptyForm(
349-
data: Partial<z.infer<T>> = {}
355+
data?: Partial<z.infer<T>>
350356
): Validation<T, M> {
351357
return {
352358
valid: false,
353359
errors: {},
354-
data,
355-
empty: true,
360+
data: data ?? {},
361+
empty: !!data,
356362
constraints: {} as Validation<T, M>['constraints']
357363
};
358364
}

src/lib/client/proxies.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import type { z, AnyZodObject } from 'zod';
1010
import {
1111
splitPath,
1212
type StringPath,
13-
type StringPathLeaves,
1413
type StringPathType
1514
} from '../stringPath.js';
15+
import type { ZodValidation } from '../index.js';
1616

1717
type DefaultOptions = {
1818
trueStringValue: string;
@@ -218,18 +218,18 @@ export type FieldProxy<
218218
};
219219

220220
export function formFieldProxy<
221-
T extends AnyZodObject,
222-
Path extends string & StringPathLeaves<z.infer<T>>
221+
T extends ZodValidation<AnyZodObject>,
222+
Path extends string & StringPath<z.infer<UnwrapEffects<T>>>
223223
>(
224-
form: SuperForm<UnwrapEffects<T>, unknown>,
224+
form: SuperForm<T, unknown>,
225225
path: Path
226226
): {
227227
path: Path;
228228
value: Writable<StringPathType<z.infer<UnwrapEffects<T>>, Path>>;
229229
errors: Writable<string[] | undefined>;
230230
constraints: Writable<InputConstraint | undefined>;
231231
} {
232-
const path2 = splitPath<z.infer<T>>(path);
232+
const path2 = splitPath<z.infer<UnwrapEffects<T>>>(path);
233233
// Filter out array indices, the constraints structure doesn't contain these.
234234
const constraintsPath = (path2 as unknown[])
235235
.filter((p) => isNaN(parseInt(String(p))))

src/lib/stringPath.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,5 @@
11
import type { FieldPath } from './index.js';
22

3-
/*
4-
type Expand<T> = T extends (...args: infer A) => infer R
5-
? (...args: Expand<A>) => Expand<R>
6-
: T extends infer O
7-
? { [K in keyof O]: O[K] }
8-
: never;
9-
10-
type ExpandRecursively<T> = T extends (...args: infer A) => infer R
11-
? (...args: ExpandRecursively<A>) => ExpandRecursively<R>
12-
: T extends object
13-
? T extends infer O
14-
? { [K in keyof O]: ExpandRecursively<O[K]> }
15-
: never
16-
: T;
17-
18-
type FilterObjects<T extends object> = {
19-
[K in keyof T]: T[K] extends object ? never : K;
20-
}[keyof T];
21-
*/
22-
233
export function splitPath<T extends object>(
244
path: StringPath<T> | StringPathLeaves<T>
255
) {

src/lib/superValidate.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ import {
3636
} from 'zod';
3737

3838
import { splitPath, type StringPathLeaves } from './stringPath.js';
39+
export {
40+
splitPath,
41+
type StringPath,
42+
type StringPathLeaves
43+
} from './stringPath.js';
3944

4045
import { clone } from './utils.js';
4146

@@ -455,7 +460,7 @@ export async function superValidate<
455460
data = null;
456461
}
457462

458-
const schemaData = getSchemaData(schema as T, options);
463+
const schemaData = getSchemaData(schema as UnwrapEffects<T>, options);
459464

460465
async function tryParseFormData(request: Request) {
461466
let formData: FormData | undefined = undefined;
@@ -508,7 +513,7 @@ export async function superValidate<
508513
}
509514

510515
const { parsed, result } = await parseRequest();
511-
return validateResult(parsed, schemaData, result);
516+
return validateResult<UnwrapEffects<T>, M>(parsed, schemaData, result);
512517
}
513518

514519
export function actionResult<
@@ -613,7 +618,7 @@ export function superValidateSync<
613618
data = null;
614619
}
615620

616-
const schemaData = getSchemaData(schema as T, options);
621+
const schemaData = getSchemaData(schema as UnwrapEffects<T>, options);
617622

618623
const parsed =
619624
data instanceof FormData
@@ -630,5 +635,5 @@ export function superValidateSync<
630635
: undefined;
631636
//////////////////////////////////////////////////////////////////////
632637

633-
return validateResult(parsed, schemaData, result);
638+
return validateResult<UnwrapEffects<T>, M>(parsed, schemaData, result);
634639
}

src/routes/properly-nested/TextField.svelte

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<script lang="ts">
2-
import type { StringPath } from '$lib/stringPath';
3-
4-
import type { FieldPath, UnwrapEffects } from '$lib';
5-
import type { SuperForm } from '$lib/client';
62
import type { z, AnyZodObject } from 'zod';
3+
import type { UnwrapEffects } from '$lib';
4+
import type { SuperForm, StringPath } from '$lib/client';
75
86
import { formFieldProxy } from '$lib/client';
97

0 commit comments

Comments
 (0)