Skip to content

Commit 6ad721b

Browse files
authored
Add RemoteCommand type (#14020)
* Add RemoteCommand type * fix async lint error * documentation * regenerate types
1 parent 9735f19 commit 6ad721b

File tree

4 files changed

+123
-23
lines changed

4 files changed

+123
-23
lines changed

packages/kit/src/exports/public.d.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,60 @@ export type RemoteFormAction<Success, Failure> = ((data: FormData) => Promise<vo
16291629
};
16301630
};
16311631

1632+
/**
1633+
* The return value of a remote `command` function.
1634+
* Call it with the input arguments to execute the command.
1635+
*
1636+
* Note: Prefer remote `form` functions when possible, as they
1637+
* work without JavaScript enabled.
1638+
*
1639+
* ```svelte
1640+
* <script>
1641+
* import { createTodo } from './todos.remote.js';
1642+
*
1643+
* let text = $state('');
1644+
* </script>
1645+
*
1646+
* <input bind:value={text} />
1647+
* <button onclick={async () => {
1648+
* await createTodo({ text });
1649+
* }}>
1650+
* Create Todo
1651+
* </button>
1652+
* ```
1653+
* Use the `updates` method to specify which queries to update in response to the command.
1654+
* ```svelte
1655+
* <script>
1656+
* import { getTodos, createTodo } from './todos.remote.js';
1657+
*
1658+
* let text = $state('');
1659+
* </script>
1660+
*
1661+
* <input bind:value={text} />
1662+
* <button onclick={async () => {
1663+
* await createTodo({ text }).updates(
1664+
* getTodos.withOverride((todos) => [...todos, { text, done: false }])
1665+
* );
1666+
* }}>
1667+
* Create Todo
1668+
* </button>
1669+
*
1670+
* <ul>
1671+
* {#each await getTodos() as todo}
1672+
* <li>{todo.text}</li>
1673+
* {/each}
1674+
* </ul>
1675+
* ```
1676+
*/
1677+
export type RemoteCommand<Input, Output> = (arg: Input) => Promise<Awaited<Output>> & {
1678+
updates: (
1679+
...queries: Array<
1680+
| ReturnType<RemoteQuery<any, any>>
1681+
| ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>
1682+
>
1683+
) => Promise<Awaited<Output>>;
1684+
};
1685+
16321686
/**
16331687
* The return value of a remote `query` or `prerender` function.
16341688
* Call it with the input arguments to retrieve the value.

packages/kit/src/runtime/app/server/remote.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { RemoteFormAction, RemoteQuery, RequestEvent, ActionFailure as IActionFailure } from '@sveltejs/kit' */
1+
/** @import { RemoteFormAction, RemoteQuery, RemoteCommand, RequestEvent, ActionFailure as IActionFailure } from '@sveltejs/kit' */
22
/** @import { RemotePrerenderEntryGenerator, RemoteInfo, ServerHooks, MaybePromise } from 'types' */
33
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
44

@@ -304,7 +304,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
304304
return result;
305305
})();
306306

307-
promise.refresh = async () => {
307+
promise.refresh = () => {
308308
throw new Error(
309309
`Cannot call '${wrapper.__.name}.refresh()'. Remote prerender functions are immutable and cannot be refreshed.`
310310
);
@@ -465,7 +465,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
465465
* @template Output
466466
* @overload
467467
* @param {() => Output} fn
468-
* @returns {() => Promise<Awaited<Output>> & { updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>>) => Promise<Awaited<Output>> }}
468+
* @returns {RemoteCommand<void, Output>}
469469
*/
470470
/**
471471
* Creates a remote command. The given function is invoked directly on the server and via a fetch call on the client.
@@ -501,7 +501,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
501501
* @overload
502502
* @param {'unchecked'} validate
503503
* @param {(arg: Input) => Output} fn
504-
* @returns {(arg: Input) => Promise<Awaited<Output>> & { updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>>) => Promise<Awaited<Output>> }}
504+
* @returns {RemoteCommand<Input, Output>}
505505
*/
506506
/**
507507
* Creates a remote command. The given function is invoked directly on the server and via a fetch call on the client.
@@ -537,14 +537,14 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
537537
* @overload
538538
* @param {Schema} validate
539539
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => Output} fn
540-
* @returns {(arg: StandardSchemaV1.InferOutput<Schema>) => Promise<Awaited<Output>> & { updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>>) => Promise<Awaited<Output>> }}
540+
* @returns {RemoteCommand<StandardSchemaV1.InferOutput<Schema>, Output>}
541541
*/
542542
/**
543543
* @template Input
544544
* @template Output
545545
* @param {any} validate_or_fn
546546
* @param {(arg?: Input) => Output} [maybe_fn]
547-
* @returns {(arg?: Input) => Promise<Awaited<Output>> & { updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>>) => Promise<Awaited<Output>> }}
547+
* @returns {RemoteCommand<Input, Output>}
548548
*/
549549
/*@__NO_SIDE_EFFECTS__*/
550550
export function command(validate_or_fn, maybe_fn) {
@@ -555,9 +555,7 @@ export function command(validate_or_fn, maybe_fn) {
555555
/** @type {(arg?: any) => MaybePromise<Input>} */
556556
const validate = create_validator(validate_or_fn, maybe_fn);
557557

558-
/**
559-
* @param {Input} [arg]
560-
*/
558+
/** @type {RemoteCommand<Input, Output> & { __: RemoteInfo }} */
561559
const wrapper = (arg) => {
562560
if (prerendering) {
563561
throw new Error(
@@ -582,7 +580,7 @@ export function command(validate_or_fn, maybe_fn) {
582580
promise.updates = () => {
583581
throw new Error(`Cannot call '${wrapper.__.name}(...).updates(...)' on the server`);
584582
};
585-
return /** @type {Promise<Awaited<Output>> & { updates: (...arsg: any[]) => any}} */ (promise);
583+
return /** @type {ReturnType<RemoteCommand<Input, Output>>} */ (promise);
586584
};
587585

588586
Object.defineProperty(wrapper, '__', {

packages/kit/src/runtime/client/remote.svelte.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { RemoteFormAction, RemoteQuery } from '@sveltejs/kit' */
1+
/** @import { RemoteFormAction, RemoteQuery, RemoteCommand } from '@sveltejs/kit' */
22
/** @import { RemoteFunctionResponse } from 'types' */
33

44
import { app_dir } from '__sveltekit/paths';
@@ -432,12 +432,12 @@ export function prerender(id) {
432432
/**
433433
* Client-version of the `command` function from `$app/server`.
434434
* @param {string} id
435-
* @returns {(arg: any) => Promise<any> & { updates: (...args: any[]) => any }}
435+
* @returns {RemoteCommand<any, any>}
436436
*/
437437
export function command(id) {
438438
// Careful: This function MUST be synchronous (can't use the async keyword) because the return type has to be a promise with an updates() method.
439439
// If we make it async, the return type will be a promise that resolves to a promise with an updates() method, which is not what we want.
440-
return (/** @type {any} */ arg) => {
440+
return (arg) => {
441441
/** @type {Array<Query<any> | ReturnType<Query<any>['withOverride']>>} */
442442
let updates = [];
443443

packages/kit/types/index.d.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,60 @@ declare module '@sveltejs/kit' {
16111611
};
16121612
};
16131613

1614+
/**
1615+
* The return value of a remote `command` function.
1616+
* Call it with the input arguments to execute the command.
1617+
*
1618+
* Note: Prefer remote `form` functions when possible, as they
1619+
* work without JavaScript enabled.
1620+
*
1621+
* ```svelte
1622+
* <script>
1623+
* import { createTodo } from './todos.remote.js';
1624+
*
1625+
* let text = $state('');
1626+
* </script>
1627+
*
1628+
* <input bind:value={text} />
1629+
* <button onclick={async () => {
1630+
* await createTodo({ text });
1631+
* }}>
1632+
* Create Todo
1633+
* </button>
1634+
* ```
1635+
* Use the `updates` method to specify which queries to update in response to the command.
1636+
* ```svelte
1637+
* <script>
1638+
* import { getTodos, createTodo } from './todos.remote.js';
1639+
*
1640+
* let text = $state('');
1641+
* </script>
1642+
*
1643+
* <input bind:value={text} />
1644+
* <button onclick={async () => {
1645+
* await createTodo({ text }).updates(
1646+
* getTodos.withOverride((todos) => [...todos, { text, done: false }])
1647+
* );
1648+
* }}>
1649+
* Create Todo
1650+
* </button>
1651+
*
1652+
* <ul>
1653+
* {#each await getTodos() as todo}
1654+
* <li>{todo.text}</li>
1655+
* {/each}
1656+
* </ul>
1657+
* ```
1658+
*/
1659+
export type RemoteCommand<Input, Output> = (arg: Input) => Promise<Awaited<Output>> & {
1660+
updates: (
1661+
...queries: Array<
1662+
| ReturnType<RemoteQuery<any, any>>
1663+
| ReturnType<ReturnType<RemoteQuery<any, any>>['withOverride']>
1664+
>
1665+
) => Promise<Awaited<Output>>;
1666+
};
1667+
16141668
/**
16151669
* The return value of a remote `query` or `prerender` function.
16161670
* Call it with the input arguments to retrieve the value.
@@ -2637,7 +2691,7 @@ declare module '$app/paths' {
26372691
}
26382692

26392693
declare module '$app/server' {
2640-
import type { RequestEvent, RemoteQuery, ActionFailure as IActionFailure, RemoteFormAction } from '@sveltejs/kit';
2694+
import type { RequestEvent, RemoteQuery, RemoteCommand, ActionFailure as IActionFailure, RemoteFormAction } from '@sveltejs/kit';
26412695
import type { StandardSchemaV1 } from '@standard-schema/spec';
26422696
/**
26432697
* Read the contents of an imported asset from the filesystem
@@ -2823,9 +2877,7 @@ declare module '$app/server' {
28232877
* ```
28242878
*
28252879
* */
2826-
export function command<Output>(fn: () => Output): () => Promise<Awaited<Output>> & {
2827-
updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>["withOverride"]>>) => Promise<Awaited<Output>>;
2828-
};
2880+
export function command<Output>(fn: () => Output): RemoteCommand<void, Output>;
28292881
/**
28302882
* Creates a remote command. The given function is invoked directly on the server and via a fetch call on the client.
28312883
*
@@ -2856,9 +2908,7 @@ declare module '$app/server' {
28562908
* ```
28572909
*
28582910
* */
2859-
export function command<Input, Output>(validate: "unchecked", fn: (arg: Input) => Output): (arg: Input) => Promise<Awaited<Output>> & {
2860-
updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>["withOverride"]>>) => Promise<Awaited<Output>>;
2861-
};
2911+
export function command<Input, Output>(validate: "unchecked", fn: (arg: Input) => Output): RemoteCommand<Input, Output>;
28622912
/**
28632913
* Creates a remote command. The given function is invoked directly on the server and via a fetch call on the client.
28642914
*
@@ -2889,9 +2939,7 @@ declare module '$app/server' {
28892939
* ```
28902940
*
28912941
* */
2892-
export function command<Schema extends StandardSchemaV1, Output>(validate: Schema, fn: (arg: StandardSchemaV1.InferOutput<Schema>) => Output): (arg: StandardSchemaV1.InferOutput<Schema>) => Promise<Awaited<Output>> & {
2893-
updates: (...queries: Array<ReturnType<RemoteQuery<any, any>> | ReturnType<ReturnType<RemoteQuery<any, any>>["withOverride"]>>) => Promise<Awaited<Output>>;
2894-
};
2942+
export function command<Schema extends StandardSchemaV1, Output>(validate: Schema, fn: (arg: StandardSchemaV1.InferOutput<Schema>) => Output): RemoteCommand<StandardSchemaV1.InferOutput<Schema>, Output>;
28952943
/**
28962944
* Creates a form action. The passed function will be called when the form is submitted.
28972945
* Returns an object that can be spread onto a form element to connect it to the function.

0 commit comments

Comments
 (0)