diff --git a/.changeset/icy-glasses-agree.md b/.changeset/icy-glasses-agree.md new file mode 100644 index 000000000000..2f103d4a8d9d --- /dev/null +++ b/.changeset/icy-glasses-agree.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +breaking: `invalid` now must be imported from `@sveltejs/kit` diff --git a/documentation/docs/20-core-concepts/60-remote-functions.md b/documentation/docs/20-core-concepts/60-remote-functions.md index 969b90d17121..bf1bbcf69aab 100644 --- a/documentation/docs/20-core-concepts/60-remote-functions.md +++ b/documentation/docs/20-core-concepts/60-remote-functions.md @@ -452,11 +452,12 @@ Alternatively, you could use `select` and `select multiple`: ### Programmatic validation -In addition to declarative schema validation, you can programmatically mark fields as invalid inside the form handler using the `invalid` function. This is useful for cases where you can't know if something is valid until you try to perform some action: +In addition to declarative schema validation, you can programmatically mark fields as invalid inside the form handler using the `invalid` function. This is useful for cases where you can't know if something is valid until you try to perform some action. Just like `redirect` or `error`, `invalid` throws. It expects a list of standard-schema-compliant issues. Use the `issue` parameter for type-safe creation of such issues: ```js /// file: src/routes/shop/data.remote.js import * as v from 'valibot'; +import { invalid } from '@sveltejs/kit'; import { form } from '$app/server'; import * as db from '$lib/server/database'; @@ -467,13 +468,17 @@ export const buyHotcakes = form( v.minValue(1, 'you must buy at least one hotcake') ) }), - async (data, invalid) => { + async (data, issue) => { try { await db.buy(data.qty); } catch (e) { if (e.code === 'OUT_OF_STOCK') { invalid( - invalid.qty(`we don't have enough hotcakes`) + // This will show up on the root issue list + 'Purchase failed', + // Creates a `{ message: ..., path: ['qty'] }` object, + // will show up on the issue list for the `qty` field + issue.qty(`we don't have enough hotcakes`) ); } } diff --git a/packages/kit/src/exports/index.js b/packages/kit/src/exports/index.js index 8663772f0c0c..0d78e017cebd 100644 --- a/packages/kit/src/exports/index.js +++ b/packages/kit/src/exports/index.js @@ -1,4 +1,6 @@ -import { HttpError, Redirect, ActionFailure } from './internal/index.js'; +/** @import { StandardSchemaV1 } from '@standard-schema/spec' */ + +import { HttpError, Redirect, ActionFailure, ValidationError } from './internal/index.js'; import { BROWSER, DEV } from 'esm-env'; import { add_data_suffix, @@ -215,6 +217,52 @@ export function isActionFailure(e) { return e instanceof ActionFailure; } +/** + * Use this to throw a validation error to imperatively fail form validation. + * Can be used in combination with `issue` passed to form actions to create field-specific issues. + * + * @example + * ```ts + * import { invalid } from '@sveltejs/kit'; + * import { form } from '$app/server'; + * import * as v from 'valibot'; + * + * function tryRegisterUser(name: string, password: string) { + * // ... + * } + * + * export const register = form( + * v.object({ name: v.string(), _password: v.string() }), + * async ({ name, _password }, issue) => { + * const success = tryRegisterUser(name, _password); + * if (!success) { + * invalid('Registration failed', issue.name('This username is already taken')); + * } + * + * // ... + * } + * ); + * ``` + * @param {...(StandardSchemaV1.Issue | string)} issues + * @returns {never} + * @since 2.47.3 + */ +export function invalid(...issues) { + throw new ValidationError( + issues.map((issue) => (typeof issue === 'string' ? { message: issue } : issue)) + ); +} + +/** + * Checks whether this is an validation error thrown by {@link invalid}. + * @param {unknown} e The object to check. + * @return {e is import('./public.js').ActionFailure} + * @since 2.47.3 + */ +export function isValidationError(e) { + return e instanceof ValidationError; +} + /** * Strips possible SvelteKit-internal suffixes and trailing slashes from the URL pathname. * Returns the normalized URL as well as a method for adding the potential suffix back diff --git a/packages/kit/src/exports/internal/index.js b/packages/kit/src/exports/internal/index.js index b87448b30914..8ef0a32a32c8 100644 --- a/packages/kit/src/exports/internal/index.js +++ b/packages/kit/src/exports/internal/index.js @@ -1,3 +1,5 @@ +/** @import { StandardSchemaV1 } from '@standard-schema/spec' */ + export class HttpError { /** * @param {number} status @@ -62,4 +64,18 @@ export class ActionFailure { } } +/** + * Error thrown when form validation fails imperatively + */ +export class ValidationError extends Error { + /** + * @param {StandardSchemaV1.Issue[]} issues + */ + constructor(issues) { + super('Validation failed'); + this.name = 'ValidationError'; + this.issues = issues; + } +} + export { init_remote_functions } from './remote-functions.js'; diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index cb42dc6cc1c0..ef6ccef0c9d1 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1974,10 +1974,13 @@ type ExtractId = Input extends { id: infer Id } : string | number; /** - * Recursively maps an input type to a structure where each field can create a validation issue. - * This mirrors the runtime behavior of the `invalid` proxy passed to form handlers. + * A function and proxy object used to imperatively create validation errors in form handlers. + * + * Access properties to create field-specific issues: `issue.fieldName('message')`. + * The type structure mirrors the input data structure for type-safe field access. + * Call `invalid(issue.foo(...), issue.nested.bar(...))` to throw a validation error. */ -type InvalidField = +export type InvalidField = WillRecurseIndefinitely extends true ? Record : NonNullable extends string | number | boolean | File @@ -1993,15 +1996,12 @@ type InvalidField = : Record; /** - * A function and proxy object used to imperatively create validation errors in form handlers. - * - * Call `invalid(issue1, issue2, ...issueN)` to throw a validation error. - * If an issue is a `string`, it applies to the form as a whole (and will show up in `fields.allIssues()`) - * Access properties to create field-specific issues: `invalid.fieldName('message')`. - * The type structure mirrors the input data structure for type-safe field access. + * A validation error thrown by `invalid`. */ -export type Invalid = ((...issues: Array) => never) & - InvalidField; +export interface ValidationError { + /** The validation issues */ + issues: StandardSchemaV1.Issue[]; +} /** * The return value of a remote `form` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. diff --git a/packages/kit/src/runtime/app/server/remote/form.js b/packages/kit/src/runtime/app/server/remote/form.js index 9d5b5b551a4d..e90a7ce7668b 100644 --- a/packages/kit/src/runtime/app/server/remote/form.js +++ b/packages/kit/src/runtime/app/server/remote/form.js @@ -1,4 +1,4 @@ -/** @import { RemoteFormInput, RemoteForm } from '@sveltejs/kit' */ +/** @import { RemoteFormInput, RemoteForm, InvalidField } from '@sveltejs/kit' */ /** @import { InternalRemoteFormIssue, MaybePromise, RemoteInfo } from 'types' */ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */ import { get_request_store } from '@sveltejs/kit/internal/server'; @@ -13,6 +13,7 @@ import { flatten_issues } from '../../../form-utils.svelte.js'; import { get_cache, run_remote_function } from './shared.js'; +import { ValidationError } from '@sveltejs/kit/internal'; /** * Creates a form object that can be spread onto a `
` element. @@ -21,7 +22,7 @@ import { get_cache, run_remote_function } from './shared.js'; * * @template Output * @overload - * @param {(invalid: import('@sveltejs/kit').Invalid) => MaybePromise} fn + * @param {() => MaybePromise} fn * @returns {RemoteForm} * @since 2.27 */ @@ -34,7 +35,7 @@ import { get_cache, run_remote_function } from './shared.js'; * @template Output * @overload * @param {'unchecked'} validate - * @param {(data: Input, invalid: import('@sveltejs/kit').Invalid) => MaybePromise} fn + * @param {(data: Input, issue: InvalidField) => MaybePromise} fn * @returns {RemoteForm} * @since 2.27 */ @@ -47,7 +48,7 @@ import { get_cache, run_remote_function } from './shared.js'; * @template Output * @overload * @param {Schema} validate - * @param {(data: StandardSchemaV1.InferOutput, invalid: import('@sveltejs/kit').Invalid>) => MaybePromise} fn + * @param {(data: StandardSchemaV1.InferOutput, issue: InvalidField>) => MaybePromise} fn * @returns {RemoteForm, Output>} * @since 2.27 */ @@ -55,7 +56,7 @@ import { get_cache, run_remote_function } from './shared.js'; * @template {RemoteFormInput} Input * @template Output * @param {any} validate_or_fn - * @param {(data_or_invalid: any, invalid?: any) => MaybePromise} [maybe_fn] + * @param {(data_or_issue: any, issue?: any) => MaybePromise} [maybe_fn] * @returns {RemoteForm} * @since 2.27 */ @@ -165,7 +166,7 @@ export function form(validate_or_fn, maybe_fn) { state.refreshes ??= {}; - const invalid = create_invalid(); + const issue = create_issues(); try { output.result = await run_remote_function( @@ -174,7 +175,7 @@ export function form(validate_or_fn, maybe_fn) { true, data, (d) => d, - (data) => (!maybe_fn ? fn(invalid) : fn(data, invalid)) + (data) => (!maybe_fn ? fn() : fn(data, issue)) ); } catch (e) { if (e instanceof ValidationError) { @@ -329,89 +330,72 @@ function handle_issues(output, issues, is_remote_request, form_data) { /** * Creates an invalid function that can be used to imperatively mark form fields as invalid - * @returns {import('@sveltejs/kit').Invalid} + * @returns {InvalidField} */ -function create_invalid() { - /** - * @param {...(string | StandardSchemaV1.Issue)} issues - * @returns {never} - */ - function invalid(...issues) { - throw new ValidationError( - issues.map((issue) => { - if (typeof issue === 'string') { - return { - path: [], - message: issue - }; +function create_issues() { + return /** @type {InvalidField} */ ( + new Proxy( + /** @param {string} message */ + (message) => { + // TODO 3.0 remove + if (typeof message !== 'string') { + throw new Error( + 'invalid() should now be imported from @sveltejs/kit to throw validaition issues. ' + + 'Keep using the parameter (now named issue) provided to the form function only to construct the issues, e.g. invalid(issue.field("message")). ' + + 'For more info see https://github.com/sveltejs/kit/pulls/14768' + ); } - return issue; - }) - ); - } - - return /** @type {import('@sveltejs/kit').Invalid} */ ( - new Proxy(invalid, { - get(target, prop) { - if (typeof prop === 'symbol') return /** @type {any} */ (target)[prop]; + return create_issue(message); + }, + { + get(target, prop) { + if (typeof prop === 'symbol') return /** @type {any} */ (target)[prop]; - /** - * @param {string} message - * @param {(string | number)[]} path - * @returns {StandardSchemaV1.Issue} - */ - const create_issue = (message, path = []) => ({ - message, - path - }); - - return create_issue_proxy(prop, create_issue, []); + return create_issue_proxy(prop, []); + } } - }) + ) ); -} -/** - * Error thrown when form validation fails imperatively - */ -class ValidationError extends Error { /** - * @param {StandardSchemaV1.Issue[]} issues + * @param {string} message + * @param {(string | number)[]} path + * @returns {StandardSchemaV1.Issue} */ - constructor(issues) { - super('Validation failed'); - this.name = 'ValidationError'; - this.issues = issues; + function create_issue(message, path = []) { + return { + message, + path + }; } -} - -/** - * Creates a proxy that builds up a path and returns a function to create an issue - * @param {string | number} key - * @param {(message: string, path: (string | number)[]) => StandardSchemaV1.Issue} create_issue - * @param {(string | number)[]} path - */ -function create_issue_proxy(key, create_issue, path) { - const new_path = [...path, key]; /** - * @param {string} message - * @returns {StandardSchemaV1.Issue} + * Creates a proxy that builds up a path and returns a function to create an issue + * @param {string | number} key + * @param {(string | number)[]} path */ - const issue_func = (message) => create_issue(message, new_path); + function create_issue_proxy(key, path) { + const new_path = [...path, key]; - return new Proxy(issue_func, { - get(target, prop) { - if (typeof prop === 'symbol') return /** @type {any} */ (target)[prop]; + /** + * @param {string} message + * @returns {StandardSchemaV1.Issue} + */ + const issue_func = (message) => create_issue(message, new_path); - // Handle array access like invalid.items[0] - if (/^\d+$/.test(prop)) { - return create_issue_proxy(parseInt(prop, 10), create_issue, new_path); - } + return new Proxy(issue_func, { + get(target, prop) { + if (typeof prop === 'symbol') return /** @type {any} */ (target)[prop]; - // Handle property access like invalid.field.nested - return create_issue_proxy(prop, create_issue, new_path); - } - }); + // Handle array access like invalid.items[0] + if (/^\d+$/.test(prop)) { + return create_issue_proxy(parseInt(prop, 10), new_path); + } + + // Handle property access like invalid.field.nested + return create_issue_proxy(prop, new_path); + } + }); + } } diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts b/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts index 10df815353a7..a823759c3dff 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts +++ b/packages/kit/test/apps/basics/src/routes/remote/form/validate/form.remote.ts @@ -1,4 +1,5 @@ import { form } from '$app/server'; +import { invalid } from '@sveltejs/kit'; import * as v from 'valibot'; export const my_form = form( @@ -7,10 +8,10 @@ export const my_form = form( bar: v.picklist(['d', 'e', 'f']), button: v.optional(v.literal('submitter')) }), - async (data, invalid) => { + async (data, issue) => { // Test imperative validation if (data.foo === 'c') { - invalid(invalid.foo('Imperative: foo cannot be c')); + invalid(issue.foo('Imperative: foo cannot be c')); } console.log(data); diff --git a/packages/kit/test/types/remote.test.ts b/packages/kit/test/types/remote.test.ts index 760663fa9291..605645254919 100644 --- a/packages/kit/test/types/remote.test.ts +++ b/packages/kit/test/types/remote.test.ts @@ -1,6 +1,6 @@ import { query, prerender, command, form } from '$app/server'; import { StandardSchemaV1 } from '@standard-schema/spec'; -import { RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; +import { RemotePrerenderFunction, RemoteQueryFunction, invalid } from '@sveltejs/kit'; const schema: StandardSchemaV1 = null as any; const schema2: StandardSchemaV1 = null as any; @@ -159,14 +159,16 @@ command_tests(); function form_tests() { const q = query(() => ''); - const f = form('unchecked', (data: { input: string }, invalid) => { + const f = form('unchecked', (data: { input: string }, issue) => { data.input; - invalid( - 'foo', - invalid.input('bar'), - // @ts-expect-error - invalid.nonexistent.prop('baz') - ); + if (Math.random() > 0.5) { + invalid( + 'foo', + issue.input('bar'), + // @ts-expect-error + issue.nonexistent.prop('baz') + ); + } return { success: true }; }); @@ -184,7 +186,7 @@ function form_tests() { const f2 = form( null as any as StandardSchemaV1<{ a: string; nested: { prop: string } }>, - (data, invalid) => { + (data, issue) => { data.a === ''; data.nested.prop === ''; // @ts-expect-error @@ -193,17 +195,19 @@ function form_tests() { data.nonexistent; // @ts-expect-error data.a === 123; - invalid( - 'foo', - invalid.nested.prop('bar'), - // @ts-expect-error - invalid.nonexistent.prop('baz') - ); + if (Math.random() > 0.5) { + invalid( + 'foo', + issue.nested.prop('bar'), + // @ts-expect-error + issue.nonexistent.prop('baz') + ); + } return { success: true }; } ); // @ts-expect-error - f2.fields.name(); + f2.fields.as('text'); f2.fields.a.issues(); f2.fields.nested.prop.issues(); // @ts-expect-error @@ -213,12 +217,12 @@ function form_tests() { // @ts-expect-error f2.fields.nonexistent.value(); // @ts-expect-error - f2.fields.array[0].array.name(); + f2.fields.array[0].array.as('text'); // all schema properties optional const f3 = form( null as any as StandardSchemaV1<{ a?: string; nested?: { prop?: string } }>, - (data, invalid) => { + (data, issue) => { data.a === ''; data.nested?.prop === ''; // @ts-expect-error @@ -229,23 +233,25 @@ function form_tests() { data.nonexistent; // @ts-expect-error data.a === 123; - invalid( - 'foo', - invalid.nested.prop('bar'), - // @ts-expect-error - invalid.nonexistent.prop('baz') - ); + if (Math.random() > 0.5) { + invalid( + 'foo', + issue.nested.prop('bar'), + // @ts-expect-error + issue.nonexistent.prop('baz') + ); + } return { success: true }; } ); // @ts-expect-error - f3.fields.name(); + f3.fields.as('text'); f3.fields.a.issues(); f3.fields.a.value(); f3.fields.nested.prop.issues(); f3.fields.nested.prop.value(); // @ts-expect-error - f3.fields.nonexistent.name(); + f3.fields.nonexistent.as('text'); // index signature schema const f4 = form(null as any as StandardSchemaV1>, (data) => { @@ -254,7 +260,7 @@ function form_tests() { return { success: true }; }); // @ts-expect-error - f4.fields.name(); + f4.fields.as('text'); f4.fields.a.issues(); f4.fields.a.value(); f4.fields.nested.prop.issues(); @@ -263,22 +269,24 @@ function form_tests() { // schema with union types const f5 = form( null as any as StandardSchemaV1<{ foo: 'a' | 'b'; bar: 'c' | 'd' }>, - (data, invalid) => { + (data, issue) => { data.foo === 'a'; data.bar === 'c'; // @ts-expect-error data.foo === 'e'; - invalid( - 'foo', - invalid.bar('bar'), - // @ts-expect-error - invalid.nonexistent.prop('baz') - ); + if (Math.random() > 0.5) { + invalid( + 'foo', + issue.bar('bar'), + // @ts-expect-error + issue.nonexistent.prop('baz') + ); + } return { success: true }; } ); // @ts-expect-error - f5.fields.name(); + f5.fields.as('text'); f5.fields.foo.issues(); f5.fields.bar.issues(); f5.fields.foo.value(); @@ -286,27 +294,29 @@ function form_tests() { // @ts-expect-error f5.fields.foo.value() === 'e'; // @ts-expect-error - f5.fields.nonexistent.name(); + f5.fields.nonexistent.as('text'); // schema with arrays const f6 = form( null as any as StandardSchemaV1<{ array: Array<{ array: string[]; prop: string }> }>, - (data, invalid) => { + (data, issue) => { data.array[0].prop === 'a'; data.array[0].array[0] === 'a'; // @ts-expect-error data.array[0].array[0] === 1; - invalid( - 'foo', - invalid.array[0].prop('bar'), - // @ts-expect-error - invalid.nonexistent.prop('baz') - ); + if (Math.random() > 0.5) { + invalid( + 'foo', + issue.array[0].prop('bar'), + // @ts-expect-error + issue.nonexistent.prop('baz') + ); + } return { success: true }; } ); // @ts-expect-error - f6.fields.name(); + f6.fields.as('text'); // @ts-expect-error f6.field('array[0].array'); f6.fields.array.issues(); @@ -317,29 +327,27 @@ function form_tests() { f6.fields.array[0].prop.value(); f6.fields.array[0].array.value(); // @ts-expect-error - f6.fields.array[0].array.name(); + f6.fields.array[0].array.as('text'); // any - const f7 = form(null as any, (data, invalid) => { + const f7 = form(null as any, (data, issue) => { data.a === ''; data.nested?.prop === ''; - invalid('foo', invalid.nested.prop('bar')); + if (Math.random() > 0.5) { + invalid('foo', issue.nested.prop('bar')); + } return { success: true }; }); // @ts-expect-error - f7.fields.name(); + f7.fields.as('text'); f7.fields.a.issues(); f7.fields.a.value(); f7.fields.nested.prop.issues(); f7.fields.nested.prop.value(); // no schema - const f8 = form((invalid) => { - invalid( - 'foo', - // @ts-expect-error - invalid.x('bar') - ); + const f8 = form(() => { + invalid('foo'); }); // @ts-expect-error f8.fields.x; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 52fcb547490d..6c7a01caf742 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1950,10 +1950,13 @@ declare module '@sveltejs/kit' { : string | number; /** - * Recursively maps an input type to a structure where each field can create a validation issue. - * This mirrors the runtime behavior of the `invalid` proxy passed to form handlers. + * A function and proxy object used to imperatively create validation errors in form handlers. + * + * Access properties to create field-specific issues: `issue.fieldName('message')`. + * The type structure mirrors the input data structure for type-safe field access. + * Call `invalid(issue.foo(...), issue.nested.bar(...))` to throw a validation error. */ - type InvalidField = + export type InvalidField = WillRecurseIndefinitely extends true ? Record : NonNullable extends string | number | boolean | File @@ -1969,15 +1972,12 @@ declare module '@sveltejs/kit' { : Record; /** - * A function and proxy object used to imperatively create validation errors in form handlers. - * - * Call `invalid(issue1, issue2, ...issueN)` to throw a validation error. - * If an issue is a `string`, it applies to the form as a whole (and will show up in `fields.allIssues()`) - * Access properties to create field-specific issues: `invalid.fieldName('message')`. - * The type structure mirrors the input data structure for type-safe field access. + * A validation error thrown by `invalid`. */ - export type Invalid = ((...issues: Array) => never) & - InvalidField; + export interface ValidationError { + /** The validation issues */ + issues: StandardSchemaV1.Issue[]; + } /** * The return value of a remote `form` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. @@ -2683,6 +2683,41 @@ declare module '@sveltejs/kit' { * @param e The object to check. * */ export function isActionFailure(e: unknown): e is ActionFailure; + /** + * Use this to throw a validation error to imperatively fail form validation. + * Can be used in combination with `issue` passed to form actions to create field-specific issues. + * + * @example + * ```ts + * import { invalid } from '@sveltejs/kit'; + * import { form } from '$app/server'; + * import * as v from 'valibot'; + * + * function tryRegisterUser(name: string, password: string) { + * // ... + * } + * + * export const register = form( + * v.object({ name: v.string(), _password: v.string() }), + * async ({ name, _password }, issue) => { + * const success = tryRegisterUser(name, _password); + * if (!success) { + * invalid('Registration failed', issue.name('This username is already taken')); + * } + * + * // ... + * } + * ); + * ``` + * @since 2.47.3 + */ + export function invalid(...issues: (StandardSchemaV1.Issue | string)[]): never; + /** + * Checks whether this is an validation error thrown by {@link invalid}. + * @param e The object to check. + * @since 2.47.3 + */ + export function isValidationError(e: unknown): e is ActionFailure; /** * Strips possible SvelteKit-internal suffixes and trailing slashes from the URL pathname. * Returns the normalized URL as well as a method for adding the potential suffix back @@ -3115,7 +3150,7 @@ declare module '$app/paths' { } declare module '$app/server' { - import type { RequestEvent, RemoteCommand, RemoteForm, RemoteFormInput, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; + import type { RequestEvent, RemoteCommand, RemoteForm, RemoteFormInput, InvalidField, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; import type { StandardSchemaV1 } from '@standard-schema/spec'; /** * Read the contents of an imported asset from the filesystem @@ -3169,7 +3204,7 @@ declare module '$app/server' { * * @since 2.27 */ - export function form(fn: (invalid: import("@sveltejs/kit").Invalid) => MaybePromise): RemoteForm; + export function form(fn: () => MaybePromise): RemoteForm; /** * Creates a form object that can be spread onto a `` element. * @@ -3177,7 +3212,7 @@ declare module '$app/server' { * * @since 2.27 */ - export function form(validate: "unchecked", fn: (data: Input, invalid: import("@sveltejs/kit").Invalid) => MaybePromise): RemoteForm; + export function form(validate: "unchecked", fn: (data: Input, issue: InvalidField) => MaybePromise): RemoteForm; /** * Creates a form object that can be spread onto a `` element. * @@ -3185,7 +3220,7 @@ declare module '$app/server' { * * @since 2.27 */ - export function form>, Output>(validate: Schema, fn: (data: StandardSchemaV1.InferOutput, invalid: import("@sveltejs/kit").Invalid>) => MaybePromise): RemoteForm, Output>; + export function form>, Output>(validate: Schema, fn: (data: StandardSchemaV1.InferOutput, issue: InvalidField>) => MaybePromise): RemoteForm, Output>; /** * Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call. *