From 01a4071ea95ca158f87c0942f8cefdd76a2fd59f Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sun, 26 Oct 2025 15:30:49 -0500 Subject: [PATCH 01/12] feat: remote form factory --- .changeset/all-symbols-hammer.md | 5 + .../20-core-concepts/60-remote-functions.md | 105 ++++--- packages/kit/src/exports/public.d.ts | 19 +- .../kit/src/runtime/app/server/remote/form.js | 271 +++++++++--------- .../client/remote-functions/form.svelte.js | 57 ++-- packages/kit/src/runtime/server/remote.js | 26 +- packages/kit/src/types/internal.d.ts | 5 +- .../src/routes/remote/form/+page.svelte | 27 +- .../remote/form/imperative/+page.svelte | 14 +- .../remote/form/preflight-only/+page.svelte | 16 +- .../routes/remote/form/preflight/+page.svelte | 15 +- .../remote/form/select-untouched/+page.svelte | 8 +- .../routes/remote/form/submitter/+page.svelte | 8 +- .../remote/form/underscore/+page.svelte | 10 +- .../routes/remote/form/validate/+page.svelte | 19 +- .../src/routes/remote/form/value/+page.svelte | 21 +- packages/kit/test/apps/basics/test/test.js | 3 +- packages/kit/types/index.d.ts | 33 +-- 18 files changed, 350 insertions(+), 312 deletions(-) create mode 100644 .changeset/all-symbols-hammer.md diff --git a/.changeset/all-symbols-hammer.md b/.changeset/all-symbols-hammer.md new file mode 100644 index 000000000000..0e9d0d649e89 --- /dev/null +++ b/.changeset/all-symbols-hammer.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: remote form factory diff --git a/documentation/docs/20-core-concepts/60-remote-functions.md b/documentation/docs/20-core-concepts/60-remote-functions.md index 969b90d17121..86218a540b13 100644 --- a/documentation/docs/20-core-concepts/60-remote-functions.md +++ b/documentation/docs/20-core-concepts/60-remote-functions.md @@ -288,11 +288,12 @@ export const createPost = form(

Create a new post

-
+ @@ -308,15 +309,20 @@ As with `query`, if the callback uses the submitted `data`, it should be [valida A form is composed of a set of _fields_, which are defined by the schema. In the case of `createPost`, we have two fields, `title` and `content`, which are both strings. To get the attributes for a field, call its `.as(...)` method, specifying which [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types) to use: ```svelte - + + + @@ -351,10 +357,11 @@ export const createProfile = form(datingProfile, (data) => { /* ... */ }); - + @@ -401,12 +408,17 @@ export const survey = form( ``` ```svelte - + + +

Which operating system do you use?

{#each ['windows', 'mac', 'linux'] as os} {/each} @@ -415,7 +427,7 @@ export const survey = form( {#each ['html', 'css', 'js'] as language} {/each} @@ -427,10 +439,10 @@ export const survey = form( Alternatively, you could use `select` and `select multiple`: ```svelte - +

Which operating system do you use?

- @@ -438,7 +450,7 @@ Alternatively, you could use `select` and `select multiple`:

Which languages do you write code in?

- @@ -492,25 +504,30 @@ The `invalid` function works as both a function and a proxy: If the submitted data doesn't pass the schema, the callback will not run. Instead, each invalid field's `issues()` method will return an array of `{ message: string }` objects, and the `aria-invalid` attribute (returned from `as(...)`) will be set to `true`: ```svelte - + + + @@ -520,7 +537,7 @@ If the submitted data doesn't pass the schema, the callback will not run. Instea You don't need to wait until the form is submitted to validate the data — you can call `validate()` programmatically, for example in an `oninput` callback (which will validate the data on every keystroke) or an `onchange` callback: ```svelte - createPost.validate()}> + form.validate()}>
``` @@ -534,6 +551,8 @@ For client-side validation, you can specify a _preflight_ schema which will popu import * as v from 'valibot'; import { createPost } from '../data.remote'; + const form = createPost(); + const schema = v.object({ title: v.pipe(v.string(), v.nonEmpty()), content: v.pipe(v.string(), v.nonEmpty()) @@ -542,7 +561,7 @@ For client-side validation, you can specify a _preflight_ schema which will popu

Create a new post

-
+
``` @@ -552,7 +571,7 @@ For client-side validation, you can specify a _preflight_ schema which will popu To get a list of _all_ issues, rather than just those belonging to a single field, you can use the `fields.allIssues()` method: ```svelte -{#each createPost.fields.allIssues() as issue} +{#each form.fields.allIssues() as issue}

{issue.message}

{/each} ``` @@ -562,17 +581,17 @@ To get a list of _all_ issues, rather than just those belonging to a single fiel Each field has a `value()` method that reflects its current value. As the user interacts with the form, it is automatically updated: ```svelte -
+
-

{createPost.fields.title.value()}

-
{@html render(createPost.fields.content.value())}
+

{form.fields.title.value()}

+
{@html render(form.fields.content.value())}
``` -Alternatively, `createPost.fields.value()` would return a `{ title, content }` object. +Alternatively, `form.fields.value()` would return a `{ title, content }` object. You can update a field (or a collection of fields) via the `set(...)` method: @@ -580,15 +599,17 @@ You can update a field (or a collection of fields) via the `set(...)` method: ``` @@ -599,15 +620,15 @@ In the case of a non-progressively-enhanced form submission (i.e. where JavaScri You can prevent sensitive data (such as passwords and credit card numbers) from being sent back to the user by using a name with a leading underscore: ```svelte -
+ @@ -666,7 +687,7 @@ The second is to drive the single-flight mutation from the client, which we'll s ### Returns and redirects -The example above uses [`redirect(...)`](@sveltejs-kit#redirect), which sends the user to the newly created page. Alternatively, the callback could return data, in which case it would be available as `createPost.result`: +The example above uses [`redirect(...)`](@sveltejs-kit#redirect), which sends the user to the newly created page. Alternatively, the callback could return data, in which case it would be available as `form.result`: ```ts /// file: src/routes/blog/data.remote.js @@ -711,15 +732,16 @@ export const createPost = form(

Create a new post

- +
-{#if createPost.result?.success} +{#if form.result?.success}

Successfully published!

{/if} ``` @@ -739,11 +761,12 @@ We can customize what happens when the form is submitted with the `enhance` meth

Create a new post

-
{ + { try { await submit(); form.reset(); @@ -796,7 +819,7 @@ The override will be applied immediately, and released when the submission compl ### Multiple instances of a form -Some forms may be repeated as part of a list. In this case you can create separate instances of a form function via `for(id)` to achieve isolation. +Some forms may be repeated as part of a list. In this case you can create separate instances of a form function via `form(id)` to achieve isolation. ```svelte @@ -807,7 +830,7 @@ Some forms may be repeated as part of a list. In this case you can create separa

Todos

{#each await getTodos() as todo} - {@const modify = modifyTodo.for(todo.id)} + {@const modify = modifyTodo(todo.id)} @@ -827,21 +850,23 @@ This attribute exists on the `buttonProps` property of a form object: - + - +
``` diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index cb42dc6cc1c0..2d5739ddf953 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2003,6 +2003,10 @@ type InvalidField = export type Invalid = ((...issues: Array) => never) & InvalidField; +export type RemoteFormFactory = ( + key?: ExtractId +) => RemoteForm; + /** * The return value of a remote `form` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. */ @@ -2026,21 +2030,6 @@ export type RemoteForm = { action: string; [attachment: symbol]: (node: HTMLFormElement) => void; }; - /** - * Create an instance of the form for the given `id`. - * The `id` is stringified and used for deduplication to potentially reuse existing instances. - * Useful when you have multiple forms that use the same remote form action, for example in a loop. - * ```svelte - * {#each todos as todo} - * {@const todoForm = updateTodo.for(todo.id)} - *
- * {#if todoForm.result?.invalid}

Invalid data

{/if} - * ... - *
- * {/each} - * ``` - */ - for(id: ExtractId): Omit, 'for'>; /** Preflight checks */ preflight(schema: StandardSchemaV1): RemoteForm; /** Validate the form contents programmatically */ diff --git a/packages/kit/src/runtime/app/server/remote/form.js b/packages/kit/src/runtime/app/server/remote/form.js index 7bc7259185e9..500bc365b435 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, RemoteFormFactory } from '@sveltejs/kit' */ /** @import { InternalRemoteFormIssue, MaybePromise, RemoteInfo } from 'types' */ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */ import { get_request_store } from '@sveltejs/kit/internal/server'; @@ -15,18 +15,18 @@ import { import { get_cache, run_remote_function } from './shared.js'; /** - * Creates a form object that can be spread onto a `
` element. + * Creates a factory function that returns form instances which can be spread onto a `` element. * * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. * * @template Output * @overload * @param {(invalid: import('@sveltejs/kit').Invalid) => MaybePromise} fn - * @returns {RemoteForm} + * @returns {RemoteFormFactory} * @since 2.27 */ /** - * Creates a form object that can be spread onto a `` element. + * Creates a factory function that returns form instances which can be spread onto a `` element. * * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. * @@ -35,11 +35,11 @@ import { get_cache, run_remote_function } from './shared.js'; * @overload * @param {'unchecked'} validate * @param {(data: Input, invalid: import('@sveltejs/kit').Invalid) => MaybePromise} fn - * @returns {RemoteForm} + * @returns {RemoteFormFactory} * @since 2.27 */ /** - * Creates a form object that can be spread onto a `` element. + * Creates a factory function that returns form instances which can be spread onto a `` element. * * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation. * @@ -48,7 +48,7 @@ import { get_cache, run_remote_function } from './shared.js'; * @overload * @param {Schema} validate * @param {(data: StandardSchemaV1.InferOutput, invalid: import('@sveltejs/kit').Invalid>) => MaybePromise} fn - * @returns {RemoteForm, Output>} + * @returns {RemoteFormFactory, Output>} * @since 2.27 */ /** @@ -56,7 +56,7 @@ import { get_cache, run_remote_function } from './shared.js'; * @template Output * @param {any} validate_or_fn * @param {(data_or_invalid: any, invalid?: any) => MaybePromise} [maybe_fn] - * @returns {RemoteForm} + * @returns {RemoteFormFactory} * @since 2.27 */ /*@__NO_SIDE_EFFECTS__*/ @@ -69,10 +69,125 @@ export function form(validate_or_fn, maybe_fn) { const schema = !maybe_fn || validate_or_fn === 'unchecked' ? null : /** @type {any} */ (validate_or_fn); + /** @type {RemoteInfo & { fn: (data: FormData, instance: RemoteForm) => Promise }} */ + const __ = { + type: 'form', + name: '', + id: '', + fn: async (form_data, instance) => { + const validate_only = form_data.get('sveltekit:validate_only') === 'true'; + + let data = maybe_fn ? convert_formdata(form_data) : undefined; + + if (data && data.id === undefined) { + const id = form_data.get('sveltekit:id'); + if (typeof id === 'string') { + data.id = JSON.parse(id); + } + } + + // TODO 3.0 remove this warning + if (DEV && !data) { + const error = () => { + throw new Error( + 'Remote form functions no longer get passed a FormData object. ' + + "`form` now has the same signature as `query` or `command`, i.e. it expects to be invoked like `form(schema, callback)` or `form('unchecked', callback)`. " + + 'The payload of the callback function is now a POJO instead of a FormData object. See https://kit.svelte.dev/docs/remote-functions#form for details.' + ); + }; + data = {}; + for (const key of [ + 'append', + 'delete', + 'entries', + 'forEach', + 'get', + 'getAll', + 'has', + 'keys', + 'set', + 'values' + ]) { + Object.defineProperty(data, key, { get: error }); + } + } + + /** @type {{ submission: true, input?: Record, issues?: InternalRemoteFormIssue[], result: Output }} */ + const output = {}; + + // make it possible to differentiate between user submission and programmatic `field.set(...)` updates + output.submission = true; + + const { event, state } = get_request_store(); + const validated = await schema?.['~standard'].validate(data); + + if (validate_only) { + return validated?.issues ?? []; + } + + if (validated?.issues !== undefined) { + handle_issues(output, validated.issues, event.isRemoteRequest, form_data); + } else { + if (validated !== undefined) { + data = validated.value; + } + + state.refreshes ??= {}; + + const invalid = create_invalid(); + + try { + output.result = await run_remote_function( + event, + state, + true, + data, + (d) => d, + (data) => (!maybe_fn ? fn(invalid) : fn(data, invalid)) + ); + } catch (e) { + if (e instanceof ValidationError) { + handle_issues(output, e.issues, event.isRemoteRequest, form_data); + } else { + throw e; + } + } + } + + // We don't need to care about args or deduplicating calls, because uneval results are only relevant in full page reloads + // where only one form submission is active at the same time + if (!event.isRemoteRequest) { + get_cache(/** @type any */ (instance).__, state)[''] ??= output; + } + + return output; + } + }; + /** - * @param {string | number | boolean} [key] + * @param {string | number} [key] */ function create_instance(key) { + const { state } = get_request_store(); + const instance_id = + key !== undefined ? `${__.id}/${encodeURIComponent(JSON.stringify(key))}` : __.id; + + // Create instance-specific info object with the correct id + /** @type {RemoteInfo} */ + const instance_info = { + type: 'form', + name: __.name, + id: instance_id, + fn: __.fn + }; + + // Check cache for keyed instances + const cache_key = instance_info.id + '|' + JSON.stringify(key); + const cached = (state.form_instances ??= new Map()).get(cache_key); + if (cached) { + return cached; + } + /** @type {RemoteForm} */ const instance = {}; @@ -99,117 +214,21 @@ export function form(validate_or_fn, maybe_fn) { value: button_props }); - /** @type {RemoteInfo} */ - const __ = { - type: 'form', - name: '', - id: '', - /** @param {FormData} form_data */ - fn: async (form_data) => { - const validate_only = form_data.get('sveltekit:validate_only') === 'true'; - - let data = maybe_fn ? convert_formdata(form_data) : undefined; - - if (data && data.id === undefined) { - const id = form_data.get('sveltekit:id'); - if (typeof id === 'string') { - data.id = JSON.parse(id); - } - } - - // TODO 3.0 remove this warning - if (DEV && !data) { - const error = () => { - throw new Error( - 'Remote form functions no longer get passed a FormData object. ' + - "`form` now has the same signature as `query` or `command`, i.e. it expects to be invoked like `form(schema, callback)` or `form('unchecked', callback)`. " + - 'The payload of the callback function is now a POJO instead of a FormData object. See https://kit.svelte.dev/docs/remote-functions#form for details.' - ); - }; - data = {}; - for (const key of [ - 'append', - 'delete', - 'entries', - 'forEach', - 'get', - 'getAll', - 'has', - 'keys', - 'set', - 'values' - ]) { - Object.defineProperty(data, key, { get: error }); - } - } - - /** @type {{ submission: true, input?: Record, issues?: InternalRemoteFormIssue[], result: Output }} */ - const output = {}; - - // make it possible to differentiate between user submission and programmatic `field.set(...)` updates - output.submission = true; - - const { event, state } = get_request_store(); - const validated = await schema?.['~standard'].validate(data); - - if (validate_only) { - return validated?.issues ?? []; - } - - if (validated?.issues !== undefined) { - handle_issues(output, validated.issues, event.isRemoteRequest, form_data); - } else { - if (validated !== undefined) { - data = validated.value; - } - - state.refreshes ??= {}; - - const invalid = create_invalid(); - - try { - output.result = await run_remote_function( - event, - state, - true, - data, - (d) => d, - (data) => (!maybe_fn ? fn(invalid) : fn(data, invalid)) - ); - } catch (e) { - if (e instanceof ValidationError) { - handle_issues(output, e.issues, event.isRemoteRequest, form_data); - } else { - throw e; - } - } - } - - // We don't need to care about args or deduplicating calls, because uneval results are only relevant in full page reloads - // where only one form submission is active at the same time - if (!event.isRemoteRequest) { - get_cache(__, state)[''] ??= output; - } - - return output; - } - }; - - Object.defineProperty(instance, '__', { value: __ }); + Object.defineProperty(instance, '__', { value: instance_info }); Object.defineProperty(instance, 'action', { - get: () => `?/remote=${__.id}`, + get: () => `?/remote=${instance_info.id}`, enumerable: true }); Object.defineProperty(button_props, 'formaction', { - get: () => `?/remote=${__.id}`, + get: () => `?/remote=${instance_info.id}`, enumerable: true }); Object.defineProperty(instance, 'fields', { get() { - const data = get_cache(__)?.['']; + const data = get_cache(instance_info)?.['']; const issues = flatten_issues(data?.issues ?? []); return create_field_proxy( @@ -224,7 +243,7 @@ export function form(validate_or_fn, maybe_fn) { const input = path.length === 0 ? value : deep_set(data?.input ?? {}, path.map(String), value); - (get_cache(__)[''] ??= {}).input = input; + (get_cache(instance_info)[''] ??= {}).input = input; }, () => issues ); @@ -239,7 +258,7 @@ export function form(validate_or_fn, maybe_fn) { Object.defineProperty(instance, 'result', { get() { try { - return get_cache(__)?.['']?.result; + return get_cache(instance_info)?.['']?.result; } catch { return undefined; } @@ -267,31 +286,19 @@ export function form(validate_or_fn, maybe_fn) { } }); - if (key == undefined) { - Object.defineProperty(instance, 'for', { - /** @type {RemoteForm['for']} */ - value: (key) => { - const { state } = get_request_store(); - const cache_key = __.id + '|' + JSON.stringify(key); - let instance = (state.form_instances ??= new Map()).get(cache_key); - - if (!instance) { - instance = create_instance(key); - instance.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key))}`; - instance.__.name = __.name; - - state.form_instances.set(cache_key, instance); - } - - return instance; - } - }); - } + // Cache keyed instances + const form_instances = state.form_instances ?? (state.form_instances = new Map()); + form_instances.set(cache_key, instance); return instance; } - return create_instance(); + /** @type {RemoteFormFactory} */ + const factory = (key) => create_instance(key); + + Object.defineProperty(factory, '__', { value: __ }); + + return factory; } /** diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js index 02bbf9239720..9cba89f2a080 100644 --- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js @@ -1,5 +1,5 @@ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */ -/** @import { RemoteFormInput, RemoteForm, RemoteQueryOverride } from '@sveltejs/kit' */ +/** @import { RemoteFormInput, RemoteForm, RemoteQueryOverride, RemoteFormFactory } from '@sveltejs/kit' */ /** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */ /** @import { Query } from './query.svelte.js' */ import { app_dir, base } from '$app/paths/internal/client'; @@ -47,7 +47,7 @@ function merge_with_server_issues(form_data, current_issues, client_issues) { * @template {RemoteFormInput} T * @template U * @param {string} id - * @returns {RemoteForm} + * @returns {RemoteFormFactory} */ export function form(id) { /** @type {Map }>} */ @@ -55,7 +55,7 @@ export function form(id) { /** @param {string | number | boolean} [key] */ function create_instance(key) { - const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : ''); + const action_id = id + (key !== undefined ? `/${JSON.stringify(key)}` : ''); const action = '?/remote=' + encodeURIComponent(action_id); /** @@ -587,37 +587,38 @@ export function form(id) { return instance; } - const instance = create_instance(); - - Object.defineProperty(instance, 'for', { - /** @type {RemoteForm['for']} */ - value: (key) => { - const entry = instances.get(key) ?? { count: 0, instance: create_instance(key) }; + /** @type {RemoteFormFactory} */ + const factory = (key) => { + if (key === undefined) { + // For the default instance (no key), create a new instance each time + return create_instance(); + } - try { - $effect.pre(() => { - return () => { - entry.count--; + const entry = instances.get(key) ?? { count: 0, instance: create_instance(key) }; - void tick().then(() => { - if (entry.count === 0) { - instances.delete(key); - } - }); - }; - }); + try { + $effect.pre(() => { + return () => { + entry.count--; - entry.count += 1; - instances.set(key, entry); - } catch { - // not in an effect context - } + void tick().then(() => { + if (entry.count === 0) { + instances.delete(key); + } + }); + }; + }); - return entry.instance; + entry.count += 1; + instances.set(key, entry); + } catch { + // not in an effect context } - }); - return instance; + return entry.instance; + }; + + return factory; } /** diff --git a/packages/kit/src/runtime/server/remote.js b/packages/kit/src/runtime/server/remote.js index 88ae0d41387c..46d8a147240f 100644 --- a/packages/kit/src/runtime/server/remote.js +++ b/packages/kit/src/runtime/server/remote.js @@ -116,6 +116,9 @@ async function handle_remote_call_internal(event, state, options, manifest, id) ); } + const action_id = decodeURIComponent(additional_args ?? ''); + const instance = fn(action_id ? JSON.parse(action_id) : undefined); + const form_data = await event.request.formData(); form_client_refreshes = /** @type {string[]} */ ( JSON.parse(/** @type {string} */ (form_data.get('sveltekit:remote_refreshes')) ?? '[]') @@ -123,12 +126,14 @@ async function handle_remote_call_internal(event, state, options, manifest, id) form_data.delete('sveltekit:remote_refreshes'); // If this is a keyed form instance (created via form.for(key)), add the key to the form data (unless already set) - if (additional_args) { - form_data.set('sveltekit:id', decodeURIComponent(additional_args)); + if (action_id) { + form_data.set('sveltekit:id', action_id); } - const fn = info.fn; - const data = await with_request_store({ event, state }, () => fn(form_data)); + const form_fn = /** @type {RemoteInfo & { type: 'form' }} */ ( + /** @type {any} */ (instance).__ + ).fn; + const data = await with_request_store({ event, state }, () => form_fn(form_data, instance)); return json( /** @type {RemoteFunctionResponse} */ ({ @@ -267,7 +272,9 @@ async function handle_remote_form_post_internal(event, state, manifest, id) { const remotes = manifest._.remotes; const module = await remotes[hash]?.(); - let form = /** @type {RemoteForm} */ (module?.default[name]); + const form = /** @type {import("@sveltejs/kit").RemoteFormFactory} */ ( + module?.default[name] + ); if (!form) { event.setHeaders({ @@ -285,14 +292,11 @@ async function handle_remote_form_post_internal(event, state, manifest, id) { }; } - if (action_id) { - // @ts-expect-error - form = with_request_store({ event, state }, () => form.for(JSON.parse(action_id))); - } + const instance = form(action_id ? JSON.parse(action_id) : undefined); try { const form_data = await event.request.formData(); - const fn = /** @type {RemoteInfo & { type: 'form' }} */ (/** @type {any} */ (form).__).fn; + const fn = /** @type {RemoteInfo & { type: 'form' }} */ (/** @type {any} */ (instance).__).fn; // If this is a keyed form instance (created via form.for(key)), add the key to the form data (unless already set) if (action_id && !form_data.has('id')) { @@ -300,7 +304,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) { form_data.set('sveltekit:id', decodeURIComponent(action_id)); } - await with_request_store({ event, state }, () => fn(form_data)); + await with_request_store({ event, state }, () => fn(form_data, instance)); // We don't want the data to appear on `let { form } = $props()`, which is why we're not returning it. // It is instead available on `myForm.result`, setting of which happens within the remote `form` function. diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 84242df90417..2d359cca5498 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -22,7 +22,8 @@ import { ClientInit, Transport, HandleValidationError, - RemoteFormIssue + RemoteFormIssue, + RemoteForm } from '@sveltejs/kit'; import { HttpMethod, @@ -572,7 +573,7 @@ export type RemoteInfo = type: 'form'; id: string; name: string; - fn: (data: FormData) => Promise; + fn: (data: FormData, instance: RemoteForm) => Promise; } | { type: 'prerender'; diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/+page.svelte index 33f931628aa1..743ffbc5a173 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/+page.svelte @@ -8,8 +8,11 @@ const message = get_message(); - const scoped = set_message.for('scoped'); - const enhanced = set_message.for('enhanced'); + const scoped = set_message('scoped'); + const enhanced = set_message('enhanced'); + const message_form = set_message(); + const reverse_message_form = set_reverse_message(); + const deferreds = resolve_deferreds();

message.current: {message.current}

@@ -21,20 +24,20 @@
- - {#if set_message.fields.message.issues()} -

{set_message.fields.message.issues()[0].message}

+ + {#if message_form.fields.message.issues()} +

{message_form.fields.message.issues()[0].message}

{/if} - + - + -

set_message.input.message: {set_message.fields.message.value()}

-

set_message.pending: {set_message.pending}

-

set_message.result: {set_message.result}

-

set_reverse_message.result: {set_reverse_message.result}

+

set_message.input.message: {message_form.fields.message.value()}

+

set_message.pending: {message_form.pending}

+

set_message.result: {message_form.result}

+

set_reverse_message.result: {reverse_message_form.result}


@@ -73,6 +76,6 @@
-
+
diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/imperative/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/imperative/+page.svelte index c21e4d146af3..e3f6eadb5b90 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/imperative/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/imperative/+page.svelte @@ -2,6 +2,8 @@ import { set_message } from '../form.remote.js'; import * as v from 'valibot'; + const message_form = set_message(); + const schema = v.object({ message: v.picklist( ['hello', 'goodbye', 'unexpected error', 'expected error', 'redirect'], @@ -10,23 +12,23 @@ }); -
+

- {set_message.fields.message.issues()?.[0]?.message ?? 'ok'} + {message_form.fields.message.issues()?.[0]?.message ?? 'ok'}

-

{set_message.fields.message.value()}

+

{message_form.fields.message.value()}

- {#each set.fields.allIssues() as issue} + {#each set_form.fields.allIssues() as issue}

{issue.message}

{/each}
diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte index 38d1248d4515..fa540ceebf9a 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/preflight/+page.svelte @@ -4,7 +4,8 @@ const number = get_number(); - const enhanced = set_number.for('enhanced'); + const number_form = set_number(); + const enhanced = set_number('enhanced'); const schema = v.object({ number: v.pipe(v.number(), v.maxValue(20, 'too big')) @@ -20,18 +21,18 @@
-
- {#each set_number.fields.number.issues() as issue} + + {#each number_form.fields.number.issues() as issue}

{issue.message}

{/each} - +
-

set_number.input.number: {set_number.fields.number.value()}

-

set_number.pending: {set_number.pending}

-

set_number.result: {set_number.result}

+

set_number.input.number: {number_form.fields.number.value()}

+

set_number.pending: {number_form.pending}

+

set_number.result: {number_form.result}


diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/+page.svelte index 5caecdde39f2..a546968d76c0 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/select-untouched/+page.svelte @@ -1,11 +1,13 @@ -
- + + - diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/submitter/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/submitter/+page.svelte index d4648f9d462e..dff36b92ace8 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/submitter/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/submitter/+page.svelte @@ -1,9 +1,11 @@ - - + +
-

{my_form.result}

+

{my_form_form.result}

diff --git a/packages/kit/test/apps/basics/src/routes/remote/form/underscore/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/form/underscore/+page.svelte index 6c8a0aff7e4f..abb644635ad0 100644 --- a/packages/kit/test/apps/basics/src/routes/remote/form/underscore/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/remote/form/underscore/+page.svelte @@ -1,15 +1,17 @@ -
- - + + +
-
{JSON.stringify(register.fields.issues(), null, '  ')}
+
{JSON.stringify(register_form.fields.issues(), null, '  ')}