diff --git a/.changeset/sour-vans-fix.md b/.changeset/sour-vans-fix.md new file mode 100644 index 000000000000..ff5f688901f6 --- /dev/null +++ b/.changeset/sour-vans-fix.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: allow commands from within endpoints diff --git a/packages/kit/src/runtime/app/server/remote/command.js b/packages/kit/src/runtime/app/server/remote/command.js index 117b5b6eca19..2ea88bfb02f0 100644 --- a/packages/kit/src/runtime/app/server/remote/command.js +++ b/packages/kit/src/runtime/app/server/remote/command.js @@ -64,7 +64,13 @@ export function command(validate_or_fn, maybe_fn) { const wrapper = (arg) => { const { event, state } = get_request_store(); - if (!event.isRemoteRequest) { + if (state.is_endpoint_request) { + if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(event.request.method)) { + throw new Error( + `Cannot call a command (\`${__.name}(${maybe_fn ? '...' : ''})\`) from a ${event.request.method} handler` + ); + } + } else if (!event.isRemoteRequest) { throw new Error( `Cannot call a command (\`${__.name}(${maybe_fn ? '...' : ''})\`) during server-side rendering` ); diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js index e79b5d083925..a8fe8719ca34 100644 --- a/packages/kit/src/runtime/server/endpoint.js +++ b/packages/kit/src/runtime/server/endpoint.js @@ -41,6 +41,8 @@ export async function render_endpoint(event, event_state, mod, state) { } } + event_state.is_endpoint_request = true; + try { const response = await with_request_store({ event, state: event_state }, () => handler(/** @type {import('@sveltejs/kit').RequestEvent>} */ (event)) diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index d5aea28c39f1..14f026d110ab 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -588,6 +588,7 @@ export interface RequestState { form_instances?: Map; remote_data?: Record>; refreshes?: Record>; + is_endpoint_request?: boolean; } export interface RequestStore { diff --git a/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/+page.svelte new file mode 100644 index 000000000000..c7b770912bf3 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/+page.svelte @@ -0,0 +1,23 @@ + + +

{result}

+ + + + diff --git a/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/api/+server.ts b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/api/+server.ts new file mode 100644 index 000000000000..dfc4e271b451 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/api/+server.ts @@ -0,0 +1,12 @@ +import { json } from '@sveltejs/kit'; +import { add, get } from '../internal.remote'; + +export async function GET() { + const result = await get(); + return json({ result }); +} + +export async function POST() { + const result = await add(1); + return json({ result }); +} diff --git a/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/internal.remote.ts b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/internal.remote.ts new file mode 100644 index 000000000000..25e83a63bee9 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/server-endpoint/internal.remote.ts @@ -0,0 +1,9 @@ +import { command, query } from '$app/server'; + +export const get = query(() => { + return 'get'; +}); + +export const add = command('unchecked', () => { + return 'post'; +}); diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 44d6a794f171..0f3f5d1cc792 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -1754,6 +1754,16 @@ test.describe('remote functions', () => { expect(request_count).toBe(1); // no query refreshes, since that happens as part of the command response }); + test('query/command inside endpoint works', async ({ page }) => { + await page.goto('/remote/server-endpoint'); + + await page.getByRole('button', { name: 'get' }).click(); + await expect(page.locator('p')).toHaveText('get'); + + await page.getByRole('button', { name: 'post' }).click(); + await expect(page.locator('p')).toHaveText('post'); + }); + test('form.enhance works', async ({ page }) => { await page.goto('/remote/form'); await page.fill('#input-task-enhance', 'abort');