@sveltejs/kit 1.0.0-next.536 → 1.0.0-next.538
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/runtime/client/client.js +15 -7
- package/src/runtime/server/cookie.js +38 -16
- package/src/runtime/server/data/index.js +3 -1
- package/src/runtime/server/index.js +16 -8
- package/src/runtime/server/page/load_data.js +25 -2
- package/src/utils/url.js +10 -13
- package/types/ambient.d.ts +3 -5
- package/types/index.d.ts +26 -2
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { onMount, tick } from 'svelte';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
make_trackable,
|
|
4
|
+
decode_pathname,
|
|
5
|
+
decode_params,
|
|
6
|
+
normalize_path,
|
|
7
|
+
add_data_suffix
|
|
8
|
+
} from '../../utils/url.js';
|
|
3
9
|
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
|
|
4
10
|
import {
|
|
5
11
|
lock_fetch,
|
|
@@ -83,10 +89,10 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
83
89
|
let load_cache = null;
|
|
84
90
|
|
|
85
91
|
const callbacks = {
|
|
86
|
-
/** @type {Array<(navigation: import('types').
|
|
92
|
+
/** @type {Array<(navigation: import('types').BeforeNavigate) => void>} */
|
|
87
93
|
before_navigate: [],
|
|
88
94
|
|
|
89
|
-
/** @type {Array<(navigation: import('types').
|
|
95
|
+
/** @type {Array<(navigation: import('types').AfterNavigate) => void>} */
|
|
90
96
|
after_navigate: []
|
|
91
97
|
};
|
|
92
98
|
|
|
@@ -382,7 +388,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
382
388
|
});
|
|
383
389
|
post_update();
|
|
384
390
|
|
|
385
|
-
/** @type {import('types').
|
|
391
|
+
/** @type {import('types').AfterNavigate} */
|
|
386
392
|
const navigation = {
|
|
387
393
|
from: null,
|
|
388
394
|
to: add_url_properties('to', {
|
|
@@ -992,7 +998,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
992
998
|
function get_navigation_intent(url, invalidating) {
|
|
993
999
|
if (is_external_url(url)) return;
|
|
994
1000
|
|
|
995
|
-
const path =
|
|
1001
|
+
const path = decode_pathname(url.pathname.slice(base.length) || '/');
|
|
996
1002
|
|
|
997
1003
|
for (const route of routes) {
|
|
998
1004
|
const params = route.exec(path);
|
|
@@ -1116,7 +1122,9 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1116
1122
|
nav_token,
|
|
1117
1123
|
() => {
|
|
1118
1124
|
navigating = false;
|
|
1119
|
-
callbacks.after_navigate.forEach((fn) =>
|
|
1125
|
+
callbacks.after_navigate.forEach((fn) =>
|
|
1126
|
+
fn(/** @type {import('types').AfterNavigate} */ (navigation))
|
|
1127
|
+
);
|
|
1120
1128
|
stores.navigating.set(null);
|
|
1121
1129
|
}
|
|
1122
1130
|
);
|
|
@@ -1307,7 +1315,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1307
1315
|
if (!navigating) {
|
|
1308
1316
|
// If we're navigating, beforeNavigate was already called. If we end up in here during navigation,
|
|
1309
1317
|
// it's due to an external or full-page-reload link, for which we don't want to call the hook again.
|
|
1310
|
-
/** @type {import('types').
|
|
1318
|
+
/** @type {import('types').BeforeNavigate} */
|
|
1311
1319
|
const navigation = {
|
|
1312
1320
|
from: add_url_properties('from', {
|
|
1313
1321
|
params: current.params,
|
|
@@ -15,9 +15,35 @@ const cookie_paths = {};
|
|
|
15
15
|
*/
|
|
16
16
|
export function get_cookies(request, url, options) {
|
|
17
17
|
const header = request.headers.get('cookie') ?? '';
|
|
18
|
-
|
|
19
18
|
const initial_cookies = parse(header);
|
|
20
19
|
|
|
20
|
+
const normalized_url = normalize_path(
|
|
21
|
+
// Remove suffix: 'foo/__data.json' would mean the cookie path is '/foo',
|
|
22
|
+
// whereas a direct hit of /foo would mean the cookie path is '/'
|
|
23
|
+
has_data_suffix(url.pathname) ? strip_data_suffix(url.pathname) : url.pathname,
|
|
24
|
+
options.trailing_slash
|
|
25
|
+
);
|
|
26
|
+
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
|
27
|
+
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
|
|
28
|
+
|
|
29
|
+
if (options.dev) {
|
|
30
|
+
// Remove all cookies that no longer exist according to the request
|
|
31
|
+
for (const name of Object.keys(cookie_paths)) {
|
|
32
|
+
cookie_paths[name] = new Set(
|
|
33
|
+
[...cookie_paths[name]].filter(
|
|
34
|
+
(path) => !path_matches(normalized_url, path) || name in initial_cookies
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
// Add all new cookies we might not have seen before
|
|
39
|
+
for (const name in initial_cookies) {
|
|
40
|
+
cookie_paths[name] = cookie_paths[name] ?? new Set();
|
|
41
|
+
if (![...cookie_paths[name]].some((path) => path_matches(normalized_url, path))) {
|
|
42
|
+
cookie_paths[name].add(default_path);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
/** @type {Record<string, import('./page/types').Cookie>} */
|
|
22
48
|
const new_cookies = {};
|
|
23
49
|
|
|
@@ -57,9 +83,14 @@ export function get_cookies(request, url, options) {
|
|
|
57
83
|
return cookie;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
const paths = new Set([...(cookie_paths[name] ?? [])]);
|
|
87
|
+
if (c) {
|
|
88
|
+
paths.add(c.options.path ?? default_path);
|
|
89
|
+
}
|
|
90
|
+
if (paths.size > 0) {
|
|
61
91
|
console.warn(
|
|
62
|
-
|
|
92
|
+
// prettier-ignore
|
|
93
|
+
`Cookie with name '${name}' was not found at path '${url.pathname}', but a cookie with that name exists at these paths: '${[...paths].join("', '")}'. Did you mean to set its 'path' to '/' instead?`
|
|
63
94
|
);
|
|
64
95
|
}
|
|
65
96
|
},
|
|
@@ -70,17 +101,7 @@ export function get_cookies(request, url, options) {
|
|
|
70
101
|
* @param {import('cookie').CookieSerializeOptions} opts
|
|
71
102
|
*/
|
|
72
103
|
set(name, value, opts = {}) {
|
|
73
|
-
let path = opts.path;
|
|
74
|
-
if (!path) {
|
|
75
|
-
const normalized = normalize_path(
|
|
76
|
-
// Remove suffix: 'foo/__data.json' would mean the cookie path is '/foo',
|
|
77
|
-
// whereas a direct hit of /foo would mean the cookie path is '/'
|
|
78
|
-
has_data_suffix(url.pathname) ? strip_data_suffix(url.pathname) : url.pathname,
|
|
79
|
-
options.trailing_slash
|
|
80
|
-
);
|
|
81
|
-
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
|
82
|
-
path = normalized.split('/').slice(0, -1).join('/') || '/';
|
|
83
|
-
}
|
|
104
|
+
let path = opts.path ?? default_path;
|
|
84
105
|
|
|
85
106
|
new_cookies[name] = {
|
|
86
107
|
name,
|
|
@@ -93,11 +114,12 @@ export function get_cookies(request, url, options) {
|
|
|
93
114
|
};
|
|
94
115
|
|
|
95
116
|
if (options.dev) {
|
|
96
|
-
cookie_paths[name] = cookie_paths[name]
|
|
117
|
+
cookie_paths[name] = cookie_paths[name] ?? new Set();
|
|
97
118
|
if (!value) {
|
|
98
119
|
if (!cookie_paths[name].has(path) && cookie_paths[name].size > 0) {
|
|
120
|
+
const paths = `'${Array.from(cookie_paths[name]).join("', '")}'`;
|
|
99
121
|
console.warn(
|
|
100
|
-
`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at
|
|
122
|
+
`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at these paths: ${paths}.`
|
|
101
123
|
);
|
|
102
124
|
}
|
|
103
125
|
cookie_paths[name].delete(path);
|
|
@@ -5,6 +5,8 @@ import { load_server_data } from '../page/load_data.js';
|
|
|
5
5
|
import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
|
|
6
6
|
import { normalize_path, strip_data_suffix } from '../../../utils/url.js';
|
|
7
7
|
|
|
8
|
+
export const INVALIDATED_HEADER = 'x-sveltekit-invalidated';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @param {import('types').RequestEvent} event
|
|
10
12
|
* @param {import('types').SSRRoute} route
|
|
@@ -24,7 +26,7 @@ export async function render_data(event, route, options, state) {
|
|
|
24
26
|
const node_ids = [...route.page.layouts, route.page.leaf];
|
|
25
27
|
|
|
26
28
|
const invalidated =
|
|
27
|
-
event.request.headers.get(
|
|
29
|
+
event.request.headers.get(INVALIDATED_HEADER)?.split(',').map(Boolean) ??
|
|
28
30
|
node_ids.map(() => true);
|
|
29
31
|
|
|
30
32
|
let aborted = false;
|
|
@@ -6,6 +6,7 @@ import { coalesce_to_error } from '../../utils/error.js';
|
|
|
6
6
|
import { is_form_content_type } from '../../utils/http.js';
|
|
7
7
|
import { GENERIC_ERROR, handle_fatal_error } from './utils.js';
|
|
8
8
|
import {
|
|
9
|
+
decode_pathname,
|
|
9
10
|
decode_params,
|
|
10
11
|
disable_search,
|
|
11
12
|
has_data_suffix,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
strip_data_suffix
|
|
14
15
|
} from '../../utils/url.js';
|
|
15
16
|
import { exec } from '../../utils/routing.js';
|
|
16
|
-
import { render_data } from './data/index.js';
|
|
17
|
+
import { INVALIDATED_HEADER, render_data } from './data/index.js';
|
|
17
18
|
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
|
18
19
|
import { HttpError } from '../control.js';
|
|
19
20
|
import { create_fetch } from './fetch.js';
|
|
@@ -44,7 +45,7 @@ export async function respond(request, options, state) {
|
|
|
44
45
|
|
|
45
46
|
let decoded;
|
|
46
47
|
try {
|
|
47
|
-
decoded =
|
|
48
|
+
decoded = decode_pathname(url.pathname);
|
|
48
49
|
} catch {
|
|
49
50
|
return new Response('Malformed URI', { status: 400 });
|
|
50
51
|
}
|
|
@@ -296,14 +297,21 @@ export async function respond(request, options, state) {
|
|
|
296
297
|
resolve(event, opts).then((response) => {
|
|
297
298
|
// add headers/cookies here, rather than inside `resolve`, so that we
|
|
298
299
|
// can do it once for all responses instead of once per `return`
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
for (const key in headers) {
|
|
301
|
+
const value = headers[key];
|
|
302
|
+
response.headers.set(key, /** @type {string} */ (value));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (is_data_request) {
|
|
306
|
+
// set the Vary header on __data.json requests to ensure we don't cache
|
|
307
|
+
// incomplete responses with skipped data loads
|
|
308
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
|
|
309
|
+
const vary = response.headers.get('Vary');
|
|
310
|
+
if (vary !== '*') {
|
|
311
|
+
response.headers.append('Vary', INVALIDATED_HEADER);
|
|
305
312
|
}
|
|
306
313
|
}
|
|
314
|
+
|
|
307
315
|
add_cookies_to_headers(response.headers, Object.values(new_cookies));
|
|
308
316
|
|
|
309
317
|
if (state.prerendering && event.route.id !== null) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { disable_search, make_trackable } from '../../../utils/url.js';
|
|
2
2
|
import { unwrap_promises } from '../../../utils/promises.js';
|
|
3
|
+
|
|
3
4
|
/**
|
|
4
|
-
* Calls the user's `load` function.
|
|
5
|
+
* Calls the user's server `load` function.
|
|
5
6
|
* @param {{
|
|
6
7
|
* event: import('types').RequestEvent;
|
|
7
8
|
* state: import('types').SSRState;
|
|
@@ -103,6 +104,7 @@ export async function load_data({
|
|
|
103
104
|
data: server_data_node?.data ?? null,
|
|
104
105
|
route: event.route,
|
|
105
106
|
fetch: async (input, init) => {
|
|
107
|
+
const cloned_body = input instanceof Request && input.body ? input.clone().body : null;
|
|
106
108
|
const response = await event.fetch(input, init);
|
|
107
109
|
|
|
108
110
|
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
|
@@ -149,7 +151,11 @@ export async function load_data({
|
|
|
149
151
|
fetched.push({
|
|
150
152
|
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
|
|
151
153
|
method: event.request.method,
|
|
152
|
-
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
|
154
|
+
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
|
155
|
+
input instanceof Request && cloned_body
|
|
156
|
+
? await stream_to_string(cloned_body)
|
|
157
|
+
: init?.body
|
|
158
|
+
),
|
|
153
159
|
response_body: body,
|
|
154
160
|
response: response
|
|
155
161
|
});
|
|
@@ -233,3 +239,20 @@ export async function load_data({
|
|
|
233
239
|
|
|
234
240
|
return data ? unwrap_promises(data) : null;
|
|
235
241
|
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {ReadableStream<Uint8Array>} stream
|
|
245
|
+
*/
|
|
246
|
+
async function stream_to_string(stream) {
|
|
247
|
+
let result = '';
|
|
248
|
+
const reader = stream.getReader();
|
|
249
|
+
const decoder = new TextDecoder();
|
|
250
|
+
while (true) {
|
|
251
|
+
const { done, value } = await reader.read();
|
|
252
|
+
if (done) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
result += decoder.decode(value);
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
package/src/utils/url.js
CHANGED
|
@@ -54,23 +54,20 @@ export function normalize_path(path, trailing_slash) {
|
|
|
54
54
|
return path;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Decode pathname excluding %25 to prevent further double decoding of params
|
|
59
|
+
* @param {string} pathname
|
|
60
|
+
*/
|
|
61
|
+
export function decode_pathname(pathname) {
|
|
62
|
+
return pathname.split('%25').map(decodeURI).join('%25');
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
/** @param {Record<string, string>} params */
|
|
58
66
|
export function decode_params(params) {
|
|
59
67
|
for (const key in params) {
|
|
60
68
|
// input has already been decoded by decodeURI
|
|
61
|
-
// now handle the rest
|
|
62
|
-
params[key] = params[key]
|
|
63
|
-
.replace(/%23/g, '#')
|
|
64
|
-
.replace(/%3[Bb]/g, ';')
|
|
65
|
-
.replace(/%2[Cc]/g, ',')
|
|
66
|
-
.replace(/%2[Ff]/g, '/')
|
|
67
|
-
.replace(/%3[Ff]/g, '?')
|
|
68
|
-
.replace(/%3[Aa]/g, ':')
|
|
69
|
-
.replace(/%40/g, '@')
|
|
70
|
-
.replace(/%26/g, '&')
|
|
71
|
-
.replace(/%3[Dd]/g, '=')
|
|
72
|
-
.replace(/%2[Bb]/g, '+')
|
|
73
|
-
.replace(/%24/g, '$');
|
|
69
|
+
// now handle the rest
|
|
70
|
+
params[key] = decodeURIComponent(params[key]);
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
return params;
|
package/types/ambient.d.ts
CHANGED
|
@@ -171,7 +171,7 @@ declare module '$app/forms' {
|
|
|
171
171
|
* ```
|
|
172
172
|
*/
|
|
173
173
|
declare module '$app/navigation' {
|
|
174
|
-
import {
|
|
174
|
+
import { BeforeNavigate, AfterNavigate } from '@sveltejs/kit';
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
177
|
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
|
|
@@ -261,16 +261,14 @@ declare module '$app/navigation' {
|
|
|
261
261
|
*
|
|
262
262
|
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
263
263
|
*/
|
|
264
|
-
export function beforeNavigate(
|
|
265
|
-
callback: (navigation: Navigation & { cancel(): void }) => void
|
|
266
|
-
): void;
|
|
264
|
+
export function beforeNavigate(callback: (navigation: BeforeNavigate) => void): void;
|
|
267
265
|
|
|
268
266
|
/**
|
|
269
267
|
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
|
|
270
268
|
*
|
|
271
269
|
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
272
270
|
*/
|
|
273
|
-
export function afterNavigate(callback: (navigation:
|
|
271
|
+
export function afterNavigate(callback: (navigation: AfterNavigate) => void): void;
|
|
274
272
|
}
|
|
275
273
|
|
|
276
274
|
/**
|
package/types/index.d.ts
CHANGED
|
@@ -414,13 +414,12 @@ export interface Navigation {
|
|
|
414
414
|
to: NavigationTarget | null;
|
|
415
415
|
/**
|
|
416
416
|
* The type of navigation:
|
|
417
|
-
* - `enter`: The app has hydrated
|
|
418
417
|
* - `leave`: The user is leaving the app by closing the tab or using the back/forward buttons to go to a different document
|
|
419
418
|
* - `link`: Navigation was triggered by a link click
|
|
420
419
|
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
|
|
421
420
|
* - `popstate`: Navigation was triggered by back/forward navigation
|
|
422
421
|
*/
|
|
423
|
-
type: NavigationType
|
|
422
|
+
type: Omit<NavigationType, 'enter'>;
|
|
424
423
|
/**
|
|
425
424
|
* Whether or not the navigation will result in the page being unloaded (i.e. not a client-side navigation)
|
|
426
425
|
*/
|
|
@@ -431,6 +430,31 @@ export interface Navigation {
|
|
|
431
430
|
delta?: number;
|
|
432
431
|
}
|
|
433
432
|
|
|
433
|
+
/**
|
|
434
|
+
* The interface that corresponds to the `beforeNavigate`'s input parameter.
|
|
435
|
+
*/
|
|
436
|
+
export interface BeforeNavigate extends Navigation {
|
|
437
|
+
/**
|
|
438
|
+
* Call this to prevent the navigation from starting.
|
|
439
|
+
*/
|
|
440
|
+
cancel(): void;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The interface that corresponds to the `afterNavigate`'s input parameter.
|
|
445
|
+
*/
|
|
446
|
+
export interface AfterNavigate extends Navigation {
|
|
447
|
+
/**
|
|
448
|
+
* The type of navigation:
|
|
449
|
+
* - `enter`: The app has hydrated
|
|
450
|
+
* - `link`: Navigation was triggered by a link click
|
|
451
|
+
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
|
|
452
|
+
* - `popstate`: Navigation was triggered by back/forward navigation
|
|
453
|
+
*/
|
|
454
|
+
type: Omit<NavigationType, 'leave'>;
|
|
455
|
+
willUnload: false;
|
|
456
|
+
}
|
|
457
|
+
|
|
434
458
|
/**
|
|
435
459
|
* The shape of the `$page` store
|
|
436
460
|
*/
|