Skip to content

Commit 440b8cd

Browse files
committed
Cleanup
1 parent 2ebf48e commit 440b8cd

File tree

16 files changed

+162
-80
lines changed

16 files changed

+162
-80
lines changed

1.0 RC release.md

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,36 @@
22

33
After plenty of work, the first release candidate for **Superforms 1.0** is ready!
44

5-
First a feature list, so you'll see what's new, then a comprehensive migration guide.
5+
Here's a feature list, so you'll see what's new, after that a comprehensive migration guide.
66

77
## New and updated features
88

99
### Automatic form id
1010

11-
Setting a form `id` for multiple forms is not required anymore when using `use:enhance`, unless the forms are using the same schema.
11+
Setting a form `id` for multiple forms on the same page is not required anymore when using `use:enhance`.
1212

1313
```diff
1414
const loginForm = await superValidate(loginSchema, {
15-
- id: 'loginForm'
15+
- id: 'loginForm'
1616
});
1717

1818
const registerForm = await superValidate(registerSchema, {
19-
- id: 'registerForm'
19+
- id: 'registerForm'
2020
});
21+
22+
return { loginForm, registerForm }
23+
```
24+
25+
With one exception, if the forms are using the same schema:
26+
27+
```ts
28+
const form1 = await superValidate(schema, { id: 'form1' });
29+
const form2 = await superValidate(schema, { id: 'form2' });
30+
31+
return { form1, form2 };
2132
```
2233

23-
Without `use:enhance`, an id can be specified in the options or in a hidden form field called `__superform_id`.
34+
If you're having multiple forms without `use:enhance`, an id can be specified in the options, or in a hidden form field called `__superform_id`.
2435

2536
For extra safety, a warning will be emitted if identical id's are detected.
2637

@@ -39,7 +50,7 @@ This was previously an undocumented function called `defaultData`. If you've use
3950

4051
### superValidateSync
4152

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.
53+
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 (which assumes that there is no async validation in the schema). Can be very convenient in SPA:s.
4354

4455
```svelte
4556
<script lang="ts">
@@ -53,7 +64,7 @@ When using `superValidate` on the client, you previously had to use a `+page.ts`
5364

5465
### String path accessors
5566

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:
67+
When you wanted to set errors, use proxies and have 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:
5768

5869
```diff
5970
import { setError } from 'sveltekit-superforms/server'
@@ -91,7 +102,9 @@ const schema = z
91102
.refine((data) => password == confirmPassword, `Passwords doesn't match.`);
92103
```
93104

94-
The above will be available on the client as `$errors._errors` as before, and will be removed (or added) upon client-side validation.
105+
The above error set in `refine` will be available on the client as `$errors._errors` as before, and will be removed (or added) during client-side validation.
106+
107+
If you'd like the error to persist, `message` will persist until the next form submission.
95108

96109
```ts
97110
const form = await superValidate(request, schema);
@@ -102,35 +115,70 @@ if (form.data.password != form.data.confirmPassword)
102115
return message(form, `Passwords doesn't match`, { status: 400 });
103116
```
104117

105-
Unlike form-level messages, `message` will persist until the next form submission.
106-
107-
## validate, setError, proxy methods (ending with `Proxy`)
118+
## setError (again), validate, proxy methods (ending with `Proxy`)
108119

109120
`FieldPath` is gone - the above methods are now using a string accessor like `tags[2].id` instead of an array like `['tags', 2, 'id']`.
110121

111122
```diff
112-
- setError(form, ['tags', i, 'name'], 'Incorrect name');
113-
+ setError(form, `tags[${i}].name`, 'Incorrect name');
123+
const { form, enhance, validate } = superForm(data.form)
124+
125+
- validate(['tags', i, 'name'], { update: false });
126+
+ validate(`tags[${i}].name`, { update: false });
114127
```
115128

116-
This also applies to generic components, so you should change the type of the field prop, as described on the componentization page:
129+
This also applies to generic components, so you should change the type of the field prop, as described on the [componentization page](https://superforms.vercel.app/components):
117130

118131
```svelte
119132
<script lang="ts">
120133
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';
134+
import type { ZodValidation, StringPathLeaves } from '$lib';
135+
import { formFieldProxy, type SuperForm } from '$lib/client';
124136
125137
type T = $$Generic<AnyZodObject>;
126138
127-
export let form: SuperForm<UnwrapEffects<T>, unknown>;
128-
export let field: string & StringPath<z.infer<UnwrapEffects<T>>>;
139+
export let form: SuperForm<ZodValidation<T>, unknown>;
140+
export let field: string & StringPathLeaves<z.infer<T>>;
129141
130142
const { path, value, errors, constraints } = formFieldProxy(form, field);
131143
</script>
132144
```
133145

146+
Also note that **arrays and objects cannot be used in formFieldProxy**. So if your schema is defined as:
147+
148+
```ts
149+
import { formFieldProxy } from 'sveltekit-superforms/client';
150+
151+
const schema = z.object({
152+
tags: z
153+
.object({
154+
id: z.number(),
155+
name: z.string().min(1)
156+
})
157+
.array()
158+
});
159+
160+
const formData = superForm(data.form);
161+
162+
// This won't work properly!
163+
const tags = formFieldProxy(formData, 'tags');
164+
165+
// Not this either
166+
const tag = formFieldProxy(formData, 'tags[0]');
167+
168+
// But this will work
169+
const tagName = formFieldProxy(formData, 'tags[0].name');
170+
```
171+
172+
This only applies to `formFieldProxy` because it maps to errors and constraints as well as the form. If you want to proxy a form value only, the `fieldProxy` will work with any of the above.
173+
174+
```ts
175+
import { fieldProxy } from 'sveltekit-superforms/client';
176+
177+
const { form } = superForm(data.form);
178+
179+
const tags = fieldProxy(form, 'tags');
180+
```
181+
134182
## allErrors, firstError
135183

136184
The signature for `allErrors` and `firstError` has changed, to make it easier to group related messages:
@@ -140,21 +188,38 @@ The signature for `allErrors` and `firstError` has changed, to make it easier to
140188
+ { path: string; messages: string[] }
141189
```
142190

