-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
index.js
162 lines (142 loc) · 4.07 KB
/
index.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
import { HttpError, Redirect } from '../../control.js';
import { normalize_error } from '../../../utils/error.js';
import { once } from '../../../utils/functions.js';
import { load_server_data } from '../page/load_data.js';
import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
import { normalize_path } from '../../../utils/url.js';
import { text } from '../../../exports/index.js';
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
/**
* @param {import('types').RequestEvent} event
* @param {import('types').SSRRoute} route
* @param {import('types').SSROptions} options
* @param {import('types').SSRManifest} manifest
* @param {import('types').SSRState} state
* @param {boolean[] | undefined} invalidated_data_nodes
* @param {import('types').TrailingSlash} trailing_slash
* @returns {Promise<Response>}
*/
export async function render_data(
event,
route,
options,
manifest,
state,
invalidated_data_nodes,
trailing_slash
) {
if (!route.page) {
// requesting /__data.json should fail for a +server.js
return new Response(undefined, {
status: 404
});
}
state.initiator = route;
try {
const node_ids = [...route.page.layouts, route.page.leaf];
const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
let aborted = false;
const url = new URL(event.url);
url.pathname = normalize_path(url.pathname, trailing_slash);
const new_event = { ...event, url };
const functions = node_ids.map((n, i) => {
return once(async () => {
try {
if (aborted) {
return /** @type {import('types').ServerDataSkippedNode} */ ({
type: 'skip'
});
}
// == because it could be undefined (in dev) or null (in build, because of JSON.stringify)
const node = n == undefined ? n : await manifest._.nodes[n]();
return load_server_data({
event: new_event,
state,
node,
parent: async () => {
/** @type {Record<string, any>} */
const data = {};
for (let j = 0; j < i; j += 1) {
const parent = /** @type {import('types').ServerDataNode | null} */ (
await functions[j]()
);
if (parent) {
Object.assign(data, parent.data);
}
}
return data;
}
});
} catch (e) {
aborted = true;
throw e;
}
});
});
const promises = functions.map(async (fn, i) => {
if (!invalidated[i]) {
return /** @type {import('types').ServerDataSkippedNode} */ ({
type: 'skip'
});
}
return fn();
});
let length = promises.length;
const nodes = await Promise.all(
promises.map((p, i) =>
p.catch(async (error) => {
if (error instanceof Redirect) {
throw error;
}
// Math.min because array isn't guaranteed to resolve in order
length = Math.min(length, i + 1);
return /** @type {import('types').ServerErrorNode} */ ({
type: 'error',
error: await handle_error_and_jsonify(event, options, error),
status: error instanceof HttpError ? error.status : undefined
});
})
)
);
try {
const stubs = nodes.slice(0, length).map(serialize_data_node);
const json = `{"type":"data","nodes":[${stubs.join(',')}]}`;
return json_response(json);
} catch (e) {
const error = /** @type {any} */ (e);
return json_response(JSON.stringify(clarify_devalue_error(event, error)), 500);
}
} catch (e) {
const error = normalize_error(e);
if (error instanceof Redirect) {
return redirect_json_response(error);
} else {
// TODO make it clearer that this was an unexpected error
return json_response(JSON.stringify(await handle_error_and_jsonify(event, options, error)));
}
}
}
/**
* @param {string} json
* @param {number} [status]
*/
function json_response(json, status = 200) {
return text(json, {
status,
headers: {
'content-type': 'application/json',
'cache-control': 'private, no-store'
}
});
}
/**
* @param {Redirect} redirect
*/
export function redirect_json_response(redirect) {
return json_response(
JSON.stringify({
type: 'redirect',
location: redirect.location
})
);
}