@sveltejs/kit 1.0.0-next.421 → 1.0.0-next.422
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/core/sync/write_types.js +18 -7
- package/src/runtime/server/endpoint.js +24 -10
- package/src/runtime/server/index.js +30 -16
- package/src/runtime/server/page/index.js +8 -11
- package/src/runtime/server/page/load_data.js +48 -2
- package/src/runtime/server/page/respond_with_error.js +1 -0
- package/src/vite/dev/index.js +1 -0
- package/src/vite/utils.js +14 -2
- package/types/index.d.ts +1 -1
- package/types/internal.d.ts +3 -0
package/package.json
CHANGED
|
@@ -139,12 +139,23 @@ function get_groups(manifest_data, routes_dir) {
|
|
|
139
139
|
return group;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
// first, sort nodes
|
|
143
|
-
const nodes = [...manifest_data.nodes].sort(
|
|
144
|
-
|
|
142
|
+
// first, sort nodes (necessary for finding the nearest layout more efficiently)...
|
|
143
|
+
const nodes = [...manifest_data.nodes].sort((n1, n2) => {
|
|
144
|
+
// Sort by path length first...
|
|
145
|
+
const path_length_diff =
|
|
145
146
|
/** @type {string} */ (n1.component ?? n1.shared ?? n1.server).split('/').length -
|
|
146
|
-
/** @type {string} */ (n2.component ?? n2.shared ?? n2.server).split('/').length
|
|
147
|
-
|
|
147
|
+
/** @type {string} */ (n2.component ?? n2.shared ?? n2.server).split('/').length;
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
path_length_diff ||
|
|
151
|
+
// ...on ties, sort named layouts first
|
|
152
|
+
(path.basename(n1.component || '').includes('-')
|
|
153
|
+
? -1
|
|
154
|
+
: path.basename(n2.component || '').includes('-')
|
|
155
|
+
? 1
|
|
156
|
+
: 0)
|
|
157
|
+
);
|
|
158
|
+
});
|
|
148
159
|
|
|
149
160
|
// ...then, populate `directories` with +page/+layout files...
|
|
150
161
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
@@ -713,12 +724,12 @@ export function find_nearest_layout(routes_dir, nodes, start_idx) {
|
|
|
713
724
|
}
|
|
714
725
|
|
|
715
726
|
let common_path = path.dirname(start_file);
|
|
716
|
-
if (match[1] === 'layout' && !name) {
|
|
727
|
+
if (match[1] === 'layout' && !match[2] && !name) {
|
|
717
728
|
// We are a default layout, so we skip the current level
|
|
718
729
|
common_path = path.dirname(common_path);
|
|
719
730
|
}
|
|
720
731
|
|
|
721
|
-
for (let i = start_idx; i >= 0; i -= 1) {
|
|
732
|
+
for (let i = start_idx - 1; i >= 0; i -= 1) {
|
|
722
733
|
const node = nodes[i];
|
|
723
734
|
const file = /** @type {string} */ (node.component || node.shared || node.server);
|
|
724
735
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HttpError, Redirect } from '../../index/private.js';
|
|
1
2
|
import { check_method_names, method_not_allowed } from './utils.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -29,16 +30,29 @@ export async function render_endpoint(event, route) {
|
|
|
29
30
|
return method_not_allowed(mod, method);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!(response instanceof Response)) {
|
|
37
|
-
return new Response(
|
|
38
|
-
`Invalid response from route ${event.url.pathname}: handler should return a Response object`,
|
|
39
|
-
{ status: 500 }
|
|
33
|
+
try {
|
|
34
|
+
const response = await handler(
|
|
35
|
+
/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
|
|
40
36
|
);
|
|
41
|
-
}
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
if (!(response instanceof Response)) {
|
|
39
|
+
return new Response(
|
|
40
|
+
`Invalid response from route ${event.url.pathname}: handler should return a Response object`,
|
|
41
|
+
{ status: 500 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof HttpError) {
|
|
48
|
+
return new Response(error.message, { status: error.status });
|
|
49
|
+
} else if (error instanceof Redirect) {
|
|
50
|
+
return new Response(undefined, {
|
|
51
|
+
status: error.status,
|
|
52
|
+
headers: { Location: error.location }
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
44
58
|
}
|
|
@@ -118,6 +118,9 @@ export async function respond(request, options, state) {
|
|
|
118
118
|
/** @type {import('types').ResponseHeaders} */
|
|
119
119
|
const headers = {};
|
|
120
120
|
|
|
121
|
+
/** @type {string[]} */
|
|
122
|
+
const cookies = [];
|
|
123
|
+
|
|
121
124
|
/** @type {import('types').RequestEvent} */
|
|
122
125
|
const event = {
|
|
123
126
|
get clientAddress() {
|
|
@@ -141,16 +144,26 @@ export async function respond(request, options, state) {
|
|
|
141
144
|
setHeaders: (new_headers) => {
|
|
142
145
|
for (const key in new_headers) {
|
|
143
146
|
const lower = key.toLowerCase();
|
|
147
|
+
const value = new_headers[key];
|
|
144
148
|
|
|
145
|
-
if (lower
|
|
146
|
-
|
|
147
|
-
}
|
|
149
|
+
if (lower === 'set-cookie') {
|
|
150
|
+
const new_cookies = /** @type {string[]} */ (Array.isArray(value) ? value : [value]);
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
for (const cookie of new_cookies) {
|
|
153
|
+
if (cookies.includes(cookie)) {
|
|
154
|
+
throw new Error(`"${key}" header already has cookie with same value`);
|
|
155
|
+
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
cookies.push(cookie);
|
|
158
|
+
}
|
|
159
|
+
} else if (lower in headers) {
|
|
160
|
+
throw new Error(`"${key}" header is already set`);
|
|
161
|
+
} else {
|
|
162
|
+
headers[lower] = value;
|
|
163
|
+
|
|
164
|
+
if (state.prerendering && lower === 'cache-control') {
|
|
165
|
+
state.prerendering.cache = /** @type {string} */ (value);
|
|
166
|
+
}
|
|
154
167
|
}
|
|
155
168
|
}
|
|
156
169
|
},
|
|
@@ -254,6 +267,7 @@ export async function respond(request, options, state) {
|
|
|
254
267
|
return {
|
|
255
268
|
// TODO return `uses`, so we can reuse server data effectively
|
|
256
269
|
data: await load_server_data({
|
|
270
|
+
dev: options.dev,
|
|
257
271
|
event,
|
|
258
272
|
node,
|
|
259
273
|
parent: async () => {
|
|
@@ -312,19 +326,19 @@ export async function respond(request, options, state) {
|
|
|
312
326
|
: await render_page(event, route, options, state, resolve_opts);
|
|
313
327
|
}
|
|
314
328
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
} else if (!is_data_request) {
|
|
322
|
-
// we only want to set cookies on __data.json requests, we don't
|
|
323
|
-
// want to cache stuff erroneously etc
|
|
329
|
+
if (!is_data_request) {
|
|
330
|
+
// we only want to set cookies on __data.json requests, we don't
|
|
331
|
+
// want to cache stuff erroneously etc
|
|
332
|
+
for (const key in headers) {
|
|
333
|
+
const value = headers[key];
|
|
324
334
|
response.headers.set(key, /** @type {string} */ (value));
|
|
325
335
|
}
|
|
326
336
|
}
|
|
327
337
|
|
|
338
|
+
for (const cookie of cookies) {
|
|
339
|
+
response.headers.append('set-cookie', cookie);
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
// respond with 304 if etag matches
|
|
329
343
|
if (response.status === 200 && response.headers.has('etag')) {
|
|
330
344
|
let if_none_match_value = request.headers.get('if-none-match');
|
|
@@ -36,7 +36,11 @@ export async function render_page(event, route, options, state, resolve_opts) {
|
|
|
36
36
|
'application/json'
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
accept === 'application/json' &&
|
|
41
|
+
event.request.method !== 'GET' &&
|
|
42
|
+
event.request.method !== 'HEAD'
|
|
43
|
+
) {
|
|
40
44
|
const node = await options.manifest._.nodes[route.leaf]();
|
|
41
45
|
if (node.server) {
|
|
42
46
|
return handle_json_request(event, options, node.server);
|
|
@@ -157,6 +161,7 @@ export async function render_page(event, route, options, state, resolve_opts) {
|
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
return await load_server_data({
|
|
164
|
+
dev: options.dev,
|
|
160
165
|
event,
|
|
161
166
|
node,
|
|
162
167
|
parent: async () => {
|
|
@@ -346,8 +351,8 @@ function get_page_config(leaf, options) {
|
|
|
346
351
|
* @param {import('types').SSRNode['server']} mod
|
|
347
352
|
*/
|
|
348
353
|
export async function handle_json_request(event, options, mod) {
|
|
349
|
-
const method = /** @type {
|
|
350
|
-
const handler = mod[method
|
|
354
|
+
const method = /** @type {'POST' | 'PUT' | 'PATCH' | 'DELETE'} */ (event.request.method);
|
|
355
|
+
const handler = mod[method];
|
|
351
356
|
|
|
352
357
|
if (!handler) {
|
|
353
358
|
return method_not_allowed(mod, method);
|
|
@@ -357,14 +362,6 @@ export async function handle_json_request(event, options, mod) {
|
|
|
357
362
|
// @ts-ignore
|
|
358
363
|
const result = await handler.call(null, event);
|
|
359
364
|
|
|
360
|
-
if (method === 'HEAD') {
|
|
361
|
-
return new Response();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (method === 'GET') {
|
|
365
|
-
return json(result);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
365
|
if (result?.errors) {
|
|
369
366
|
// @ts-ignore
|
|
370
367
|
return json({ errors: result.errors }, { status: result.status || 400 });
|
|
@@ -3,12 +3,13 @@ import { LoadURL, PrerenderingURL } from '../../../utils/url.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Calls the user's `load` function.
|
|
5
5
|
* @param {{
|
|
6
|
+
* dev: boolean;
|
|
6
7
|
* event: import('types').RequestEvent;
|
|
7
8
|
* node: import('types').SSRNode | undefined;
|
|
8
9
|
* parent: () => Promise<Record<string, any>>;
|
|
9
10
|
* }} opts
|
|
10
11
|
*/
|
|
11
|
-
export async function load_server_data({ event, node, parent }) {
|
|
12
|
+
export async function load_server_data({ dev, event, node, parent }) {
|
|
12
13
|
if (!node?.server) return null;
|
|
13
14
|
|
|
14
15
|
const server_data = await node.server.load?.call(null, {
|
|
@@ -27,7 +28,13 @@ export async function load_server_data({ event, node, parent }) {
|
|
|
27
28
|
url: event.url
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
const result = server_data ? await unwrap_promises(server_data) : null;
|
|
32
|
+
|
|
33
|
+
if (dev) {
|
|
34
|
+
check_serializability(result, /** @type {string} */ (node.server_id), 'data');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
|
@@ -79,3 +86,42 @@ async function unwrap_promises(object) {
|
|
|
79
86
|
|
|
80
87
|
return unwrapped;
|
|
81
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check that the data can safely be serialized to JSON
|
|
92
|
+
* @param {any} value
|
|
93
|
+
* @param {string} id
|
|
94
|
+
* @param {string} path
|
|
95
|
+
*/
|
|
96
|
+
function check_serializability(value, id, path) {
|
|
97
|
+
const type = typeof value;
|
|
98
|
+
|
|
99
|
+
if (type === 'string' || type === 'boolean' || type === 'number' || type === 'undefined') {
|
|
100
|
+
// primitives are fine
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (type === 'object') {
|
|
105
|
+
// nulls are fine...
|
|
106
|
+
if (!value) return;
|
|
107
|
+
|
|
108
|
+
// ...so are plain arrays...
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
value.forEach((child, i) => {
|
|
111
|
+
check_serializability(child, id, `${path}[${i}]`);
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ...and objects
|
|
117
|
+
const tag = Object.prototype.toString.call(value);
|
|
118
|
+
if (tag === '[object Object]') {
|
|
119
|
+
for (const key in value) {
|
|
120
|
+
check_serializability(value[key], id, `${path}.${key}`);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error(`${path} returned from 'load' in ${id} cannot be serialized as JSON`);
|
|
127
|
+
}
|
|
@@ -35,6 +35,7 @@ export async function respond_with_error({ event, options, state, status, error,
|
|
|
35
35
|
const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
|
|
36
36
|
|
|
37
37
|
const server_data_promise = load_server_data({
|
|
38
|
+
dev: options.dev,
|
|
38
39
|
event,
|
|
39
40
|
node: default_layout,
|
|
40
41
|
parent: async () => ({})
|
package/src/vite/dev/index.js
CHANGED
|
@@ -119,6 +119,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
119
119
|
if (node.server) {
|
|
120
120
|
const { module } = await resolve(node.server);
|
|
121
121
|
result.server = module;
|
|
122
|
+
result.server_id = node.server;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
// in dev we inline all styles to avoid FOUC. this gets populated lazily so that
|
package/src/vite/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { loadConfigFromFile, loadEnv, normalizePath } from 'vite';
|
|
4
4
|
import { runtime_directory } from '../core/utils.js';
|
|
5
|
+
import { posixify } from '../utils/filesystem.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @param {import('vite').ResolvedConfig} config
|
|
@@ -113,18 +114,22 @@ export function get_aliases(config) {
|
|
|
113
114
|
];
|
|
114
115
|
|
|
115
116
|
for (let [key, value] of Object.entries(config.alias)) {
|
|
117
|
+
value = posixify(value);
|
|
116
118
|
if (value.endsWith('/*')) {
|
|
117
119
|
value = value.slice(0, -2);
|
|
118
120
|
}
|
|
119
121
|
if (key.endsWith('/*')) {
|
|
120
122
|
// Doing just `{ find: key.slice(0, -2) ,..}` would mean `import .. from "key"` would also be matched, which we don't want
|
|
121
123
|
alias.push({
|
|
122
|
-
find: new RegExp(`^${key.slice(0, -2)}\\/(.+)$`),
|
|
124
|
+
find: new RegExp(`^${escape_for_regexp(key.slice(0, -2))}\\/(.+)$`),
|
|
123
125
|
replacement: `${path.resolve(value)}/$1`
|
|
124
126
|
});
|
|
125
127
|
} else if (key + '/*' in config.alias) {
|
|
126
128
|
// key and key/* both exist -> the replacement for key needs to happen _only_ on import .. from "key"
|
|
127
|
-
alias.push({
|
|
129
|
+
alias.push({
|
|
130
|
+
find: new RegExp(`^${escape_for_regexp(key)}$`),
|
|
131
|
+
replacement: path.resolve(value)
|
|
132
|
+
});
|
|
128
133
|
} else {
|
|
129
134
|
alias.push({ find: key, replacement: path.resolve(value) });
|
|
130
135
|
}
|
|
@@ -133,6 +138,13 @@ export function get_aliases(config) {
|
|
|
133
138
|
return alias;
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} str
|
|
143
|
+
*/
|
|
144
|
+
function escape_for_regexp(str) {
|
|
145
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
|
|
146
|
+
}
|
|
147
|
+
|
|
136
148
|
/**
|
|
137
149
|
* Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js
|
|
138
150
|
* @param {string} entry
|
package/types/index.d.ts
CHANGED
|
@@ -312,7 +312,7 @@ export interface Action<
|
|
|
312
312
|
Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
|
|
313
313
|
> {
|
|
314
314
|
(event: RequestEvent<Params>): MaybePromise<
|
|
315
|
-
| { status?: number; errors: Record<string,
|
|
315
|
+
| { status?: number; errors: Record<string, any>; location?: never }
|
|
316
316
|
| { status?: never; errors?: never; location: string }
|
|
317
317
|
| void
|
|
318
318
|
>;
|
package/types/internal.d.ts
CHANGED