@sveltejs/kit 1.0.0-next.19 → 1.0.0-next.193

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