143-
The path follows the same format as the string accessor path above.
191+
The path follows the same format as the above described string accessor path. If you want to display all messages grouped:
144192

145193
```svelte
146194
{#if $allErrors.length}
147195
<ul>
148196
{#each $allErrors as error}
149197
<li>
150198
<b>{error.path}:</b>
151-
{error.messages}
199+
{error.messages.join('. ')}.
152200
</li>
153201
{/each}
154202
</ul>
155203
{/if}
156204
```
157205

206+
Or as before, separate for each error:
207+
208+
```svelte
209+
{#if $allErrors.length}
210+
<ul>
211+
{#each $allErrors as error}
212+
{#each error.messages as message}
213+
<li>
214+
<b>{error.path}:</b>
215+
{message}.
216+
</li>
217+
{/each}
218+
{/each}
219+
</ul>
220+
{/if}
221+
```
222+
158223
## defaultData
159224

160225
The `defaultData` function is now called `defaultValues`.
@@ -204,4 +269,13 @@ The following `superValidate` options have changed:
204269
const form = await superValidate(schema, { errors: true });
205270
```
206271

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!
272+
# Try it out!
273+
274+
To install this RC, update `package.json` with the new version, then update:
275+
276+
```diff
277+
- "sveltekit-superforms": "^0.8.7",
278+
+ "sveltekit-superforms": "^1.0.0-rc.1",
279+
```
280+
281+
The [changelog](https://github.com/ciscoheat/sveltekit-superforms/blob/main/CHANGELOG.md) has a full list of changes. 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [1.0.0-rc.1]
99

1010
### Changed
1111

src/index.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,17 +642,17 @@ describe('Errors', async () => {
642642
);
643643

644644
expect(findErrors(form.errors)).toStrictEqual([
645-
{ path: ['id'], messages: ['Required'] },
645+
{ path: 'id', messages: ['Required'] },
646646
{
647-
path: ['users', '0', 'name'],
647+
path: 'users[0].name',
648648
messages: ['String must contain at least 2 character(s)', 'Invalid']
649649
},
650650
{
651-
path: ['users', '0', 'posts', '0', 'subject'],
651+
path: 'users[0].posts[0].subject',
652652
messages: ['String must contain at least 1 character(s)']
653653
},
654654
{
655-
path: ['users', '0', 'posts', '_errors'],
655+
path: 'users[0].posts._errors',
656656
messages: ['Array must contain at least 2 element(s)']
657657
}
658658
]);

src/lib/client/index.ts

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

68-
export {
69-
splitPath,
70-
type StringPath,
71-
type StringPathLeaves
72-
} from '../stringPath.js';
73-
7468
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7569
export type FormOptions<T extends ZodValidation<AnyZodObject>, M> = Partial<{
7670
id: string;
@@ -224,8 +218,8 @@ export type SuperForm<T extends ZodValidation<AnyZodObject>, M = any> = {
224218
timeout: Readable<boolean>;
225219

226220
fields: FormFields<UnwrapEffects<T>>;
227-
firstError: Readable<{ path: string[]; messages: string[] } | null>;
228-
allErrors: Readable<{ path: string[]; messages: string[] }[]>;
221+
firstError: Readable<{ path: string; messages: string[] } | null>;
222+
allErrors: Readable<{ path: string; messages: string[] }[]>;
229223

230224
options: FormOptions<T, M>;
231225

src/lib/client/proxies.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { z, AnyZodObject } from 'zod';
1010
import {
1111
splitPath,
1212
type StringPath,
13+
type StringPathLeaves,
1314
type StringPathType
1415
} from '../stringPath.js';
1516
import type { ZodValidation } from '../index.js';
@@ -37,7 +38,7 @@ const defaultOptions: DefaultOptions = {
3738

3839
export function intProxy<
3940
T extends Record<string, unknown>,
40-
Path extends string & StringPath<T>
41+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
4142
>(
4243
form: Writable<T>,
4344
path: Path,
@@ -51,7 +52,7 @@ export function intProxy<
5152

5253
export function booleanProxy<
5354
T extends Record<string, unknown>,
54-
Path extends string & StringPath<T>
55+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
5556
>(
5657
form: Writable<T>,
5758
path: Path,
@@ -67,7 +68,7 @@ export function booleanProxy<
6768

6869
export function numberProxy<
6970
T extends Record<string, unknown>,
70-
Path extends string & StringPath<T>
71+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
7172
>(
7273
form: Writable<T>,
7374
path: Path,
@@ -81,7 +82,7 @@ export function numberProxy<
8182

8283
export function dateProxy<
8384
T extends Record<string, unknown>,
84-
Path extends string & StringPath<T>
85+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
8586
>(
8687
form: Writable<T>,
8788
path: Path,
@@ -101,7 +102,7 @@ export function dateProxy<
101102

102103
export function stringProxy<
103104
T extends Record<string, unknown>,
104-
Path extends string & StringPath<T>
105+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
105106
>(
106107
form: Writable<T>,
107108
path: Path,
@@ -124,7 +125,7 @@ export function stringProxy<
124125
function _stringProxy<
125126
T extends Record<string, unknown>,
126127
Type extends 'number' | 'int' | 'boolean' | 'date' | 'string',
127-
Path extends string & StringPath<T>
128+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
128129
>(
129130
form: Writable<T>,
130131
path: Path,
@@ -207,7 +208,8 @@ function _stringProxy<
207208
};
208209
}
209210

210-
export type FieldProxy<
211+
/*
212+
type FieldProxy<
211213
T extends AnyZodObject,
212214
Path extends string & StringPath<z.infer<T>>
213215
> = {
@@ -216,10 +218,11 @@ export type FieldProxy<
216218
errors?: Writable<string[] | undefined>;
217219
constraints?: Writable<InputConstraint | undefined>;
218220
};
221+
*/
219222

220223
export function formFieldProxy<
221224
T extends ZodValidation<AnyZodObject>,
222-
Path extends string & StringPath<z.infer<UnwrapEffects<T>>>
225+
Path extends string & StringPathLeaves<z.infer<UnwrapEffects<T>>>
223226
>(
224227
form: SuperForm<T, unknown>,
225228
path: Path
@@ -250,7 +253,7 @@ export function formFieldProxy<
250253

251254
export function fieldProxy<
252255
T extends object,
253-
Path extends string & StringPath<T>
256+
Path extends (string & StringPath<T>) | (string & StringPathLeaves<T>)
254257
>(form: Writable<T>, path: Path): Writable<StringPathType<T, Path>> {
255258
const path2 = splitPath<T>(path);
256259

src/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export type ZodValidation<T extends AnyZodObject> =
3434
| ZodEffects<ZodEffects<ZodEffects<ZodEffects<T>>>>
3535
| ZodEffects<ZodEffects<ZodEffects<ZodEffects<ZodEffects<T>>>>>;
3636

37+
export { type StringPath, type StringPathLeaves } from './stringPath.js';
38+
3739
export type RawShape<T> = T extends ZodObject<infer U> ? U : never;
3840

3941
type UnwrappedRawShape<

src/lib/stringPath.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ export function splitPath<T extends object>(
99
.filter((p) => p) as FieldPath<T>;
1010
}
1111

12+
export function mergePath(path: (string | number | symbol)[]) {
13+
return path.reduce((acc: string, next) => {
14+
if (typeof next === 'number' || !isNaN(parseInt(String(next), 10)))
15+
acc += `[${String(next)}]`;
16+
else if (!acc) acc += String(next);
17+
else acc += `.${String(next)}`;
18+
19+
return acc;
20+
}, '');
21+
}
22+
1223
/**
1324
* Lists all paths in an object as string accessors.
1425
*/

src/lib/superValidate.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ 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';
4439

4540
import { clone } from './utils.js';
4641

0 commit comments

Comments
 (0)