-
-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathutils.js
182 lines (153 loc) · 4.92 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { DEV } from 'esm-env';
import { json, text } from '../../exports/index.js';
import { coalesce_to_error, get_message, get_status } from '../../utils/error.js';
import { negotiate } from '../../utils/http.js';
import { HttpError } from '../control.js';
import { fix_stack_trace } from '../shared-server.js';
import { ENDPOINT_METHODS } from '../../constants.js';
import { escape_html } from '../../utils/escape.js';
import { with_event } from '../app/server/event.js';
/** @param {any} body */
export function is_pojo(body) {
if (typeof body !== 'object') return false;
if (body) {
if (body instanceof Uint8Array) return false;
if (body instanceof ReadableStream) return false;
}
return true;
}
/**
* @param {Partial<Record<import('types').HttpMethod, any>>} mod
* @param {import('types').HttpMethod} method
*/
export function method_not_allowed(mod, method) {
return text(`${method} method not allowed`, {
status: 405,
headers: {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: allowed_methods(mod).join(', ')
}
});
}
/** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
export function allowed_methods(mod) {
const allowed = ENDPOINT_METHODS.filter((method) => method in mod);
if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
return allowed;
}
/**
* Return as a response that renders the error.html
*
* @param {import('types').SSROptions} options
* @param {number} status
* @param {string} message
*/
export function static_error_page(options, status, message) {
let page = options.templates.error({ status, message: escape_html(message) });
if (DEV) {
// inject Vite HMR client, for easier debugging
page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
}
return text(page, {
headers: { 'content-type': 'text/html; charset=utf-8' },
status
});
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {unknown} error
*/
export async function handle_fatal_error(event, options, error) {
error = error instanceof HttpError ? error : coalesce_to_error(error);
const status = get_status(error);
const body = await handle_error_and_jsonify(event, options, error);
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
'application/json',
'text/html'
]);
if (event.isDataRequest || type === 'application/json') {
return json(body, {
status
});
}
return static_error_page(options, status, body.message);
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {any} error
* @returns {Promise<App.Error>}
*/
export async function handle_error_and_jsonify(event, options, error) {
if (error instanceof HttpError) {
return error.body;
}
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
fix_stack_trace(error);
}
const status = get_status(error);
const message = get_message(error);
return (
(await with_event(event, () =>
options.hooks.handleError({ error, event, status, message })
)) ?? { message }
);
}
/**
* @param {number} status
* @param {string} location
*/
export function redirect_response(status, location) {
const response = new Response(undefined, {
status,
headers: { location }
});
return response;
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {Error & { path: string }} error
*/
export function clarify_devalue_error(event, error) {
if (error.path) {
return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (${error.path})`;
}
if (error.path === '') {
return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
}
// belt and braces — this should never happen
return error.message;
}
/**
* @param {import('types').ServerDataNode} node
*/
export function serialize_uses(node) {
const uses = {};
if (node.uses && node.uses.dependencies.size > 0) {
uses.dependencies = Array.from(node.uses.dependencies);
}
if (node.uses && node.uses.search_params.size > 0) {
uses.search_params = Array.from(node.uses.search_params);
}
if (node.uses && node.uses.params.size > 0) {
uses.params = Array.from(node.uses.params);
}
if (node.uses?.parent) uses.parent = 1;
if (node.uses?.route) uses.route = 1;
if (node.uses?.url) uses.url = 1;
return uses;
}
/**
* Returns `true` if the given path was prerendered
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {string} pathname Should include the base and be decoded
*/
export function has_prerendered_path(manifest, pathname) {
return (
manifest._.prerendered_routes.has(pathname) ||
(pathname.at(-1) === '/' && manifest._.prerendered_routes.has(pathname.slice(0, -1)))
);
}