Skip to content

Conversation

Rich-Harris
Copy link
Member

@Rich-Harris Rich-Harris commented Sep 6, 2025

Implements #14288

  • might not be able to flatten keys recursively, because TypeScript complains figured it out, I think
  • issues
  • input
  • preflight(...)
  • validate()
  • pass converted data to enhance callback, rather than the FormData object
  • invalid() On second thoughts, I think we should ship what we have and see if we truly need this API, since it's a slightly awkward one

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

Copy link

changeset-bot bot commented Sep 6, 2025

🦋 Changeset detected

Latest commit: 37c10c9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot
Copy link

Copy link
Member

@dummdidumm dummdidumm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far! Some notes / blockers:

  • would be nice to have a way to tell "hide this from output" (e.g. password)
  • we should coerce boolean/number via some opt in mechanism, else you'll often parse twice. The types should allow it, too since you can do it yourself today, but it's not pretty:
v.pipe(
	v.unknown(),
	v.transform((value) => {
		if (typeof value !== 'string') {
			return value;
		}

		if (value === '') {
			return undefined;
		}

		return Number(value.trim().replace(/,/g, ''));
	}),
	v.number()
)

Maybe both of these can be solved via some opionated name syntax: <input type="password" name="hidden:password and <input type="number" name="number:count" > (you can concatenate them)

Also you cannot use files with this yet, as the submission tries to return the file in inputs, and that fails when devalue tries to stringify it. We should omit them from the response I think.

I also twice got "excessively deep" type errors, which makes me a bit nervous, but it's not reproducible.

@Rich-Harris
Copy link
Member Author

isn't coercion as simple as this?

v.pipe(v.string(), v.regex(/^\d+$/), v.transform((n) => +n))

would be nice to have a way to tell "hide this from output" (e.g. password)

I've searched in vain for any authoritative advice that this is necessary, rather than paranoid. Do we truly need it?

Yeah omitting files altogether is probably the best approach

@dummdidumm
Copy link
Member

dummdidumm commented Sep 16, 2025

I take back the coercion part - it is easy enough in most validators, and it also solves my adjacent "the types should allow you to pass in more object shapes" request, because if the coercion is done via validators then the shape of only allowing strings or files is correct. We should have something about it in the docs though, gonna work on that.

The "excessively deep" error indeed occured from unknown, will also look into fixing that.

@AndreasHald
Copy link

Really excited about this PR - can't wait to get my hands on it, regarding your comment on whether or not we could do without invalid

do you have any suggestion to how we could protect unique indexes such as a user email, I confess I can't see an elegant way to do it without a transaction in the remote function

// signup.remote.ts

const schema = z.object({
   email: z.string().refine(async (email) => {
   const { locals: { db } } = getRequestEvent();
   const emailIsTaken = db.select().from(db.users).where(...)
   return !!emailIsTaken;
}, ['This email is already taken'])
})

export const signup = form(schema, async ({email} => {

   // Inserting email here could still result in an index violation
   // I can't see a way to do it without resorting to the invalid api

    db.transaction((tx) => {
       const emailIsTaken = db.select().from(users)
       if (emailIsTaken) {
        invalid('email', 'this email is already taken')
       }
    })     

     redirect(...)
}) 

@Rich-Harris
Copy link
Member Author

Great question — I'll confess I hadn't really considered the race condition case. To be honest I would probably just live with the extremely slim chance of a 500 (what are the odds of someone registering the same email address at the exact same moment?) but that doesn't really work if you're building the order form for hotcakes.com.

One of the reasons I wasn't too confident about the invalid API was that I couldn't figure out how best to pass issues into it. You probably want to be able to pass issues directly from a standard schema library...

const result = await MySchema.safeParse(data);

if (!result.success) {
  invalid(result.issues);
}

...but you also want to be able to do this...

db.transaction((tx) => {
  const emailIsTaken = db.select().from(users)
  if (emailIsTaken) {
    invalid(???)
  }

  // ...
})

...and ideally you wouldn't have to conform to the Issue shape of your specific validation library, nor live with the weirdness of some issues being one shape and others being another.

But a few commits ago we decided to redact everything inside each issue except the path (which now gets normalized — in Standard Schema it can take two different forms which is a PITA to deal with) and the message. (This is so that we don't have to worry about sending sensitive data back to the browser, as in issues._password[0].input.) We also add the flattened name to the object. The upshot of this is that we can probably come up with an API for invalid that feels appropriate to both schema and non-schema cases. Still filed under 'future work' for the moment though. API design is hard! 😅

@Rich-Harris Rich-Harris merged commit 4c77bcd into main Sep 16, 2025
21 of 22 checks passed
@Rich-Harris Rich-Harris deleted the validated-forms branch September 16, 2025 20:50
@github-actions github-actions bot mentioned this pull request Sep 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request forms Stuff relating to forms and form actions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants