@sveltejs/kit 1.0.0-next.22 → 1.0.0-next.220

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.
Files changed (82) hide show
  1. package/README.md +12 -9
  2. package/assets/components/error.svelte +19 -3
  3. package/assets/kit.js +1935 -0
  4. package/assets/runtime/app/env.js +20 -0
  5. package/assets/runtime/app/navigation.js +55 -13
  6. package/assets/runtime/app/paths.js +1 -2
  7. package/assets/runtime/app/stores.js +16 -10
  8. package/assets/runtime/chunks/utils.js +13 -0
  9. package/assets/runtime/env.js +8 -0
  10. package/assets/runtime/internal/singletons.js +12 -9
  11. package/assets/runtime/internal/start.js +991 -356
  12. package/assets/runtime/paths.js +13 -0
  13. package/dist/chunks/cert.js +29255 -0
  14. package/dist/chunks/constants.js +8 -0
  15. package/dist/chunks/error.js +12 -0
  16. package/dist/chunks/index.js +476 -0
  17. package/dist/chunks/index2.js +817 -0
  18. package/dist/chunks/index3.js +640 -0
  19. package/dist/chunks/index4.js +109 -0
  20. package/dist/chunks/index5.js +635 -0
  21. package/dist/chunks/index6.js +827 -0
  22. package/dist/chunks/index7.js +15575 -0
  23. package/dist/chunks/index8.js +4207 -0
  24. package/dist/chunks/misc.js +3 -0
  25. package/dist/chunks/multipart-parser.js +449 -0
  26. package/dist/chunks/url.js +62 -0
  27. package/dist/cli.js +974 -83
  28. package/dist/hooks.js +28 -0
  29. package/dist/install-fetch.js +6514 -0
  30. package/dist/node.js +51 -0
  31. package/dist/ssr.js +1865 -0
  32. package/package.json +96 -54
  33. package/svelte-kit.js +2 -0
  34. package/types/ambient-modules.d.ts +190 -0
  35. package/types/app.d.ts +45 -0
  36. package/types/config.d.ts +165 -0
  37. package/types/endpoint.d.ts +18 -0
  38. package/types/helper.d.ts +41 -0
  39. package/types/hooks.d.ts +48 -0
  40. package/types/index.d.ts +17 -0
  41. package/types/internal.d.ts +231 -0
  42. package/types/page.d.ts +71 -0
  43. package/CHANGELOG.md +0 -288
  44. package/assets/runtime/app/navigation.js.map +0 -1
  45. package/assets/runtime/app/paths.js.map +0 -1
  46. package/assets/runtime/app/stores.js.map +0 -1
  47. package/assets/runtime/internal/singletons.js.map +0 -1
  48. package/assets/runtime/internal/start.js.map +0 -1
  49. package/assets/runtime/utils-85ebcc60.js +0 -18
  50. package/assets/runtime/utils-85ebcc60.js.map +0 -1
  51. package/dist/api.js +0 -44
  52. package/dist/api.js.map +0 -1
  53. package/dist/build.js +0 -246
  54. package/dist/build.js.map +0 -1
  55. package/dist/cli.js.map +0 -1
  56. package/dist/colors.js +0 -37
  57. package/dist/colors.js.map +0 -1
  58. package/dist/create_app.js +0 -578
  59. package/dist/create_app.js.map +0 -1
  60. package/dist/index.js +0 -12042
  61. package/dist/index.js.map +0 -1
  62. package/dist/index2.js +0 -544
  63. package/dist/index2.js.map +0 -1
  64. package/dist/index3.js +0 -71
  65. package/dist/index3.js.map +0 -1
  66. package/dist/index4.js +0 -466
  67. package/dist/index4.js.map +0 -1
  68. package/dist/index5.js +0 -729
  69. package/dist/index5.js.map +0 -1
  70. package/dist/index6.js +0 -730
  71. package/dist/index6.js.map +0 -1
  72. package/dist/logging.js +0 -43
  73. package/dist/logging.js.map +0 -1
  74. package/dist/package.js +0 -432
  75. package/dist/package.js.map +0 -1
  76. package/dist/renderer.js +0 -2391
  77. package/dist/renderer.js.map +0 -1
  78. package/dist/standard.js +0 -101
  79. package/dist/standard.js.map +0 -1
  80. package/dist/utils.js +0 -54
  81. package/dist/utils.js.map +0 -1
  82. package/svelte-kit +0 -3
