Skip to content
Draft
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/tidy-matching-queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: allow calling match from remote queries
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/app/paths/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export async function match(url) {
const store = try_get_request_store();

if (typeof url === 'string') {
const origin = store?.event.url.origin ?? 'a://a';
// `event.url` is unavailable inside queries, but the request origin is still usable
const origin = store ? new URL(store.event.request.url).origin : 'a://a';
url = new URL(url, origin);
}

Expand Down
7 changes: 7 additions & 0 deletions packages/kit/test/apps/async/src/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ export const transport = {
decode: ([message]) => new Foo(message)
}
};

/** @type {import('@sveltejs/kit/hooks').Reroute} */
export const reroute = ({ url }) => {
if (url.pathname === '/remote/match/origin') {
return `/fork/${url.host}`;
}
};
28 changes: 28 additions & 0 deletions packages/kit/test/apps/async/src/routes/remote/match/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script>
import { page } from '$app/state';
import { match_route, match_routes } from './data.remote';

const samples = [
'/remote',
'fork/relative',
'/fork/test-slug?search=value#hash',
'/remote/match/missing',
'/remote/match/origin',
`${page.url.origin}/fork/absolute`,
new URL('/fork/url', page.url.origin)
];

const batched = [
'/remote/match/origin',
'https://string.example/remote/match/origin',
new URL('https://url.example/remote/match/origin')
].map((href) => match_routes(href));
</script>

{#each samples as href, i}
<p data-id="match-{i}">{JSON.stringify(await match_route(href))}</p>
{/each}

{#each batched as result, i}
<p data-id="batch-match-{i}">{JSON.stringify(await result)}</p>
{/each}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { match } from '$app/paths';
import { query } from '$app/server';
import * as v from 'valibot';

const schema = v.union([v.string(), v.instance(URL)]);

export const match_route = query(schema, async (href) => {
return await match(href);
});

export const match_routes = query.batch(schema, async (hrefs) => {
const results = await Promise.all(hrefs.map((href) => match_route(href)));
return (_, i) => results[i];
});
11 changes: 11 additions & 0 deletions packages/kit/test/apps/async/test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ test.skip(({ javaScriptEnabled }) => javaScriptEnabled);
const root = path.resolve(fileURLToPath(import.meta.url), '..', '..');

test.describe('remote functions', () => {
test('match preserves URL origins inside nested batched queries during SSR', async ({ page }) => {
await page.goto('/remote/match');

const hosts = [new URL(page.url()).host, 'string.example', 'url.example'];
for (const [i, host] of hosts.entries()) {
await expect(page.locator(`[data-id="batch-match-${i}"]`)).toHaveText(
JSON.stringify({ id: '/fork/[index]', params: { index: host } })
);
}
});

test('production client output excludes remote diagnostic text and build dependencies', () => {
test.skip(!!process.env.DEV, 'only applicable after build');
const files = fs.globSync(`${root}/.svelte-kit/output/client/**/*.js`);
Expand Down
27 changes: 27 additions & 0 deletions packages/kit/test/apps/async/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,33 @@ test.describe('remote functions', () => {
}
});

test('match can be called from a remote query', async ({ page, app, javaScriptEnabled }) => {
const check = async () => {
const expected = [
{ id: '/remote', params: {} },
{ id: '/fork/[index]', params: { index: 'relative' } },
{ id: '/fork/[index]', params: { index: 'test-slug' } },
null,
{ id: '/fork/[index]', params: { index: new URL(page.url()).host } },
{ id: '/fork/[index]', params: { index: 'absolute' } },
{ id: '/fork/[index]', params: { index: 'url' } }
];

for (const [i, result] of expected.entries()) {
await expect(page.locator(`[data-id="match-${i}"]`)).toHaveText(JSON.stringify(result));
}
};

await page.goto('/remote/match');
await check();

if (javaScriptEnabled) {
await page.goto('/remote');
await app.goto('/remote/match');
await check();
}
});

test('queries can read prerendered data during SSR', async ({ page }) => {
await page.goto('/remote/prerender-in-query');
await expect(page.locator('[data-id="nested-prerender"]')).toHaveText('yes');
Expand Down
Loading