Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/polite-rooms-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: expose `event.route` and `event.url` to remote functions
5 changes: 4 additions & 1 deletion documentation/docs/20-core-concepts/60-remote-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,10 @@ const getUser = query(() => {
});
```

Note that some properties of `RequestEvent` are different inside remote functions. There are no `params` or `route.id`, and you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions), and `url.pathname` is always `/` (since the path that’s actually being requested by the client is purely an implementation detail).
Note that some properties of `RequestEvent` are different inside remote functions:

- you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions)
- `route`, `params` and `url` relate to the page the remote function was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Queries are not re-run when the user navigates (unless the argument to the query changes as a result of navigation), and so you should be mindful of how you use these values. In particular, never use them to determine whether or not a user is authorized to access certain data.

## Redirects

Expand Down
4 changes: 1 addition & 3 deletions packages/kit/src/runtime/app/server/remote/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ export async function run_remote_function(event, state, allow_cookies, arg, vali

return event.cookies.delete(name, opts);
}
},
route: { id: null },
url: new URL(event.url.origin)
}
};

// In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export function command(id) {
refreshes: updates.map((u) => u._key)
}),
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'x-sveltekit-pathname': location.pathname,
'x-sveltekit-search': location.search
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ export function form(id) {

const response = await fetch(`${base}/${app_dir}/remote/${action_id}`, {
method: 'POST',
body: data
body: data,
headers: {
'x-sveltekit-pathname': location.pathname,
'x-sveltekit-search': location.search
}
});

if (!response.ok) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ import { create_remote_cache_key, stringify_remote_arg } from '../../shared.js';
* @param {string} url
*/
export async function remote_request(url) {
const response = await fetch(url);
const response = await fetch(url, {
headers: {
// TODO in future, when we support forking, we will likely need
// to grab this from context as queries will run before
// `location.pathname` is updated
'x-sveltekit-pathname': location.pathname,
'x-sveltekit-search': location.search
}
});

if (!response.ok) {
throw new HttpError(500, 'Failed to execute remote function');
Expand Down
8 changes: 4 additions & 4 deletions packages/kit/src/runtime/server/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ export async function internal_respond(request, options, manifest, state) {
.map((node) => node === '1');
url.searchParams.delete(INVALIDATED_PARAM);
} else if (remote_id) {
url.pathname = base;
url.search = '';
url.pathname = request.headers.get('x-sveltekit-pathname') ?? base;
url.search = request.headers.get('x-sveltekit-search') ?? '';
}

/** @type {Record<string, string>} */
Expand Down Expand Up @@ -294,7 +294,7 @@ export async function internal_respond(request, options, manifest, state) {
return text('Not found', { status: 404, headers });
}

if (!state.prerendering?.fallback && !remote_id) {
if (!state.prerendering?.fallback) {
// TODO this could theoretically break — should probably be inside a try-catch
const matchers = await manifest._.matchers();

Expand Down Expand Up @@ -329,7 +329,7 @@ export async function internal_respond(request, options, manifest, state) {
: undefined;

// determine whether we need to redirect to add/remove a trailing slash
if (route) {
if (route && !remote_id) {
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
// regardless of the `trailingSlash` route option
if (url.pathname === base || url.pathname === base + '/') {
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/test/apps/basics/src/routes/remote/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,5 @@
refreshAll (remote functions only)
</button>
<button id="resolve-deferreds" onclick={() => resolve_deferreds()}>Resolve Deferreds</button>

<a href="/remote/event">/remote/event</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { get_event } from './data.remote.js';
</script>

<!-- TODO use inline await when we can -->
{#await get_event() then event}
<p data-id="route">route: {event.route.id}</p>
<p data-id="pathname">pathname: {event.url.pathname}</p>
{/await}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getRequestEvent, query } from '$app/server';

export const get_event = query(() => {
const { route, url } = getRequestEvent();

return {
route,
url
};
});
16 changes: 16 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,22 @@ test.describe('remote functions', () => {
await expect(page.locator('h1')).toHaveText('3');
});

test('queries can access the route/url of the page they were called from', async ({
page,
javaScriptEnabled,
clicknav
}) => {
// TODO remove once async SSR exists
if (!javaScriptEnabled) return;

await page.goto('/remote');

await clicknav('[href="/remote/event"]');

await expect(page.locator('[data-id="route"]')).toHaveText('route: /remote/event');
await expect(page.locator('[data-id="pathname"]')).toHaveText('pathname: /remote/event');
});

test('form works', async ({ page, javaScriptEnabled }) => {
await page.goto('/remote/form');

Expand Down
Loading
Loading