package/assets/kit.js ADDED
@@ -0,0 +1,1935 @@
1
+ /**
2
+ * @param {Record<string, string | string[] | undefined>} headers
3
+ * @param {string} key
4
+ * @returns {string | undefined}
5
+ * @throws {Error}
6
+ */
7
+ function get_single_valued_header(headers, key) {
8
+ const value = headers[key];
9
+ if (Array.isArray(value)) {
10
+ if (value.length === 0) {
11
+ return undefined;
12
+ }
13
+ if (value.length > 1) {
14
+ throw new Error(
15
+ `Multiple headers provided for ${key}. Multiple may be provided only for set-cookie`
16
+ );
17
+ }
18
+ return value[0];
19
+ }
20
+ return value;
21
+ }
22
+
23
+ /** @param {Record<string, any>} obj */
24
+ function lowercase_keys(obj) {
25
+ /** @type {Record<string, any>} */
26
+ const clone = {};
27
+
28
+ for (const key in obj) {
29
+ clone[key.toLowerCase()] = obj[key];
30
+ }
31
+
32
+ return clone;
33
+ }
34
+
35
+ /** @param {Record<string, string>} params */
36
+ function decode_params(params) {
37
+ for (const key in params) {
38
+ // input has already been decoded by decodeURI
39
+ // now handle the rest that decodeURIComponent would do
40
+ params[key] = params[key]
41
+ .replace(/%23/g, '#')
42
+ .replace(/%3[Bb]/g, ';')
43
+ .replace(/%2[Cc]/g, ',')
44
+ .replace(/%2[Ff]/g, '/')
45
+ .replace(/%3[Ff]/g, '?')
46
+ .replace(/%3[Aa]/g, ':')
47
+ .replace(/%40/g, '@')
48
+ .replace(/%26/g, '&')
49
+ .replace(/%3[Dd]/g, '=')
50
+ .replace(/%2[Bb]/g, '+')
51
+ .replace(/%24/g, '$');
52
+ }
53
+
54
+ return params;
55
+ }
56
+
57
+ /** @param {string} body */
58
+ function error(body) {
59
+ return {
60
+ status: 500,
61
+ body,
62
+ headers: {}
63
+ };
64
+ }
65
+
66
+ /** @param {unknown} s */
67
+ function is_string(s) {
68
+ return typeof s === 'string' || s instanceof String;
69
+ }
70
+
71
+ const text_types = new Set([
72
+ 'application/xml',
73
+ 'application/json',
74
+ 'application/x-www-form-urlencoded',
75
+ 'multipart/form-data'
76
+ ]);
77
+
78
+ /**
79
+ * Decides how the body should be parsed based on its mime type. Should match what's in parse_body
80
+ *
81
+ * @param {string | undefined | null} content_type The `content-type` header of a request/response.
82
+ * @returns {boolean}
83
+ */
84
+ function is_text(content_type) {
85
+ if (!content_type) return true; // defaults to json
86
+ const type = content_type.split(';')[0].toLowerCase(); // get the mime type
87
+
88
+ return type.startsWith('text/') || type.endsWith('+xml') || text_types.has(type);
89
+ }
90
+
91
+ /**
92
+ * @param {import('types/hooks').ServerRequest} request
93
+ * @param {import('types/internal').SSREndpoint} route
94
+ * @param {RegExpExecArray} match
95
+ * @returns {Promise<import('types/hooks').ServerResponse | undefined>}
96
+ */
97
+ async function render_endpoint(request, route, match) {
98
+ const mod = await route.load();
99
+
100
+ /** @type {import('types/endpoint').RequestHandler} */
101
+ const handler = mod[request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word
102
+
103
+ if (!handler) {
104
+ return;
105
+ }
106
+
107
+ // we're mutating `request` so that we don't have to do { ...request, params }
108
+ // on the next line, since that breaks the getters that replace path, query and
109
+ // origin. We could revert that once we remove the getters
110
+ request.params = route.params ? decode_params(route.params(match)) : {};
111
+
112
+ const response = await handler(request);
113
+ const preface = `Invalid response from route ${request.url.pathname}`;
114
+
115
+ if (!response) {
116
+ return;
117
+ }
118
+ if (typeof response !== 'object') {
119
+ return error(`${preface}: expected an object, got ${typeof response}`);
120
+ }
121
+
122
+ let { status = 200, body, headers = {} } = response;
123
+
124
+ headers = lowercase_keys(headers);
125
+ const type = get_single_valued_header(headers, 'content-type');
126
+
127
+ if (!is_text(type) && !(body instanceof Uint8Array || is_string(body))) {
128
+ return error(
129
+ `${preface}: body must be an instance of string or Uint8Array if content-type is not a supported textual content-type`
130
+ );
131
+ }
132
+
133
+ /** @type {import('types/hooks').StrictBody} */
134
+ let normalized_body;
135
+
136
+ // ensure the body is an object
137
+ if (
138
+ (typeof body === 'object' || typeof body === 'undefined') &&
139
+ !(body instanceof Uint8Array) &&
140
+ (!type || type.startsWith('application/json'))
141
+ ) {
142
+ headers = { ...headers, 'content-type': 'application/json; charset=utf-8' };
143
+ normalized_body = JSON.stringify(typeof body === 'undefined' ? {} : body);
144
+ } else {
145
+ normalized_body = /** @type {import('types/hooks').StrictBody} */ (body);
146
+ }
147
+
148
+ return { status, body: normalized_body, headers };
149
+ }
150
+
151
+ var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
152
+ var unsafeChars = /[<>\b\f\n\r\t\0\u2028\u2029]/g;
153
+ var reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
154
+ var escaped = {
155
+ '<': '\\u003C',
156
+ '>': '\\u003E',
157
+ '/': '\\u002F',
158
+ '\\': '\\\\',
159
+ '\b': '\\b',
160
+ '\f': '\\f',
161
+ '\n': '\\n',
162
+ '\r': '\\r',
163
+ '\t': '\\t',
164
+ '\0': '\\0',
165
+ '\u2028': '\\u2028',
166
+ '\u2029': '\\u2029'
167
+ };
168
+ var objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0');
169
+ function devalue(value) {
170
+ var counts = new Map();
171
+ function walk(thing) {
172
+ if (typeof thing === 'function') {
173
+ throw new Error("Cannot stringify a function");
174
+ }
175
+ if (counts.has(thing)) {
176
+ counts.set(thing, counts.get(thing) + 1);
177
+ return;
178
+ }
179
+ counts.set(thing, 1);
180
+ if (!isPrimitive(thing)) {
181
+ var type = getType(thing);
182
+ switch (type) {
183
+ case 'Number':
184
+ case 'String':
185
+ case 'Boolean':
186
+ case 'Date':
187
+ case 'RegExp':
188
+ return;
189
+ case 'Array':
190
+ thing.forEach(walk);
191
+ break;
192
+ case 'Set':
193
+ case 'Map':
194
+ Array.from(thing).forEach(walk);
195
+ break;
196
+ default:
197
+ var proto = Object.getPrototypeOf(thing);
198
+ if (proto !== Object.prototype &&
199
+ proto !== null &&
200
+ Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames) {
201
+ throw new Error("Cannot stringify arbitrary non-POJOs");
202
+ }
203
+ if (Object.getOwnPropertySymbols(thing).length > 0) {
204
+ throw new Error("Cannot stringify POJOs with symbolic keys");
205
+ }
206
+ Object.keys(thing).forEach(function (key) { return walk(thing[key]); });
207
+ }
208
+ }
209
+ }
210
+ walk(value);
211
+ var names = new Map();
212
+ Array.from(counts)
213
+ .filter(function (entry) { return entry[1] > 1; })
214
+ .sort(function (a, b) { return b[1] - a[1]; })
215
+ .forEach(function (entry, i) {
216
+ names.set(entry[0], getName(i));
217
+ });
218
+ function stringify(thing) {
219
+ if (names.has(thing)) {
220
+ return names.get(thing);
221
+ }
222
+ if (isPrimitive(thing)) {
223
+ return stringifyPrimitive(thing);
224
+ }
225
+ var type = getType(thing);
226
+ switch (type) {
227
+ case 'Number':
228
+ case 'String':
229
+ case 'Boolean':
230
+ return "Object(" + stringify(thing.valueOf()) + ")";
231
+ case 'RegExp':
232
+ return "new RegExp(" + stringifyString(thing.source) + ", \"" + thing.flags + "\")";
233
+ case 'Date':
234
+ return "new Date(" + thing.getTime() + ")";
235
+ case 'Array':
236
+ var members = thing.map(function (v, i) { return i in thing ? stringify(v) : ''; });
237
+ var tail = thing.length === 0 || (thing.length - 1 in thing) ? '' : ',';
238
+ return "[" + members.join(',') + tail + "]";
239
+ case 'Set':
240
+ case 'Map':
241
+ return "new " + type + "([" + Array.from(thing).map(stringify).join(',') + "])";
242
+ default:
243
+ var obj = "{" + Object.keys(thing).map(function (key) { return safeKey(key) + ":" + stringify(thing[key]); }).join(',') + "}";
244
+ var proto = Object.getPrototypeOf(thing);
245
+ if (proto === null) {
246
+ return Object.keys(thing).length > 0
247
+ ? "Object.assign(Object.create(null)," + obj + ")"
248
+ : "Object.create(null)";
249
+ }
250
+ return obj;
251
+ }
252
+ }
253
+ var str = stringify(value);
254
+ if (names.size) {
255
+ var params_1 = [];
256
+ var statements_1 = [];
257
+ var values_1 = [];
258
+ names.forEach(function (name, thing) {
259
+ params_1.push(name);
260
+ if (isPrimitive(thing)) {
261
+ values_1.push(stringifyPrimitive(thing));
262
+ return;
263
+ }
264
+ var type = getType(thing);
265
+ switch (type) {
266
+ case 'Number':
267
+ case 'String':
268
+ case 'Boolean':
269
+ values_1.push("Object(" + stringify(thing.valueOf()) + ")");
270
+ break;
271
+ case 'RegExp':
272
+ values_1.push(thing.toString());
273
+ break;
274
+ case 'Date':
275
+ values_1.push("new Date(" + thing.getTime() + ")");
276
+ break;
277
+ case 'Array':
278
+ values_1.push("Array(" + thing.length + ")");
279
+ thing.forEach(function (v, i) {
280
+ statements_1.push(name + "[" + i + "]=" + stringify(v));
281
+ });
282
+ break;
283
+ case 'Set':
284
+ values_1.push("new Set");
285
+ statements_1.push(name + "." + Array.from(thing).map(function (v) { return "add(" + stringify(v) + ")"; }).join('.'));
286
+ break;
287
+ case 'Map':
288
+ values_1.push("new Map");
289
+ statements_1.push(name + "." + Array.from(thing).map(function (_a) {
290
+ var k = _a[0], v = _a[1];
291
+ return "set(" + stringify(k) + ", " + stringify(v) + ")";
292
+ }).join('.'));
293
+ break;
294
+ default:
295
+ values_1.push(Object.getPrototypeOf(thing) === null ? 'Object.create(null)' : '{}');
296
+ Object.keys(thing).forEach(function (key) {
297
+ statements_1.push("" + name + safeProp(key) + "=" + stringify(thing[key]));
298
+ });
299
+ }
300
+ });
301
+ statements_1.push("return " + str);
302
+ return "(function(" + params_1.join(',') + "){" + statements_1.join(';') + "}(" + values_1.join(',') + "))";
303
+ }
304
+ else {
305
+ return str;
306
+ }
307
+ }
308
+ function getName(num) {
309
+ var name = '';
310
+ do {
311
+ name = chars[num % chars.length] + name;
312
+ num = ~~(num / chars.length) - 1;
313
+ } while (num >= 0);
314
+ return reserved.test(name) ? name + "_" : name;
315
+ }
316
+ function isPrimitive(thing) {
317
+ return Object(thing) !== thing;
318
+ }
319
+ function stringifyPrimitive(thing) {
320
+ if (typeof thing === 'string')
321
+ return stringifyString(thing);
322
+ if (thing === void 0)
323
+ return 'void 0';
324
+ if (thing === 0 && 1 / thing < 0)
325
+ return '-0';
326
+ var str = String(thing);
327
+ if (typeof thing === 'number')
328
+ return str.replace(/^(-)?0\./, '$1.');
329
+ return str;
330
+ }
331
+ function getType(thing) {
332
+ return Object.prototype.toString.call(thing).slice(8, -1);
333
+ }
334
+ function escapeUnsafeChar(c) {
335
+ return escaped[c] || c;
336
+ }
337
+ function escapeUnsafeChars(str) {
338
+ return str.replace(unsafeChars, escapeUnsafeChar);
339
+ }
340
+ function safeKey(key) {
341
+ return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escapeUnsafeChars(JSON.stringify(key));
342
+ }
343
+ function safeProp(key) {
344
+ return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? "." + key : "[" + escapeUnsafeChars(JSON.stringify(key)) + "]";
345
+ }
346
+ function stringifyString(str) {
347
+ var result = '"';
348
+ for (var i = 0; i < str.length; i += 1) {
349
+ var char = str.charAt(i);
350
+ var code = char.charCodeAt(0);
351
+ if (char === '"') {
352
+ result += '\\"';
353
+ }
354
+ else if (char in escaped) {
355
+ result += escaped[char];
356
+ }
357
+ else if (code >= 0xd800 && code <= 0xdfff) {
358
+ var next = str.charCodeAt(i + 1);
359
+ // If this is the beginning of a [high, low] surrogate pair,
360
+ // add the next two characters, otherwise escape
361
+ if (code <= 0xdbff && (next >= 0xdc00 && next <= 0xdfff)) {
362
+ result += char + str[++i];
363
+ }
364
+ else {
365
+ result += "\\u" + code.toString(16).toUpperCase();
366
+ }
367
+ }
368
+ else {
369
+ result += char;
370
+ }
371
+ }
372
+ result += '"';
373
+ return result;
374
+ }
375
+
376
+ function noop() { }
377
+ function safe_not_equal(a, b) {
378
+ return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
379
+ }
380
+ Promise.resolve();
381
+
382
+ const subscriber_queue = [];
383
+ /**
384
+ * Create a `Writable` store that allows both updating and reading by subscription.
385
+ * @param {*=}value initial value
386
+ * @param {StartStopNotifier=}start start and stop notifications for subscriptions
387
+ */
388
+ function writable(value, start = noop) {
389
+ let stop;
390
+ const subscribers = new Set();
391
+ function set(new_value) {
392
+ if (safe_not_equal(value, new_value)) {
393
+ value = new_value;
394
+ if (stop) { // store is ready
395
+ const run_queue = !subscriber_queue.length;
396
+ for (const subscriber of subscribers) {
397
+ subscriber[1]();
398
+ subscriber_queue.push(subscriber, value);
399
+ }
400
+ if (run_queue) {
401
+ for (let i = 0; i < subscriber_queue.length; i += 2) {
402
+ subscriber_queue[i][0](subscriber_queue[i + 1]);
403
+ }
404
+ subscriber_queue.length = 0;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ function update(fn) {
410
+ set(fn(value));
411
+ }
412
+ function subscribe(run, invalidate = noop) {
413
+ const subscriber = [run, invalidate];
414
+ subscribers.add(subscriber);
415
+ if (subscribers.size === 1) {
416
+ stop = start(set) || noop;
417
+ }
418
+ run(value);
419
+ return () => {
420
+ subscribers.delete(subscriber);
421
+ if (subscribers.size === 0) {
422
+ stop();
423
+ stop = null;
424
+ }
425
+ };
426
+ }
427
+ return { set, update, subscribe };
428
+ }
429
+
430
+ /**
431
+ * @param {unknown} err
432
+ * @return {Error}
433
+ */
434
+ function coalesce_to_error(err) {
435
+ return err instanceof Error ||
436
+ (err && /** @type {any} */ (err).name && /** @type {any} */ (err).message)
437
+ ? /** @type {Error} */ (err)
438
+ : new Error(JSON.stringify(err));
439
+ }
440
+
441
+ /**
442
+ * Hash using djb2
443
+ * @param {import('types/hooks').StrictBody} value
444
+ */
445
+ function hash(value) {
446
+ let hash = 5381;
447
+ let i = value.length;
448
+
449
+ if (typeof value === 'string') {
450
+ while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
451
+ } else {
452
+ while (i) hash = (hash * 33) ^ value[--i];
453
+ }
454
+
455
+ return (hash >>> 0).toString(36);
456
+ }
457
+
458
+ /** @type {Record<string, string>} */
459
+ const escape_json_string_in_html_dict = {
460
+ '"': '\\"',
461
+ '<': '\\u003C',
462
+ '>': '\\u003E',
463
+ '/': '\\u002F',
464
+ '\\': '\\\\',
465
+ '\b': '\\b',
466
+ '\f': '\\f',
467
+ '\n': '\\n',
468
+ '\r': '\\r',
469
+ '\t': '\\t',
470
+ '\0': '\\0',
471
+ '\u2028': '\\u2028',
472
+ '\u2029': '\\u2029'
473
+ };
474
+
475
+ /** @param {string} str */
476
+ function escape_json_string_in_html(str) {
477
+ return escape(
478
+ str,
479
+ escape_json_string_in_html_dict,
480
+ (code) => `\\u${code.toString(16).toUpperCase()}`
481
+ );
482
+ }
483
+
484
+ /** @type {Record<string, string>} */
485
+ const escape_html_attr_dict = {
486
+ '<': '&lt;',
487
+ '>': '&gt;',
488
+ '"': '&quot;'
489
+ };
490
+
491
+ /**
492
+ * use for escaping string values to be used html attributes on the page
493
+ * e.g.
494
+ * <script data-url="here">
495
+ *
496
+ * @param {string} str
497
+ * @returns string escaped string
498
+ */
499
+ function escape_html_attr(str) {
500
+ return '"' + escape(str, escape_html_attr_dict, (code) => `&#${code};`) + '"';
501
+ }
502
+
503
+ /**
504
+ *
505
+ * @param str {string} string to escape
506
+ * @param dict {Record<string, string>} dictionary of character replacements
507
+ * @param unicode_encoder {function(number): string} encoder to use for high unicode characters
508
+ * @returns {string}
509
+ */
510
+ function escape(str, dict, unicode_encoder) {
511
+ let result = '';
512
+
513
+ for (let i = 0; i < str.length; i += 1) {
514
+ const char = str.charAt(i);
515
+ const code = char.charCodeAt(0);
516
+
517
+ if (char in dict) {
518
+ result += dict[char];
519
+ } else if (code >= 0xd800 && code <= 0xdfff) {
520
+ const next = str.charCodeAt(i + 1);
521
+
522
+ // If this is the beginning of a [high, low] surrogate pair,
523
+ // add the next two characters, otherwise escape
524
+ if (code <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) {
525
+ result += char + str[++i];
526
+ } else {
527
+ result += unicode_encoder(code);
528
+ }
529
+ } else {
530
+ result += char;
531
+ }
532
+ }
533
+
534
+ return result;
535
+ }
536
+
537
+ const s = JSON.stringify;
538
+
539
+ // TODO rename this function/module
540
+
541
+ /**
542
+ * @param {{
543
+ * branch: Array<import('./types').Loaded>;
544
+ * options: import('types/internal').SSRRenderOptions;
545
+ * $session: any;
546
+ * page_config: { hydrate: boolean, router: boolean, ssr: boolean };
547
+ * status: number;
548
+ * error?: Error,
549
+ * url: URL;
550
+ * params: Record<string, string>
551
+ * }} opts
552
+ */
553
+ async function render_response({
554
+ branch,
555
+ options,
556
+ $session,
557
+ page_config,
558
+ status,
559
+ error,
560
+ url,
561
+ params
562
+ }) {
563
+ const css = new Set(options.manifest._.entry.css);
564
+ const js = new Set(options.manifest._.entry.js);
565
+ const styles = new Set();
566
+
567
+ /** @type {Array<{ url: string, body: string, json: string }>} */
568
+ const serialized_data = [];
569
+
570
+ let rendered;
571
+
572
+ let is_private = false;
573
+ let maxage;
574
+
575
+ if (error) {
576
+ error.stack = options.get_stack(error);
577
+ }
578
+
579
+ if (page_config.ssr) {
580
+ branch.forEach(({ node, loaded, fetched, uses_credentials }) => {
581
+ if (node.css) node.css.forEach((url) => css.add(url));
582
+ if (node.js) node.js.forEach((url) => js.add(url));
583
+ if (node.styles) node.styles.forEach((content) => styles.add(content));
584
+
585
+ // TODO probably better if `fetched` wasn't populated unless `hydrate`
586
+ if (fetched && page_config.hydrate) serialized_data.push(...fetched);
587
+
588
+ if (uses_credentials) is_private = true;
589
+
590
+ maxage = loaded.maxage;
591
+ });
592
+
593
+ const session = writable($session);
594
+
595
+ /** @type {Record<string, any>} */
596
+ const props = {
597
+ stores: {
598
+ page: writable(null),
599
+ navigating: writable(null),
600
+ session
601
+ },
602
+ page: { url, params, status, error },
603
+ components: branch.map(({ node }) => node.module.default)
604
+ };
605
+
606
+ // TODO remove this for 1.0
607
+ /**
608
+ * @param {string} property
609
+ * @param {string} replacement
610
+ */
611
+ const print_error = (property, replacement) => {
612
+ Object.defineProperty(props.page, property, {
613
+ get: () => {
614
+ throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
615
+ }
616
+ });
617
+ };
618
+
619
+ print_error('origin', 'origin');
620
+ print_error('path', 'pathname');
621
+ print_error('query', 'searchParams');
622
+
623
+ // props_n (instead of props[n]) makes it easy to avoid
624
+ // unnecessary updates for layout components
625
+ for (let i = 0; i < branch.length; i += 1) {
626
+ props[`props_${i}`] = await branch[i].loaded.props;
627
+ }
628
+
629
+ let session_tracking_active = false;
630
+ const unsubscribe = session.subscribe(() => {
631
+ if (session_tracking_active) is_private = true;
632
+ });
633
+ session_tracking_active = true;
634
+
635
+ try {
636
+ rendered = options.root.render(props);
637
+ } finally {
638
+ unsubscribe();
639
+ }
640
+ } else {
641
+ rendered = { head: '', html: '', css: { code: '', map: null } };
642
+ }
643
+
644
+ const include_js = page_config.router || page_config.hydrate;
645
+ if (!include_js) js.clear();
646
+
647
+ // TODO strip the AMP stuff out of the build if not relevant
648
+ const links = options.amp
649
+ ? styles.size > 0 || rendered.css.code.length > 0
650
+ ? `<style amp-custom>${Array.from(styles).concat(rendered.css.code).join('\n')}</style>`
651
+ : ''
652
+ : [
653
+ // From https://web.dev/priority-hints/:
654
+ // Generally, preloads will load in the order the parser gets to them for anything above "Medium" priority
655
+ // Thus, we should list CSS first
656
+ ...Array.from(css).map((dep) => `<link rel="stylesheet" href="${options.prefix}${dep}">`),
657
+ ...Array.from(js).map((dep) => `<link rel="modulepreload" href="${options.prefix}${dep}">`)
658
+ ].join('\n\t\t');
659
+
660
+ /** @type {string} */
661
+ let init = '';
662
+
663
+ if (options.amp) {
664
+ init = `
665
+ <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>
666
+ <noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
667
+ <script async src="https://cdn.ampproject.org/v0.js"></script>`;
668
+ init += options.service_worker
669
+ ? '<script async custom-element="amp-install-serviceworker" src="https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js"></script>'
670
+ : '';
671
+ } else if (include_js) {
672
+ // prettier-ignore
673
+ init = `<script type="module">
674
+ import { start } from ${s(options.prefix + options.manifest._.entry.file)};
675
+ start({
676
+ target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
677
+ paths: ${s(options.paths)},
678
+ session: ${try_serialize($session, (error) => {
679
+ throw new Error(`Failed to serialize session data: ${error.message}`);
680
+ })},
681
+ route: ${!!page_config.router},
682
+ spa: ${!page_config.ssr},
683
+ trailing_slash: ${s(options.trailing_slash)},
684
+ hydrate: ${page_config.ssr && page_config.hydrate ? `{
685
+ status: ${status},
686
+ error: ${serialize_error(error)},
687
+ nodes: [
688
+ ${(branch || [])
689
+ .map(({ node }) => `import(${s(options.prefix + node.entry)})`)
690
+ .join(',\n\t\t\t\t\t\t')}
691
+ ],
692
+ url: new URL(${s(url.href)}),
693
+ params: ${devalue(params)}
694
+ }` : 'null'}
695
+ });
696
+ </script>`;
697
+ }
698
+
699
+ if (options.service_worker && !options.amp) {
700
+ init += `<script>
701
+ if ('serviceWorker' in navigator) {
702
+ navigator.serviceWorker.register('${options.service_worker}');
703
+ }
704
+ </script>`;
705
+ }
706
+
707
+ const head = [
708
+ rendered.head,
709
+ styles.size && !options.amp
710
+ ? `<style data-svelte>${Array.from(styles).join('\n')}</style>`
711
+ : '',
712
+ links,
713
+ init
714
+ ].join('\n\n\t\t');
715
+
716
+ let body = rendered.html;
717
+ if (options.amp) {
718
+ if (options.service_worker) {
719
+ body += `<amp-install-serviceworker src="${options.service_worker}" layout="nodisplay"></amp-install-serviceworker>`;
720
+ }
721
+ } else {
722
+ body += serialized_data
723
+ .map(({ url, body, json }) => {
724
+ let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(
725
+ url
726
+ )}`;
727
+ if (body) attributes += ` data-body="${hash(body)}"`;
728
+
729
+ return `<script ${attributes}>${json}</script>`;
730
+ })
731
+ .join('\n\n\t');
732
+ }
733
+
734
+ /** @type {import('types/helper').ResponseHeaders} */
735
+ const headers = {
736
+ 'content-type': 'text/html'
737
+ };
738
+
739
+ if (maxage) {
740
+ headers['cache-control'] = `${is_private ? 'private' : 'public'}, max-age=${maxage}`;
741
+ }
742
+
743
+ if (!options.floc) {
744
+ headers['permissions-policy'] = 'interest-cohort=()';
745
+ }
746
+
747
+ const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
748
+ const assets =
749
+ options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
750
+
751
+ return {
752
+ status,
753
+ headers,
754
+ body: options.template({
755
+ head,
756
+ body,
757
+ assets
758
+ })
759
+ };
760
+ }
761
+
762
+ /**
763
+ * @param {any} data
764
+ * @param {(error: Error) => void} [fail]
765
+ */
766
+ function try_serialize(data, fail) {
767
+ try {
768
+ return devalue(data);
769
+ } catch (err) {
770
+ if (fail) fail(coalesce_to_error(err));
771
+ return null;
772
+ }
773
+ }
774
+
775
+ // Ensure we return something truthy so the client will not re-render the page over the error
776
+
777
+ /** @param {(Error & {frame?: string} & {loc?: object}) | undefined | null} error */
778
+ function serialize_error(error) {
779
+ if (!error) return null;
780
+ let serialized = try_serialize(error);
781
+ if (!serialized) {
782
+ const { name, message, stack } = error;
783
+ serialized = try_serialize({ ...error, name, message, stack });
784
+ }
785
+ if (!serialized) {
786
+ serialized = '{}';
787
+ }
788
+ return serialized;
789
+ }
790
+
791
+ /**
792
+ * @param {import('types/page').LoadOutput} loaded
793
+ * @returns {import('types/internal').NormalizedLoadOutput}
794
+ */
795
+ function normalize(loaded) {
796
+ const has_error_status =
797
+ loaded.status && loaded.status >= 400 && loaded.status <= 599 && !loaded.redirect;
798
+ if (loaded.error || has_error_status) {
799
+ const status = loaded.status;
800
+
801
+ if (!loaded.error && has_error_status) {
802
+ return {
803
+ status: status || 500,
804
+ error: new Error()
805
+ };
806
+ }
807
+
808
+ const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
809
+
810
+ if (!(error instanceof Error)) {
811
+ return {
812
+ status: 500,
813
+ error: new Error(
814
+ `"error" property returned from load() must be a string or instance of Error, received type "${typeof error}"`
815
+ )
816
+ };
817
+ }
818
+
819
+ if (!status || status < 400 || status > 599) {
820
+ console.warn('"error" returned from load() without a valid status code — defaulting to 500');
821
+ return { status: 500, error };
822
+ }
823
+
824
+ return { status, error };
825
+ }
826
+
827
+ if (loaded.redirect) {
828
+ if (!loaded.status || Math.floor(loaded.status / 100) !== 3) {
829
+ return {
830
+ status: 500,
831
+ error: new Error(
832
+ '"redirect" property returned from load() must be accompanied by a 3xx status code'
833
+ )
834
+ };
835
+ }
836
+
837
+ if (typeof loaded.redirect !== 'string') {
838
+ return {
839
+ status: 500,
840
+ error: new Error('"redirect" property returned from load() must be a string')
841
+ };
842
+ }
843
+ }
844
+
845
+ // TODO remove before 1.0
846
+ if (/** @type {any} */ (loaded).context) {
847
+ throw new Error(
848
+ 'You are returning "context" from a load function. ' +
849
+ '"context" was renamed to "stuff", please adjust your code accordingly.'
850
+ );
851
+ }
852
+
853
+ return /** @type {import('types/internal').NormalizedLoadOutput} */ (loaded);
854
+ }
855
+
856
+ const absolute = /^([a-z]+:)?\/?\//;
857
+ const scheme = /^[a-z]+:/;
858
+
859
+ /**
860
+ * @param {string} base
861
+ * @param {string} path
862
+ */
863
+ function resolve(base, path) {
864
+ if (scheme.test(path)) return path;
865
+
866
+ const base_match = absolute.exec(base);
867
+ const path_match = absolute.exec(path);
868
+
869
+ if (!base_match) {
870
+ throw new Error(`bad base path: "${base}"`);
871
+ }
872
+
873
+ const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/');
874
+ const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/');
875
+
876
+ baseparts.pop();
877
+
878
+ for (let i = 0; i < pathparts.length; i += 1) {
879
+ const part = pathparts[i];
880
+ if (part === '.') continue;
881
+ else if (part === '..') baseparts.pop();
882
+ else baseparts.push(part);
883
+ }
884
+
885
+ const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || '';
886
+
887
+ return `${prefix}${baseparts.join('/')}`;
888
+ }
889
+
890
+ /** @param {string} path */
891
+ function is_root_relative(path) {
892
+ return path[0] === '/' && path[1] !== '/';
893
+ }
894
+
895
+ /**
896
+ * @param {{
897
+ * request: import('types/hooks').ServerRequest;
898
+ * options: import('types/internal').SSRRenderOptions;
899
+ * state: import('types/internal').SSRRenderState;
900
+ * route: import('types/internal').SSRPage | null;
901
+ * url: URL;
902
+ * params: Record<string, string>;
903
+ * node: import('types/internal').SSRNode;
904
+ * $session: any;
905
+ * stuff: Record<string, any>;
906
+ * prerender_enabled: boolean;
907
+ * is_leaf: boolean;
908
+ * is_error: boolean;
909
+ * status?: number;
910
+ * error?: Error;
911
+ * }} opts
912
+ * @returns {Promise<import('./types').Loaded | undefined>} undefined for fallthrough
913
+ */
914
+ async function load_node({
915
+ request,
916
+ options,
917
+ state,
918
+ route,
919
+ url,
920
+ params,
921
+ node,
922
+ $session,
923
+ stuff,
924
+ prerender_enabled,
925
+ is_leaf,
926
+ is_error,
927
+ status,
928
+ error
929
+ }) {
930
+ const { module } = node;
931
+
932
+ let uses_credentials = false;
933
+
934
+ /**
935
+ * @type {Array<{
936
+ * url: string;
937
+ * body: string;
938
+ * json: string;
939
+ * }>}
940
+ */
941
+ const fetched = [];
942
+
943
+ /**
944
+ * @type {string[]}
945
+ */
946
+ let set_cookie_headers = [];
947
+
948
+ let loaded;
949
+
950
+ const url_proxy = new Proxy(url, {
951
+ get: (target, prop, receiver) => {
952
+ if (prerender_enabled && (prop === 'search' || prop === 'searchParams')) {
953
+ throw new Error('Cannot access query on a page with prerendering enabled');
954
+ }
955
+ return Reflect.get(target, prop, receiver);
956
+ }
957
+ });
958
+
959
+ if (module.load) {
960
+ /** @type {import('types/page').LoadInput | import('types/page').ErrorLoadInput} */
961
+ const load_input = {
962
+ url: url_proxy,
963
+ params,
964
+ get session() {
965
+ uses_credentials = true;
966
+ return $session;
967
+ },
968
+ /**
969
+ * @param {RequestInfo} resource
970
+ * @param {RequestInit} opts
971
+ */
972
+ fetch: async (resource, opts = {}) => {
973
+ /** @type {string} */
974
+ let requested;
975
+
976
+ if (typeof resource === 'string') {
977
+ requested = resource;
978
+ } else {
979
+ requested = resource.url;
980
+
981
+ opts = {
982
+ method: resource.method,
983
+ headers: resource.headers,
984
+ body: resource.body,
985
+ mode: resource.mode,
986
+ credentials: resource.credentials,
987
+ cache: resource.cache,
988
+ redirect: resource.redirect,
989
+ referrer: resource.referrer,
990
+ integrity: resource.integrity,
991
+ ...opts
992
+ };
993
+ }
994
+
995
+ opts.headers = new Headers(opts.headers);
996
+
997
+ const resolved = resolve(request.url.pathname, requested.split('?')[0]);
998
+
999
+ let response;
1000
+
1001
+ // handle fetch requests for static assets. e.g. prebaked data, etc.
1002
+ // we need to support everything the browser's fetch supports
1003
+ const prefix = options.paths.assets || options.paths.base;
1004
+ const filename = (
1005
+ resolved.startsWith(prefix) ? resolved.slice(prefix.length) : resolved
1006
+ ).slice(1);
1007
+ const filename_html = `${filename}/index.html`; // path may also match path/index.html
1008
+
1009
+ const is_asset = options.manifest.assets.has(filename);
1010
+ const is_asset_html = options.manifest.assets.has(filename_html);
1011
+
1012
+ if (is_asset || is_asset_html) {
1013
+ const file = is_asset ? filename : filename_html;
1014
+
1015
+ if (options.read) {
1016
+ const type = is_asset
1017
+ ? options.manifest._.mime[filename.slice(filename.lastIndexOf('.'))]
1018
+ : 'text/html';
1019
+
1020
+ response = new Response(options.read(file), {
1021
+ headers: type ? { 'content-type': type } : {}
1022
+ });
1023
+ } else {
1024
+ response = await fetch(`${url.origin}/${file}`, /** @type {RequestInit} */ (opts));
1025
+ }
1026
+ } else if (is_root_relative(resolved)) {
1027
+ const relative = resolved;
1028
+
1029
+ // TODO: fix type https://github.com/node-fetch/node-fetch/issues/1113
1030
+ if (opts.credentials !== 'omit') {
1031
+ uses_credentials = true;
1032
+
1033
+ if (request.headers.cookie) {
1034
+ opts.headers.set('cookie', request.headers.cookie);
1035
+ }
1036
+
1037
+ if (request.headers.authorization && !opts.headers.has('authorization')) {
1038
+ opts.headers.set('authorization', request.headers.authorization);
1039
+ }
1040
+ }
1041
+
1042
+ if (opts.body && typeof opts.body !== 'string') {
1043
+ // per https://developer.mozilla.org/en-US/docs/Web/API/Request/Request, this can be a
1044
+ // Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object.
1045
+ // non-string bodies are irksome to deal with, but luckily aren't particularly useful
1046
+ // in this context anyway, so we take the easy route and ban them
1047
+ throw new Error('Request body must be a string');
1048
+ }
1049
+
1050
+ const rendered = await respond(
1051
+ {
1052
+ url: new URL(requested, request.url),
1053
+ method: opts.method || 'GET',
1054
+ headers: Object.fromEntries(opts.headers),
1055
+ rawBody: opts.body == null ? null : new TextEncoder().encode(opts.body)
1056
+ },
1057
+ options,
1058
+ {
1059
+ fetched: requested,
1060
+ initiator: route
1061
+ }
1062
+ );
1063
+
1064
+ if (rendered) {
1065
+ if (state.prerender) {
1066
+ state.prerender.dependencies.set(relative, rendered);
1067
+ }
1068
+
1069
+ // Set-Cookie must be filtered out (done below) and that's the only header value that
1070
+ // can be an array so we know we have only simple values
1071
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
1072
+ response = new Response(rendered.body, {
1073
+ status: rendered.status,
1074
+ headers: /** @type {Record<string, string>} */ (rendered.headers)
1075
+ });
1076
+ } else {
1077
+ // we can't load the endpoint from our own manifest,
1078
+ // so we need to make an actual HTTP request
1079
+ return fetch(new URL(requested, request.url).href, {
1080
+ method: opts.method || 'GET',
1081
+ headers: opts.headers
1082
+ });
1083
+ }
1084
+ } else {
1085
+ // external
1086
+ if (resolved.startsWith('//')) {
1087
+ throw new Error(
1088
+ `Cannot request protocol-relative URL (${requested}) in server-side fetch`
1089
+ );
1090
+ }
1091
+
1092
+ // external fetch
1093
+ // allow cookie passthrough for "same-origin"
1094
+ // if SvelteKit is serving my.domain.com:
1095
+ // - domain.com WILL NOT receive cookies
1096
+ // - my.domain.com WILL receive cookies
1097
+ // - api.domain.dom WILL NOT receive cookies
1098
+ // - sub.my.domain.com WILL receive cookies
1099
+ // ports do not affect the resolution
1100
+ // leading dot prevents mydomain.com matching domain.com
1101
+ if (
1102
+ `.${new URL(requested).hostname}`.endsWith(`.${request.url.hostname}`) &&
1103
+ opts.credentials !== 'omit'
1104
+ ) {
1105
+ uses_credentials = true;
1106
+ opts.headers.set('cookie', request.headers.cookie);
1107
+ }
1108
+
1109
+ const external_request = new Request(requested, /** @type {RequestInit} */ (opts));
1110
+ response = await options.hooks.externalFetch.call(null, external_request);
1111
+ }
1112
+
1113
+ if (response) {
1114
+ const proxy = new Proxy(response, {
1115
+ get(response, key, _receiver) {
1116
+ async function text() {
1117
+ const body = await response.text();
1118
+
1119
+ /** @type {import('types/helper').ResponseHeaders} */
1120
+ const headers = {};
1121
+ for (const [key, value] of response.headers) {
1122
+ if (key === 'set-cookie') {
1123
+ set_cookie_headers = set_cookie_headers.concat(value);
1124
+ } else if (key !== 'etag') {
1125
+ headers[key] = value;
1126
+ }
1127
+ }
1128
+
1129
+ if (!opts.body || typeof opts.body === 'string') {
1130
+ // prettier-ignore
1131
+ fetched.push({
1132
+ url: requested,
1133
+ body: /** @type {string} */ (opts.body),
1134
+ json: `{"status":${response.status},"statusText":${s(response.statusText)},"headers":${s(headers)},"body":"${escape_json_string_in_html(body)}"}`
1135
+ });
1136
+ }
1137
+
1138
+ return body;
1139
+ }
1140
+
1141
+ if (key === 'text') {
1142
+ return text;
1143
+ }
1144
+
1145
+ if (key === 'json') {
1146
+ return async () => {
1147
+ return JSON.parse(await text());
1148
+ };
1149
+ }
1150
+
1151
+ // TODO arrayBuffer?
1152
+
1153
+ return Reflect.get(response, key, response);
1154
+ }
1155
+ });
1156
+
1157
+ return proxy;
1158
+ }
1159
+
1160
+ return (
1161
+ response ||
1162
+ new Response('Not found', {
1163
+ status: 404
1164
+ })
1165
+ );
1166
+ },
1167
+ stuff: { ...stuff }
1168
+ };
1169
+
1170
+ if (options.dev) {
1171
+ // TODO remove this for 1.0
1172
+ Object.defineProperty(load_input, 'page', {
1173
+ get: () => {
1174
+ throw new Error('`page` in `load` functions has been replaced by `url` and `params`');
1175
+ }
1176
+ });
1177
+ }
1178
+
1179
+ if (is_error) {
1180
+ /** @type {import('types/page').ErrorLoadInput} */ (load_input).status = status;
1181
+ /** @type {import('types/page').ErrorLoadInput} */ (load_input).error = error;
1182
+ }
1183
+
1184
+ loaded = await module.load.call(null, load_input);
1185
+ } else {
1186
+ loaded = {};
1187
+ }
1188
+
1189
+ // if leaf node (i.e. page component) has a load function
1190
+ // that returns nothing, we fall through to the next one
1191
+ if (!loaded && is_leaf && !is_error) return;
1192
+
1193
+ if (!loaded) {
1194
+ throw new Error(`${node.entry} - load must return a value except for page fall through`);
1195
+ }
1196
+
1197
+ return {
1198
+ node,
1199
+ loaded: normalize(loaded),
1200
+ stuff: loaded.stuff || stuff,
1201
+ fetched,
1202
+ set_cookie_headers,
1203
+ uses_credentials
1204
+ };
1205
+ }
1206
+
1207
+ /**
1208
+ * @typedef {import('./types.js').Loaded} Loaded
1209
+ * @typedef {import('types/internal').SSRNode} SSRNode
1210
+ * @typedef {import('types/internal').SSRRenderOptions} SSRRenderOptions
1211
+ * @typedef {import('types/internal').SSRRenderState} SSRRenderState
1212
+ */
1213
+
1214
+ /**
1215
+ * @param {{
1216
+ * request: import('types/hooks').ServerRequest;
1217
+ * options: SSRRenderOptions;
1218
+ * state: SSRRenderState;
1219
+ * $session: any;
1220
+ * status: number;
1221
+ * error: Error;
1222
+ * }} opts
1223
+ */
1224
+ async function respond_with_error({ request, options, state, $session, status, error }) {
1225
+ try {
1226
+ const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
1227
+ const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
1228
+
1229
+ /** @type {Record<string, string>} */
1230
+ const params = {}; // error page has no params
1231
+
1232
+ // error pages don't fall through, so we know it's not undefined
1233
+ const loaded = /** @type {Loaded} */ (
1234
+ await load_node({
1235
+ request,
1236
+ options,
1237
+ state,
1238
+ route: null,
1239
+ url: request.url, // TODO this is redundant, no?
1240
+ params,
1241
+ node: default_layout,
1242
+ $session,
1243
+ stuff: {},
1244
+ prerender_enabled: is_prerender_enabled(options, default_error, state),
1245
+ is_leaf: false,
1246
+ is_error: false
1247
+ })
1248
+ );
1249
+
1250
+ const branch = [
1251
+ loaded,
1252
+ /** @type {Loaded} */ (
1253
+ await load_node({
1254
+ request,
1255
+ options,
1256
+ state,
1257
+ route: null,
1258
+ url: request.url,
1259
+ params,
1260
+ node: default_error,
1261
+ $session,
1262
+ stuff: loaded ? loaded.stuff : {},
1263
+ prerender_enabled: is_prerender_enabled(options, default_error, state),
1264
+ is_leaf: false,
1265
+ is_error: true,
1266
+ status,
1267
+ error
1268
+ })
1269
+ )
1270
+ ];
1271
+
1272
+ return await render_response({
1273
+ options,
1274
+ $session,
1275
+ page_config: {
1276
+ hydrate: options.hydrate,
1277
+ router: options.router,
1278
+ ssr: options.ssr
1279
+ },
1280
+ status,
1281
+ error,
1282
+ branch,
1283
+ url: request.url,
1284
+ params
1285
+ });
1286
+ } catch (err) {
1287
+ const error = coalesce_to_error(err);
1288
+
1289
+ options.handle_error(error, request);
1290
+
1291
+ return {
1292
+ status: 500,
1293
+ headers: {},
1294
+ body: error.stack
1295
+ };
1296
+ }
1297
+ }
1298
+
1299
+ /**
1300
+ * @param {SSRRenderOptions} options
1301
+ * @param {SSRNode} node
1302
+ * @param {SSRRenderState} state
1303
+ */
1304
+ function is_prerender_enabled(options, node, state) {
1305
+ return (
1306
+ options.prerender && (!!node.module.prerender || (!!state.prerender && state.prerender.all))
1307
+ );
1308
+ }
1309
+
1310
+ /**
1311
+ * @typedef {import('./types.js').Loaded} Loaded
1312
+ * @typedef {import('types/hooks').ServerResponse} ServerResponse
1313
+ * @typedef {import('types/internal').SSRNode} SSRNode
1314
+ * @typedef {import('types/internal').SSRRenderOptions} SSRRenderOptions
1315
+ * @typedef {import('types/internal').SSRRenderState} SSRRenderState
1316
+ */
1317
+
1318
+ /**
1319
+ * @param {{
1320
+ * request: import('types/hooks').ServerRequest;
1321
+ * options: SSRRenderOptions;
1322
+ * state: SSRRenderState;
1323
+ * $session: any;
1324
+ * route: import('types/internal').SSRPage;
1325
+ * params: Record<string, string>;
1326
+ * }} opts
1327
+ * @returns {Promise<ServerResponse | undefined>}
1328
+ */
1329
+ async function respond$1(opts) {
1330
+ const { request, options, state, $session, route } = opts;
1331
+
1332
+ /** @type {Array<SSRNode | undefined>} */
1333
+ let nodes;
1334
+
1335
+ try {
1336
+ nodes = await Promise.all(
1337
+ route.a.map((n) => options.manifest._.nodes[n] && options.manifest._.nodes[n]())
1338
+ );
1339
+ } catch (err) {
1340
+ const error = coalesce_to_error(err);
1341
+
1342
+ options.handle_error(error, request);
1343
+
1344
+ return await respond_with_error({
1345
+ request,
1346
+ options,
1347
+ state,
1348
+ $session,
1349
+ status: 500,
1350
+ error
1351
+ });
1352
+ }
1353
+
1354
+ // the leaf node will be present. only layouts may be undefined
1355
+ const leaf = /** @type {SSRNode} */ (nodes[nodes.length - 1]).module;
1356
+
1357
+ let page_config = get_page_config(leaf, options);
1358
+
1359
+ if (!leaf.prerender && state.prerender && !state.prerender.all) {
1360
+ // if the page has `export const prerender = true`, continue,
1361
+ // otherwise bail out at this point
1362
+ return {
1363
+ status: 204,
1364
+ headers: {}
1365
+ };
1366
+ }
1367
+
1368
+ /** @type {Array<Loaded>} */
1369
+ let branch = [];
1370
+
1371
+ /** @type {number} */
1372
+ let status = 200;
1373
+
1374
+ /** @type {Error|undefined} */
1375
+ let error;
1376
+
1377
+ /** @type {string[]} */
1378
+ let set_cookie_headers = [];
1379
+
1380
+ ssr: if (page_config.ssr) {
1381
+ let stuff = {};
1382
+
1383
+ for (let i = 0; i < nodes.length; i += 1) {
1384
+ const node = nodes[i];
1385
+
1386
+ /** @type {Loaded | undefined} */
1387
+ let loaded;
1388
+
1389
+ if (node) {
1390
+ try {
1391
+ loaded = await load_node({
1392
+ ...opts,
1393
+ url: request.url,
1394
+ node,
1395
+ stuff,
1396
+ prerender_enabled: is_prerender_enabled(options, node, state),
1397
+ is_leaf: i === nodes.length - 1,
1398
+ is_error: false
1399
+ });
1400
+
1401
+ if (!loaded) return;
1402
+
1403
+ set_cookie_headers = set_cookie_headers.concat(loaded.set_cookie_headers);
1404
+
1405
+ if (loaded.loaded.redirect) {
1406
+ return with_cookies(
1407
+ {
1408
+ status: loaded.loaded.status,
1409
+ headers: {
1410
+ location: encodeURI(loaded.loaded.redirect)
1411
+ }
1412
+ },
1413
+ set_cookie_headers
1414
+ );
1415
+ }
1416
+
1417
+ if (loaded.loaded.error) {
1418
+ ({ status, error } = loaded.loaded);
1419
+ }
1420
+ } catch (err) {
1421
+ const e = coalesce_to_error(err);
1422
+
1423
+ options.handle_error(e, request);
1424
+
1425
+ status = 500;
1426
+ error = e;
1427
+ }
1428
+
1429
+ if (loaded && !error) {
1430
+ branch.push(loaded);
1431
+ }
1432
+
1433
+ if (error) {
1434
+ while (i--) {
1435
+ if (route.b[i]) {
1436
+ const error_node = await options.manifest._.nodes[route.b[i]]();
1437
+
1438
+ /** @type {Loaded} */
1439
+ let node_loaded;
1440
+ let j = i;
1441
+ while (!(node_loaded = branch[j])) {
1442
+ j -= 1;
1443
+ }
1444
+
1445
+ try {
1446
+ // there's no fallthough on an error page, so we know it's not undefined
1447
+ const error_loaded = /** @type {import('./types').Loaded} */ (
1448
+ await load_node({
1449
+ ...opts,
1450
+ url: request.url,
1451
+ node: error_node,
1452
+ stuff: node_loaded.stuff,
1453
+ prerender_enabled: is_prerender_enabled(options, error_node, state),
1454
+ is_leaf: false,
1455
+ is_error: true,
1456
+ status,
1457
+ error
1458
+ })
1459
+ );
1460
+
1461
+ if (error_loaded.loaded.error) {
1462
+ continue;
1463
+ }
1464
+
1465
+ page_config = get_page_config(error_node.module, options);
1466
+ branch = branch.slice(0, j + 1).concat(error_loaded);
1467
+ break ssr;
1468
+ } catch (err) {
1469
+ const e = coalesce_to_error(err);
1470
+
1471
+ options.handle_error(e, request);
1472
+
1473
+ continue;
1474
+ }
1475
+ }
1476
+ }
1477
+
1478
+ // TODO backtrack until we find an __error.svelte component
1479
+ // that we can use as the leaf node
1480
+ // for now just return regular error page
1481
+ return with_cookies(
1482
+ await respond_with_error({
1483
+ request,
1484
+ options,
1485
+ state,
1486
+ $session,
1487
+ status,
1488
+ error
1489
+ }),
1490
+ set_cookie_headers
1491
+ );
1492
+ }
1493
+ }
1494
+
1495
+ if (loaded && loaded.loaded.stuff) {
1496
+ stuff = {
1497
+ ...stuff,
1498
+ ...loaded.loaded.stuff
1499
+ };
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ try {
1505
+ return with_cookies(
1506
+ await render_response({
1507
+ ...opts,
1508
+ url: request.url,
1509
+ page_config,
1510
+ status,
1511
+ error,
1512
+ branch: branch.filter(Boolean)
1513
+ }),
1514
+ set_cookie_headers
1515
+ );
1516
+ } catch (err) {
1517
+ const error = coalesce_to_error(err);
1518
+
1519
+ options.handle_error(error, request);
1520
+
1521
+ return with_cookies(
1522
+ await respond_with_error({
1523
+ ...opts,
1524
+ status: 500,
1525
+ error
1526
+ }),
1527
+ set_cookie_headers
1528
+ );
1529
+ }
1530
+ }
1531
+
1532
+ /**
1533
+ * @param {import('types/internal').SSRComponent} leaf
1534
+ * @param {SSRRenderOptions} options
1535
+ */
1536
+ function get_page_config(leaf, options) {
1537
+ return {
1538
+ ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr,
1539
+ router: 'router' in leaf ? !!leaf.router : options.router,
1540
+ hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate
1541
+ };
1542
+ }
1543
+
1544
+ /**
1545
+ * @param {ServerResponse} response
1546
+ * @param {string[]} set_cookie_headers
1547
+ */
1548
+ function with_cookies(response, set_cookie_headers) {
1549
+ if (set_cookie_headers.length) {
1550
+ response.headers['set-cookie'] = set_cookie_headers;
1551
+ }
1552
+ return response;
1553
+ }
1554
+
1555
+ /**
1556
+ * @param {import('types/hooks').ServerRequest} request
1557
+ * @param {import('types/internal').SSRPage} route
1558
+ * @param {RegExpExecArray} match
1559
+ * @param {import('types/internal').SSRRenderOptions} options
1560
+ * @param {import('types/internal').SSRRenderState} state
1561
+ * @returns {Promise<import('types/hooks').ServerResponse | undefined>}
1562
+ */
1563
+ async function render_page(request, route, match, options, state) {
1564
+ if (state.initiator === route) {
1565
+ // infinite request cycle detected
1566
+ return {
1567
+ status: 404,
1568
+ headers: {},
1569
+ body: `Not found: ${request.url.pathname}`
1570
+ };
1571
+ }
1572
+
1573
+ const params = route.params ? decode_params(route.params(match)) : {};
1574
+
1575
+ const $session = await options.hooks.getSession(request);
1576
+
1577
+ const response = await respond$1({
1578
+ request,
1579
+ options,
1580
+ state,
1581
+ $session,
1582
+ route,
1583
+ params
1584
+ });
1585
+
1586
+ if (response) {
1587
+ return response;
1588
+ }
1589
+
1590
+ if (state.fetched) {
1591
+ // we came here because of a bad request in a `load` function.
1592
+ // rather than render the error page — which could lead to an
1593
+ // infinite loop, if the `load` belonged to the root layout,
1594
+ // we respond with a bare-bones 500
1595
+ return {
1596
+ status: 500,
1597
+ headers: {},
1598
+ body: `Bad request in load function: failed to fetch ${state.fetched}`
1599
+ };
1600
+ }
1601
+ }
1602
+
1603
+ function read_only_form_data() {
1604
+ /** @type {Map<string, string[]>} */
1605
+ const map = new Map();
1606
+
1607
+ return {
1608
+ /**
1609
+ * @param {string} key
1610
+ * @param {string} value
1611
+ */
1612
+ append(key, value) {
1613
+ if (map.has(key)) {
1614
+ (map.get(key) || []).push(value);
1615
+ } else {
1616
+ map.set(key, [value]);
1617
+ }
1618
+ },
1619
+
1620
+ data: new ReadOnlyFormData(map)
1621
+ };
1622
+ }
1623
+
1624
+ class ReadOnlyFormData {
1625
+ /** @type {Map<string, string[]>} */
1626
+ #map;
1627
+
1628
+ /** @param {Map<string, string[]>} map */
1629
+ constructor(map) {
1630
+ this.#map = map;
1631
+ }
1632
+
1633
+ /** @param {string} key */
1634
+ get(key) {
1635
+ const value = this.#map.get(key);
1636
+ return value && value[0];
1637
+ }
1638
+
1639
+ /** @param {string} key */
1640
+ getAll(key) {
1641
+ return this.#map.get(key);
1642
+ }
1643
+
1644
+ /** @param {string} key */
1645
+ has(key) {
1646
+ return this.#map.has(key);
1647
+ }
1648
+
1649
+ *[Symbol.iterator]() {
1650
+ for (const [key, value] of this.#map) {
1651
+ for (let i = 0; i < value.length; i += 1) {
1652
+ yield [key, value[i]];
1653
+ }
1654
+ }
1655
+ }
1656
+
1657
+ *entries() {
1658
+ for (const [key, value] of this.#map) {
1659
+ for (let i = 0; i < value.length; i += 1) {
1660
+ yield [key, value[i]];
1661
+ }
1662
+ }
1663
+ }
1664
+
1665
+ *keys() {
1666
+ for (const [key] of this.#map) yield key;
1667
+ }
1668
+
1669
+ *values() {
1670
+ for (const [, value] of this.#map) {
1671
+ for (let i = 0; i < value.length; i += 1) {
1672
+ yield value[i];
1673
+ }
1674
+ }
1675
+ }
1676
+ }
1677
+
1678
+ /**
1679
+ * @param {import('types/app').RawBody} raw
1680
+ * @param {import('types/helper').RequestHeaders} headers
1681
+ */
1682
+ function parse_body(raw, headers) {
1683
+ if (!raw) return raw;
1684
+
1685
+ const content_type = headers['content-type'];
1686
+ const [type, ...directives] = content_type ? content_type.split(/;\s*/) : [];
1687
+
1688
+ const text = () => new TextDecoder(headers['content-encoding'] || 'utf-8').decode(raw);
1689
+
1690
+ switch (type) {
1691
+ case 'text/plain':
1692
+ return text();
1693
+
1694
+ case 'application/json':
1695
+ return JSON.parse(text());
1696
+
1697
+ case 'application/x-www-form-urlencoded':
1698
+ return get_urlencoded(text());
1699
+
1700
+ case 'multipart/form-data': {
1701
+ const boundary = directives.find((directive) => directive.startsWith('boundary='));
1702
+ if (!boundary) throw new Error('Missing boundary');
1703
+ return get_multipart(text(), boundary.slice('boundary='.length));
1704
+ }
1705
+ default:
1706
+ return raw;
1707
+ }
1708
+ }
1709
+
1710
+ /** @param {string} text */
1711
+ function get_urlencoded(text) {
1712
+ const { data, append } = read_only_form_data();
1713
+
1714
+ text
1715
+ .replace(/\+/g, ' ')
1716
+ .split('&')
1717
+ .forEach((str) => {
1718
+ const [key, value] = str.split('=');
1719
+ append(decodeURIComponent(key), decodeURIComponent(value));
1720
+ });
1721
+
1722
+ return data;
1723
+ }
1724
+
1725
+ /**
1726
+ * @param {string} text
1727
+ * @param {string} boundary
1728
+ */
1729
+ function get_multipart(text, boundary) {
1730
+ const parts = text.split(`--${boundary}`);
1731
+
1732
+ if (parts[0] !== '' || parts[parts.length - 1].trim() !== '--') {
1733
+ throw new Error('Malformed form data');
1734
+ }
1735
+
1736
+ const { data, append } = read_only_form_data();
1737
+
1738
+ parts.slice(1, -1).forEach((part) => {
1739
+ const match = /\s*([\s\S]+?)\r\n\r\n([\s\S]*)\s*/.exec(part);
1740
+ if (!match) {
1741
+ throw new Error('Malformed form data');
1742
+ }
1743
+ const raw_headers = match[1];
1744
+ const body = match[2].trim();
1745
+
1746
+ let key;
1747
+
1748
+ /** @type {Record<string, string>} */
1749
+ const headers = {};
1750
+ raw_headers.split('\r\n').forEach((str) => {
1751
+ const [raw_header, ...raw_directives] = str.split('; ');
1752
+ let [name, value] = raw_header.split(': ');
1753
+
1754
+ name = name.toLowerCase();
1755
+ headers[name] = value;
1756
+
1757
+ /** @type {Record<string, string>} */
1758
+ const directives = {};
1759
+ raw_directives.forEach((raw_directive) => {
1760
+ const [name, value] = raw_directive.split('=');
1761
+ directives[name] = JSON.parse(value); // TODO is this right?
1762
+ });
1763
+
1764
+ if (name === 'content-disposition') {
1765
+ if (value !== 'form-data') throw new Error('Malformed form data');
1766
+
1767
+ if (directives.filename) {
1768
+ // TODO we probably don't want to do this automatically
1769
+ throw new Error('File upload is not yet implemented');
1770
+ }
1771
+
1772
+ if (directives.name) {
1773
+ key = directives.name;
1774
+ }
1775
+ }
1776
+ });
1777
+
1778
+ if (!key) throw new Error('Malformed form data');
1779
+
1780
+ append(key, body);
1781
+ });
1782
+
1783
+ return data;
1784
+ }
1785
+
1786
+ /** @type {import('@sveltejs/kit/ssr').Respond} */
1787
+ async function respond(incoming, options, state = {}) {
1788
+ if (incoming.url.pathname !== '/' && options.trailing_slash !== 'ignore') {
1789
+ const has_trailing_slash = incoming.url.pathname.endsWith('/');
1790
+
1791
+ if (
1792
+ (has_trailing_slash && options.trailing_slash === 'never') ||
1793
+ (!has_trailing_slash &&
1794
+ options.trailing_slash === 'always' &&
1795
+ !(incoming.url.pathname.split('/').pop() || '').includes('.'))
1796
+ ) {
1797
+ incoming.url.pathname = has_trailing_slash
1798
+ ? incoming.url.pathname.slice(0, -1)
1799
+ : incoming.url.pathname + '/';
1800
+
1801
+ if (incoming.url.search === '?') incoming.url.search = '';
1802
+
1803
+ return {
1804
+ status: 301,
1805
+ headers: {
1806
+ location: incoming.url.pathname + incoming.url.search
1807
+ }
1808
+ };
1809
+ }
1810
+ }
1811
+
1812
+ const headers = lowercase_keys(incoming.headers);
1813
+ const request = {
1814
+ ...incoming,
1815
+ headers,
1816
+ body: parse_body(incoming.rawBody, headers),
1817
+ params: {},
1818
+ locals: {}
1819
+ };
1820
+
1821
+ // TODO remove this for 1.0
1822
+ /**
1823
+ * @param {string} property
1824
+ * @param {string} replacement
1825
+ */
1826
+ const print_error = (property, replacement) => {
1827
+ Object.defineProperty(request, property, {
1828
+ get: () => {
1829
+ throw new Error(`request.${property} has been replaced by request.url.${replacement}`);
1830
+ }
1831
+ });
1832
+ };
1833
+
1834
+ print_error('origin', 'origin');
1835
+ print_error('path', 'pathname');
1836
+ print_error('query', 'searchParams');
1837
+
1838
+ try {
1839
+ return await options.hooks.handle({
1840
+ request,
1841
+ resolve: async (request) => {
1842
+ if (state.prerender && state.prerender.fallback) {
1843
+ return await render_response({
1844
+ url: request.url,
1845
+ params: request.params,
1846
+ options,
1847
+ $session: await options.hooks.getSession(request),
1848
+ page_config: { ssr: false, router: true, hydrate: true },
1849
+ status: 200,
1850
+ branch: []
1851
+ });
1852
+ }
1853
+
1854
+ const decoded = decodeURI(request.url.pathname).replace(options.paths.base, '');
1855
+
1856
+ for (const route of options.manifest._.routes) {
1857
+ const match = route.pattern.exec(decoded);
1858
+ if (!match) continue;
1859
+
1860
+ const response =
1861
+ route.type === 'endpoint'
1862
+ ? await render_endpoint(request, route, match)
1863
+ : await render_page(request, route, match, options, state);
1864
+
1865
+ if (response) {
1866
+ // inject ETags for 200 responses
1867
+ if (response.status === 200) {
1868
+ const cache_control = get_single_valued_header(response.headers, 'cache-control');
1869
+ if (!cache_control || !/(no-store|immutable)/.test(cache_control)) {
1870
+ let if_none_match_value = request.headers['if-none-match'];
1871
+ // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
1872
+ if (if_none_match_value?.startsWith('W/"')) {
1873
+ if_none_match_value = if_none_match_value.substring(2);
1874
+ }
1875
+
1876
+ const etag = `"${hash(response.body || '')}"`;
1877
+
1878
+ if (if_none_match_value === etag) {
1879
+ return {
1880
+ status: 304,
1881
+ headers: {}
1882
+ };
1883
+ }
1884
+
1885
+ response.headers['etag'] = etag;
1886
+ }
1887
+ }
1888
+
1889
+ return response;
1890
+ }
1891
+ }
1892
+
1893
+ // if this request came direct from the user, rather than
1894
+ // via a `fetch` in a `load`, render a 404 page
1895
+ if (!state.initiator) {
1896
+ const $session = await options.hooks.getSession(request);
1897
+ return await respond_with_error({
1898
+ request,
1899
+ options,
1900
+ state,
1901
+ $session,
1902
+ status: 404,
1903
+ error: new Error(`Not found: ${request.url.pathname}`)
1904
+ });
1905
+ }
1906
+ }
1907
+ });
1908
+ } catch (/** @type {unknown} */ e) {
1909
+ const error = coalesce_to_error(e);
1910
+
1911
+ options.handle_error(error, request);
1912
+
1913
+ try {
1914
+ const $session = await options.hooks.getSession(request);
1915
+ return await respond_with_error({
1916
+ request,
1917
+ options,
1918
+ state,
1919
+ $session,
1920
+ status: 500,
1921
+ error
1922
+ });
1923
+ } catch (/** @type {unknown} */ e) {
1924
+ const error = coalesce_to_error(e);
1925
+
1926
+ return {
1927
+ status: 500,
1928
+ headers: {},
1929
+ body: options.dev ? error.stack : error.message
1930
+ };
1931
+ }
1932
+ }
1933
+ }
1934
+
1935
+ export { respond };