@sveltejs/kit 1.0.0-next.312 → 1.0.0-next.316

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.
@@ -27,10 +27,7 @@ function normalize(loaded) {
27
27
  const status = loaded.status;
28
28
 
29
29
  if (!loaded.error && has_error_status) {
30
- return {
31
- status: status || 500,
32
- error: new Error()
33
- };
30
+ return { status: status || 500, error: new Error() };
34
31
  }
35
32
 
36
33
  const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
@@ -70,6 +67,18 @@ function normalize(loaded) {
70
67
  }
71
68
  }
72
69
 
70
+ if (loaded.dependencies) {
71
+ if (
72
+ !Array.isArray(loaded.dependencies) ||
73
+ loaded.dependencies.some((dep) => typeof dep !== 'string')
74
+ ) {
75
+ return {
76
+ status: 500,
77
+ error: new Error('"dependencies" property returned from load() must be of type string[]')
78
+ };
79
+ }
80
+ }
81
+
73
82
  // TODO remove before 1.0
74
83
  if (/** @type {any} */ (loaded).context) {
75
84
  throw new Error(
@@ -389,6 +398,8 @@ const routes = parse(components, dictionary, matchers);
389
398
  const default_layout = components[0]();
390
399
  const default_error = components[1]();
391
400
 
401
+ const root_stuff = {};
402
+
392
403
  // We track the scroll position associated with each history entry in sessionStorage,
393
404
  // rather than on history.state itself, because when navigation is driven by
394
405
  // popstate it's too late to update the scroll position associated with the
@@ -421,8 +432,8 @@ function create_client({ target, session, base, trailing_slash }) {
421
432
  /** @type {Map<string, import('./types').NavigationResult>} */
422
433
  const cache = new Map();
423
434
 
424
- /** @type {Set<string>} */
425
- const invalidated = new Set();
435
+ /** @type {Array<((href: string) => boolean)>} */
436
+ const invalidated = [];
426
437
 
427
438
  const stores = {
428
439
  url: notifiable_store({}),
@@ -448,10 +459,12 @@ function create_client({ target, session, base, trailing_slash }) {
448
459
 
449
460
  /** @type {import('./types').NavigationState} */
450
461
  let current = {
451
- // @ts-ignore - we need the initial value to be null
452
- url: null,
462
+ branch: [],
463
+ error: null,
453
464
  session_id: 0,
454
- branch: []
465
+ stuff: root_stuff,
466
+ // @ts-ignore - we need the initial value to be null
467
+ url: null
455
468
  };
456
469
 
457
470
  let started = false;
@@ -485,17 +498,28 @@ function create_client({ target, session, base, trailing_slash }) {
485
498
  let router_enabled = true;
486
499
 
487
500
  // keeping track of the history index in order to prevent popstate navigation events if needed
488
- let current_history_index = history.state?.[INDEX_KEY] ?? 0;
501
+ let current_history_index = history.state?.[INDEX_KEY];
502
+
503
+ if (!current_history_index) {
504
+ // we use Date.now() as an offset so that cross-document navigations
505
+ // within the app don't result in data loss
506
+ current_history_index = Date.now();
489
507
 
490
- if (current_history_index === 0) {
491
508
  // create initial history entry, so we can return here
492
- history.replaceState({ ...history.state, [INDEX_KEY]: 0 }, '', location.href);
509
+ history.replaceState(
510
+ { ...history.state, [INDEX_KEY]: current_history_index },
511
+ '',
512
+ location.href
513
+ );
493
514
  }
494
515
 
495
516
  // if we reload the page, or Cmd-Shift-T back to it,
496
517
  // recover scroll position
497
518
  const scroll = scroll_positions[current_history_index];
498
- if (scroll) scrollTo(scroll.x, scroll.y);
519
+ if (scroll) {
520
+ history.scrollRestoration = 'manual';
521
+ scrollTo(scroll.x, scroll.y);
522
+ }
499
523
 
500
524
  let hash_navigating = false;
501
525
 
@@ -590,7 +614,7 @@ function create_client({ target, session, base, trailing_slash }) {
590
614
  // abort if user navigated during update
591
615
  if (token !== current_token) return;
592
616
 
593
- invalidated.clear();
617
+ invalidated.length = 0;
594
618
 
595
619
  if (navigation_result.redirect) {
596
620
  if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
@@ -699,7 +723,7 @@ function create_client({ target, session, base, trailing_slash }) {
699
723
  function initialize(result) {
700
724
  current = result.state;
701
725
 
702
- const style = document.querySelector('style[data-svelte]');
726
+ const style = document.querySelector('style[data-sveltekit]');
703
727
  if (style) style.remove();
704
728
 
705
729
  page = result.props.page;
@@ -726,7 +750,7 @@ function create_client({ target, session, base, trailing_slash }) {
726
750
  * stuff: Record<string, any>;
727
751
  * branch: Array<import('./types').BranchNode | undefined>;
728
752
  * status: number;
729
- * error?: Error;
753
+ * error: Error | null;
730
754
  * routeId: string | null;
731
755
  * }} opts
732
756
  */
@@ -749,6 +773,8 @@ function create_client({ target, session, base, trailing_slash }) {
749
773
  url,
750
774
  params,
751
775
  branch,
776
+ error,
777
+ stuff,
752
778
  session_id
753
779
  },
754
780
  props: {
@@ -761,7 +787,13 @@ function create_client({ target, session, base, trailing_slash }) {
761
787
  result.props[`props_${i}`] = loaded ? await loaded.props : null;
762
788
  }
763
789
 
764
- if (!current.url || url.href !== current.url.href) {
790
+ const page_changed =
791
+ !current.url ||
792
+ url.href !== current.url.href ||
793
+ current.error !== error ||
794
+ current.stuff !== stuff;
795
+
796
+ if (page_changed) {
765
797
  result.props.page = { error, params, routeId, status, stuff, url };
766
798
 
767
799
  // TODO remove this for 1.0
@@ -839,6 +871,12 @@ function create_client({ target, session, base, trailing_slash }) {
839
871
  stuff
840
872
  };
841
873
 
874
+ /** @param dep {string} */
875
+ function add_dependency(dep) {
876
+ const { href } = new URL(dep, url);
877
+ node.uses.dependencies.add(href);
878
+ }
879
+
842
880
  if (props) {
843
881
  // shadow endpoint props means we need to mark this URL as a dependency of itself
844
882
  node.uses.dependencies.add(url.href);
@@ -859,7 +897,7 @@ function create_client({ target, session, base, trailing_slash }) {
859
897
  const session = $session;
860
898
 
861
899
  if (module.load) {
862
- /** @type {import('types').LoadInput | import('types').ErrorLoadInput} */
900
+ /** @type {import('types').LoadInput} */
863
901
  const load_input = {
864
902
  routeId,
865
903
  params: uses_params,
@@ -878,11 +916,12 @@ function create_client({ target, session, base, trailing_slash }) {
878
916
  },
879
917
  fetch(resource, info) {
880
918
  const requested = typeof resource === 'string' ? resource : resource.url;
881
- const { href } = new URL(requested, url);
882
- node.uses.dependencies.add(href);
919
+ add_dependency(requested);
883
920
 
884
921
  return started ? fetch(resource, info) : initial_fetch(resource, info);
885
- }
922
+ },
923
+ status: status ?? null,
924
+ error: error ?? null
886
925
  };
887
926
 
888
927
  if (import.meta.env.DEV) {
@@ -894,11 +933,6 @@ function create_client({ target, session, base, trailing_slash }) {
894
933
  });
895
934
  }
896
935
 
897
- if (error) {
898
- /** @type {import('types').ErrorLoadInput} */ (load_input).status = status;
899
- /** @type {import('types').ErrorLoadInput} */ (load_input).error = error;
900
- }
901
-
902
936
  const loaded = await module.load.call(null, load_input);
903
937
 
904
938
  if (!loaded) {
@@ -907,6 +941,9 @@ function create_client({ target, session, base, trailing_slash }) {
907
941
 
908
942
  node.loaded = normalize(loaded);
909
943
  if (node.loaded.stuff) node.stuff = node.loaded.stuff;
944
+ if (node.loaded.dependencies) {
945
+ node.loaded.dependencies.forEach(add_dependency);
946
+ }
910
947
  } else if (props) {
911
948
  node.loaded = normalize({ props });
912
949
  }
@@ -940,14 +977,14 @@ function create_client({ target, session, base, trailing_slash }) {
940
977
  let branch = [];
941
978
 
942
979
  /** @type {Record<string, any>} */
943
- let stuff = {};
980
+ let stuff = root_stuff;
944
981
  let stuff_changed = false;
945
982
 
946
983
  /** @type {number | undefined} */
947
984
  let status = 200;
948
985
 
949
- /** @type {Error | undefined} */
950
- let error;
986
+ /** @type {Error | null} */
987
+ let error = null;
951
988
 
952
989
  // preload modules
953
990
  a.forEach((loader) => loader());
@@ -968,7 +1005,7 @@ function create_client({ target, session, base, trailing_slash }) {
968
1005
  (changed.url && previous.uses.url) ||
969
1006
  changed.params.some((param) => previous.uses.params.has(param)) ||
970
1007
  (changed.session && previous.uses.session) ||
971
- Array.from(previous.uses.dependencies).some((dep) => invalidated.has(dep)) ||
1008
+ Array.from(previous.uses.dependencies).some((dep) => invalidated.some((fn) => fn(dep))) ||
972
1009
  (stuff_changed && previous.uses.stuff);
973
1010
 
974
1011
  if (changed_since_last_render) {
@@ -1309,9 +1346,12 @@ function create_client({ target, session, base, trailing_slash }) {
1309
1346
  goto: (href, opts = {}) => goto(href, opts, []),
1310
1347
 
1311
1348
  invalidate: (resource) => {
1312
- const { href } = new URL(resource, location.href);
1313
-
1314
- invalidated.add(href);
1349
+ if (typeof resource === 'function') {
1350
+ invalidated.push(resource);
1351
+ } else {
1352
+ const { href } = new URL(resource, location.href);
1353
+ invalidated.push((dep) => dep === href);
1354
+ }
1315
1355
 
1316
1356
  if (!invalidating) {
1317
1357
  invalidating = Promise.resolve().then(async () => {
@@ -1,5 +1,5 @@
1
1
  <script context="module">
2
- /** @type {import('@sveltejs/kit').ErrorLoad} */
2
+ /** @type {import('@sveltejs/kit').Load} */
3
3
  export function load({ error, status }) {
4
4
  return {
5
5
  props: { error, status }
@@ -143,14 +143,28 @@ async function render_endpoint(event, mod) {
143
143
  }
144
144
 
145
145
  if (!handler) {
146
+ const allowed = [];
147
+
148
+ for (const method in ['get', 'post', 'put', 'patch']) {
149
+ if (mod[method]) allowed.push(method.toUpperCase());
150
+ }
151
+
152
+ if (mod.del) allowed.push('DELETE');
153
+ if (mod.get || mod.head) allowed.push('HEAD');
154
+
146
155
  return event.request.headers.get('x-sveltekit-load')
147
156
  ? // TODO would be nice to avoid these requests altogether,
148
157
  // by noting whether or not page endpoints export `get`
149
158
  new Response(undefined, {
150
159
  status: 204
151
160
  })
152
- : new Response('Method not allowed', {
153
- status: 405
161
+ : new Response(`${event.request.method} method not allowed`, {
162
+ status: 405,
163
+ headers: {
164
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
165
+ // "The server must generate an Allow header field in a 405 status code response"
166
+ allow: allowed.join(', ')
167
+ }
154
168
  });
155
169
  }
156
170
 
@@ -639,7 +653,7 @@ function sha256(data) {
639
653
  if (!key[0]) precompute();
640
654
 
641
655
  const out = init.slice(0);
642
- const array = encode(data);
656
+ const array = encode$1(data);
643
657
 
644
658
  for (let i = 0; i < array.length; i += 16) {
645
659
  const w = array.subarray(i, i + 16);
@@ -789,7 +803,7 @@ function reverse_endianness(bytes) {
789
803
  }
790
804
 
791
805
  /** @param {string} str */
792
- function encode(str) {
806
+ function encode$1(str) {
793
807
  const encoded = encoder.encode(str);
794
808
  const length = encoded.length * 8;
795
809
 
@@ -1276,7 +1290,7 @@ async function render_response({
1276
1290
  } else {
1277
1291
  if (inlined_style) {
1278
1292
  const attributes = [];
1279
- if (options.dev) attributes.push(' data-svelte');
1293
+ if (options.dev) attributes.push(' data-sveltekit');
1280
1294
  if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
1281
1295
 
1282
1296
  csp.add_style(inlined_style);
@@ -1424,6 +1438,478 @@ function serialize_error(error) {
1424
1438
  return serialized;
1425
1439
  }
1426
1440
 
1441
+ /*!
1442
+ * cookie
1443
+ * Copyright(c) 2012-2014 Roman Shtylman
1444
+ * Copyright(c) 2015 Douglas Christopher Wilson
1445
+ * MIT Licensed
1446
+ */
1447
+
1448
+ /**
1449
+ * Module exports.
1450
+ * @public
1451
+ */
1452
+
1453
+ var parse_1 = parse$1;
1454
+ var serialize_1 = serialize;
1455
+
1456
+ /**
1457
+ * Module variables.
1458
+ * @private
1459
+ */
1460
+
1461
+ var __toString = Object.prototype.toString;
1462
+
1463
+ /**
1464
+ * RegExp to match field-content in RFC 7230 sec 3.2
1465
+ *
1466
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
1467
+ * field-vchar = VCHAR / obs-text
1468
+ * obs-text = %x80-FF
1469
+ */
1470
+
1471
+ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
1472
+
1473
+ /**
1474
+ * Parse a cookie header.
1475
+ *
1476
+ * Parse the given cookie header string into an object
1477
+ * The object has the various cookies as keys(names) => values
1478
+ *
1479
+ * @param {string} str
1480
+ * @param {object} [options]
1481
+ * @return {object}
1482
+ * @public
1483
+ */
1484
+
1485
+ function parse$1(str, options) {
1486
+ if (typeof str !== 'string') {
1487
+ throw new TypeError('argument str must be a string');
1488
+ }
1489
+
1490
+ var obj = {};
1491
+ var opt = options || {};
1492
+ var dec = opt.decode || decode;
1493
+
1494
+ var index = 0;
1495
+ while (index < str.length) {
1496
+ var eqIdx = str.indexOf('=', index);
1497
+
1498
+ // no more cookie pairs
1499
+ if (eqIdx === -1) {
1500
+ break
1501
+ }
1502
+
1503
+ var endIdx = str.indexOf(';', index);
1504
+
1505
+ if (endIdx === -1) {
1506
+ endIdx = str.length;
1507
+ } else if (endIdx < eqIdx) {
1508
+ // backtrack on prior semicolon
1509
+ index = str.lastIndexOf(';', eqIdx - 1) + 1;
1510
+ continue
1511
+ }
1512
+
1513
+ var key = str.slice(index, eqIdx).trim();
1514
+
1515
+ // only assign once
1516
+ if (undefined === obj[key]) {
1517
+ var val = str.slice(eqIdx + 1, endIdx).trim();
1518
+
1519
+ // quoted values
1520
+ if (val.charCodeAt(0) === 0x22) {
1521
+ val = val.slice(1, -1);
1522
+ }
1523
+
1524
+ obj[key] = tryDecode(val, dec);
1525
+ }
1526
+
1527
+ index = endIdx + 1;
1528
+ }
1529
+
1530
+ return obj;
1531
+ }
1532
+
1533
+ /**
1534
+ * Serialize data into a cookie header.
1535
+ *
1536
+ * Serialize the a name value pair into a cookie string suitable for
1537
+ * http headers. An optional options object specified cookie parameters.
1538
+ *
1539
+ * serialize('foo', 'bar', { httpOnly: true })
1540
+ * => "foo=bar; httpOnly"
1541
+ *
1542
+ * @param {string} name
1543
+ * @param {string} val
1544
+ * @param {object} [options]
1545
+ * @return {string}
1546
+ * @public
1547
+ */
1548
+
1549
+ function serialize(name, val, options) {
1550
+ var opt = options || {};
1551
+ var enc = opt.encode || encode;
1552
+
1553
+ if (typeof enc !== 'function') {
1554
+ throw new TypeError('option encode is invalid');
1555
+ }
1556
+
1557
+ if (!fieldContentRegExp.test(name)) {
1558
+ throw new TypeError('argument name is invalid');
1559
+ }
1560
+
1561
+ var value = enc(val);
1562
+
1563
+ if (value && !fieldContentRegExp.test(value)) {
1564
+ throw new TypeError('argument val is invalid');
1565
+ }
1566
+
1567
+ var str = name + '=' + value;
1568
+
1569
+ if (null != opt.maxAge) {
1570
+ var maxAge = opt.maxAge - 0;
1571
+
1572
+ if (isNaN(maxAge) || !isFinite(maxAge)) {
1573
+ throw new TypeError('option maxAge is invalid')
1574
+ }
1575
+
1576
+ str += '; Max-Age=' + Math.floor(maxAge);
1577
+ }
1578
+
1579
+ if (opt.domain) {
1580
+ if (!fieldContentRegExp.test(opt.domain)) {
1581
+ throw new TypeError('option domain is invalid');
1582
+ }
1583
+
1584
+ str += '; Domain=' + opt.domain;
1585
+ }
1586
+
1587
+ if (opt.path) {
1588
+ if (!fieldContentRegExp.test(opt.path)) {
1589
+ throw new TypeError('option path is invalid');
1590
+ }
1591
+
1592
+ str += '; Path=' + opt.path;
1593
+ }
1594
+
1595
+ if (opt.expires) {
1596
+ var expires = opt.expires;
1597
+
1598
+ if (!isDate(expires) || isNaN(expires.valueOf())) {
1599
+ throw new TypeError('option expires is invalid');
1600
+ }
1601
+
1602
+ str += '; Expires=' + expires.toUTCString();
1603
+ }
1604
+
1605
+ if (opt.httpOnly) {
1606
+ str += '; HttpOnly';
1607
+ }
1608
+
1609
+ if (opt.secure) {
1610
+ str += '; Secure';
1611
+ }
1612
+
1613
+ if (opt.priority) {
1614
+ var priority = typeof opt.priority === 'string'
1615
+ ? opt.priority.toLowerCase()
1616
+ : opt.priority;
1617
+
1618
+ switch (priority) {
1619
+ case 'low':
1620
+ str += '; Priority=Low';
1621
+ break
1622
+ case 'medium':
1623
+ str += '; Priority=Medium';
1624
+ break
1625
+ case 'high':
1626
+ str += '; Priority=High';
1627
+ break
1628
+ default:
1629
+ throw new TypeError('option priority is invalid')
1630
+ }
1631
+ }
1632
+
1633
+ if (opt.sameSite) {
1634
+ var sameSite = typeof opt.sameSite === 'string'
1635
+ ? opt.sameSite.toLowerCase() : opt.sameSite;
1636
+
1637
+ switch (sameSite) {
1638
+ case true:
1639
+ str += '; SameSite=Strict';
1640
+ break;
1641
+ case 'lax':
1642
+ str += '; SameSite=Lax';
1643
+ break;
1644
+ case 'strict':
1645
+ str += '; SameSite=Strict';
1646
+ break;
1647
+ case 'none':
1648
+ str += '; SameSite=None';
1649
+ break;
1650
+ default:
1651
+ throw new TypeError('option sameSite is invalid');
1652
+ }
1653
+ }
1654
+
1655
+ return str;
1656
+ }
1657
+
1658
+ /**
1659
+ * URL-decode string value. Optimized to skip native call when no %.
1660
+ *
1661
+ * @param {string} str
1662
+ * @returns {string}
1663
+ */
1664
+
1665
+ function decode (str) {
1666
+ return str.indexOf('%') !== -1
1667
+ ? decodeURIComponent(str)
1668
+ : str
1669
+ }
1670
+
1671
+ /**
1672
+ * URL-encode value.
1673
+ *
1674
+ * @param {string} str
1675
+ * @returns {string}
1676
+ */
1677
+
1678
+ function encode (val) {
1679
+ return encodeURIComponent(val)
1680
+ }
1681
+
1682
+ /**
1683
+ * Determine if value is a Date.
1684
+ *
1685
+ * @param {*} val
1686
+ * @private
1687
+ */
1688
+
1689
+ function isDate (val) {
1690
+ return __toString.call(val) === '[object Date]' ||
1691
+ val instanceof Date
1692
+ }
1693
+
1694
+ /**
1695
+ * Try decoding a string using a decoding function.
1696
+ *
1697
+ * @param {string} str
1698
+ * @param {function} decode
1699
+ * @private
1700
+ */
1701
+
1702
+ function tryDecode(str, decode) {
1703
+ try {
1704
+ return decode(str);
1705
+ } catch (e) {
1706
+ return str;
1707
+ }
1708
+ }
1709
+
1710
+ var setCookie = {exports: {}};
1711
+
1712
+ var defaultParseOptions = {
1713
+ decodeValues: true,
1714
+ map: false,
1715
+ silent: false,
1716
+ };
1717
+
1718
+ function isNonEmptyString(str) {
1719
+ return typeof str === "string" && !!str.trim();
1720
+ }
1721
+
1722
+ function parseString(setCookieValue, options) {
1723
+ var parts = setCookieValue.split(";").filter(isNonEmptyString);
1724
+ var nameValue = parts.shift().split("=");
1725
+ var name = nameValue.shift();
1726
+ var value = nameValue.join("="); // everything after the first =, joined by a "=" if there was more than one part
1727
+
1728
+ options = options
1729
+ ? Object.assign({}, defaultParseOptions, options)
1730
+ : defaultParseOptions;
1731
+
1732
+ try {
1733
+ value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
1734
+ } catch (e) {
1735
+ console.error(
1736
+ "set-cookie-parser encountered an error while decoding a cookie with value '" +
1737
+ value +
1738
+ "'. Set options.decodeValues to false to disable this feature.",
1739
+ e
1740
+ );
1741
+ }
1742
+
1743
+ var cookie = {
1744
+ name: name, // grab everything before the first =
1745
+ value: value,
1746
+ };
1747
+
1748
+ parts.forEach(function (part) {
1749
+ var sides = part.split("=");
1750
+ var key = sides.shift().trimLeft().toLowerCase();
1751
+ var value = sides.join("=");
1752
+ if (key === "expires") {
1753
+ cookie.expires = new Date(value);
1754
+ } else if (key === "max-age") {
1755
+ cookie.maxAge = parseInt(value, 10);
1756
+ } else if (key === "secure") {
1757
+ cookie.secure = true;
1758
+ } else if (key === "httponly") {
1759
+ cookie.httpOnly = true;
1760
+ } else if (key === "samesite") {
1761
+ cookie.sameSite = value;
1762
+ } else {
1763
+ cookie[key] = value;
1764
+ }
1765
+ });
1766
+
1767
+ return cookie;
1768
+ }
1769
+
1770
+ function parse(input, options) {
1771
+ options = options
1772
+ ? Object.assign({}, defaultParseOptions, options)
1773
+ : defaultParseOptions;
1774
+
1775
+ if (!input) {
1776
+ if (!options.map) {
1777
+ return [];
1778
+ } else {
1779
+ return {};
1780
+ }
1781
+ }
1782
+
1783
+ if (input.headers && input.headers["set-cookie"]) {
1784
+ // fast-path for node.js (which automatically normalizes header names to lower-case
1785
+ input = input.headers["set-cookie"];
1786
+ } else if (input.headers) {
1787
+ // slow-path for other environments - see #25
1788
+ var sch =
1789
+ input.headers[
1790
+ Object.keys(input.headers).find(function (key) {
1791
+ return key.toLowerCase() === "set-cookie";
1792
+ })
1793
+ ];
1794
+ // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
1795
+ if (!sch && input.headers.cookie && !options.silent) {
1796
+ console.warn(
1797
+ "Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning."
1798
+ );
1799
+ }
1800
+ input = sch;
1801
+ }
1802
+ if (!Array.isArray(input)) {
1803
+ input = [input];
1804
+ }
1805
+
1806
+ options = options
1807
+ ? Object.assign({}, defaultParseOptions, options)
1808
+ : defaultParseOptions;
1809
+
1810
+ if (!options.map) {
1811
+ return input.filter(isNonEmptyString).map(function (str) {
1812
+ return parseString(str, options);
1813
+ });
1814
+ } else {
1815
+ var cookies = {};
1816
+ return input.filter(isNonEmptyString).reduce(function (cookies, str) {
1817
+ var cookie = parseString(str, options);
1818
+ cookies[cookie.name] = cookie;
1819
+ return cookies;
1820
+ }, cookies);
1821
+ }
1822
+ }
1823
+
1824
+ /*
1825
+ Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
1826
+ that are within a single set-cookie field-value, such as in the Expires portion.
1827
+
1828
+ This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
1829
+ Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
1830
+ React Native's fetch does this for *every* header, including set-cookie.
1831
+
1832
+ Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
1833
+ Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
1834
+ */
1835
+ function splitCookiesString(cookiesString) {
1836
+ if (Array.isArray(cookiesString)) {
1837
+ return cookiesString;
1838
+ }
1839
+ if (typeof cookiesString !== "string") {
1840
+ return [];
1841
+ }
1842
+
1843
+ var cookiesStrings = [];
1844
+ var pos = 0;
1845
+ var start;
1846
+ var ch;
1847
+ var lastComma;
1848
+ var nextStart;
1849
+ var cookiesSeparatorFound;
1850
+
1851
+ function skipWhitespace() {
1852
+ while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
1853
+ pos += 1;
1854
+ }
1855
+ return pos < cookiesString.length;
1856
+ }
1857
+
1858
+ function notSpecialChar() {
1859
+ ch = cookiesString.charAt(pos);
1860
+
1861
+ return ch !== "=" && ch !== ";" && ch !== ",";
1862
+ }
1863
+
1864
+ while (pos < cookiesString.length) {
1865
+ start = pos;
1866
+ cookiesSeparatorFound = false;
1867
+
1868
+ while (skipWhitespace()) {
1869
+ ch = cookiesString.charAt(pos);
1870
+ if (ch === ",") {
1871
+ // ',' is a cookie separator if we have later first '=', not ';' or ','
1872
+ lastComma = pos;
1873
+ pos += 1;
1874
+
1875
+ skipWhitespace();
1876
+ nextStart = pos;
1877
+
1878
+ while (pos < cookiesString.length && notSpecialChar()) {
1879
+ pos += 1;
1880
+ }
1881
+
1882
+ // currently special character
1883
+ if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
1884
+ // we found cookies separator
1885
+ cookiesSeparatorFound = true;
1886
+ // pos is inside the next cookie, so back up and return it.
1887
+ pos = nextStart;
1888
+ cookiesStrings.push(cookiesString.substring(start, lastComma));
1889
+ start = pos;
1890
+ } else {
1891
+ // in param ',' or param separator ';',
1892
+ // we continue from that comma
1893
+ pos = lastComma + 1;
1894
+ }
1895
+ } else {
1896
+ pos += 1;
1897
+ }
1898
+ }
1899
+
1900
+ if (!cookiesSeparatorFound || pos >= cookiesString.length) {
1901
+ cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
1902
+ }
1903
+ }
1904
+
1905
+ return cookiesStrings;
1906
+ }
1907
+
1908
+ setCookie.exports = parse;
1909
+ setCookie.exports.parse = parse;
1910
+ var parseString_1 = setCookie.exports.parseString = parseString;
1911
+ var splitCookiesString_1 = setCookie.exports.splitCookiesString = splitCookiesString;
1912
+
1427
1913
  /**
1428
1914
  * @param {import('types').LoadOutput} loaded
1429
1915
  * @returns {import('types').NormalizedLoadOutput}
@@ -1435,10 +1921,7 @@ function normalize(loaded) {
1435
1921
  const status = loaded.status;
1436
1922
 
1437
1923
  if (!loaded.error && has_error_status) {
1438
- return {
1439
- status: status || 500,
1440
- error: new Error()
1441
- };
1924
+ return { status: status || 500, error: new Error() };
1442
1925
  }
1443
1926
 
1444
1927
  const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
@@ -1478,6 +1961,18 @@ function normalize(loaded) {
1478
1961
  }
1479
1962
  }
1480
1963
 
1964
+ if (loaded.dependencies) {
1965
+ if (
1966
+ !Array.isArray(loaded.dependencies) ||
1967
+ loaded.dependencies.some((dep) => typeof dep !== 'string')
1968
+ ) {
1969
+ return {
1970
+ status: 500,
1971
+ error: new Error('"dependencies" property returned from load() must be of type string[]')
1972
+ };
1973
+ }
1974
+ }
1975
+
1481
1976
  // TODO remove before 1.0
1482
1977
  if (/** @type {any} */ (loaded).context) {
1483
1978
  throw new Error(
@@ -1544,6 +2039,32 @@ function normalize_path(path, trailing_slash) {
1544
2039
  return path;
1545
2040
  }
1546
2041
 
2042
+ /**
2043
+ * @param {string} hostname
2044
+ * @param {string} [constraint]
2045
+ */
2046
+ function domain_matches(hostname, constraint) {
2047
+ if (!constraint) return true;
2048
+
2049
+ const normalized = constraint[0] === '.' ? constraint.slice(1) : constraint;
2050
+
2051
+ if (hostname === normalized) return true;
2052
+ return hostname.endsWith('.' + normalized);
2053
+ }
2054
+
2055
+ /**
2056
+ * @param {string} path
2057
+ * @param {string} [constraint]
2058
+ */
2059
+ function path_matches(path, constraint) {
2060
+ if (!constraint) return true;
2061
+
2062
+ const normalized = constraint.endsWith('/') ? constraint.slice(0, -1) : constraint;
2063
+
2064
+ if (path === normalized) return true;
2065
+ return path.startsWith(normalized + '/');
2066
+ }
2067
+
1547
2068
  /**
1548
2069
  * @param {{
1549
2070
  * event: import('types').RequestEvent;
@@ -1580,10 +2101,10 @@ async function load_node({
1580
2101
  /** @type {Array<import('./types').Fetched>} */
1581
2102
  const fetched = [];
1582
2103
 
1583
- /**
1584
- * @type {string[]}
1585
- */
1586
- let set_cookie_headers = [];
2104
+ const cookies = parse_1(event.request.headers.get('cookie') || '');
2105
+
2106
+ /** @type {import('set-cookie-parser').Cookie[]} */
2107
+ const new_cookies = [];
1587
2108
 
1588
2109
  /** @type {import('types').LoadOutput} */
1589
2110
  let loaded;
@@ -1599,7 +2120,9 @@ async function load_node({
1599
2120
  : {};
1600
2121
 
1601
2122
  if (shadow.cookies) {
1602
- set_cookie_headers.push(...shadow.cookies);
2123
+ shadow.cookies.forEach((header) => {
2124
+ new_cookies.push(parseString_1(header));
2125
+ });
1603
2126
  }
1604
2127
 
1605
2128
  if (shadow.error) {
@@ -1613,7 +2136,7 @@ async function load_node({
1613
2136
  redirect: shadow.redirect
1614
2137
  };
1615
2138
  } else if (module.load) {
1616
- /** @type {import('types').LoadInput | import('types').ErrorLoadInput} */
2139
+ /** @type {import('types').LoadInput} */
1617
2140
  const load_input = {
1618
2141
  url: state.prerender ? create_prerendering_url_proxy(event.url) : event.url,
1619
2142
  params: event.params,
@@ -1705,9 +2228,23 @@ async function load_node({
1705
2228
  if (opts.credentials !== 'omit') {
1706
2229
  uses_credentials = true;
1707
2230
 
1708
- const cookie = event.request.headers.get('cookie');
1709
2231
  const authorization = event.request.headers.get('authorization');
1710
2232
 
2233
+ // combine cookies from the initiating request with any that were
2234
+ // added via set-cookie
2235
+ const combined_cookies = { ...cookies };
2236
+
2237
+ for (const cookie of new_cookies) {
2238
+ if (!domain_matches(event.url.hostname, cookie.domain)) continue;
2239
+ if (!path_matches(resolved, cookie.path)) continue;
2240
+
2241
+ combined_cookies[cookie.name] = cookie.value;
2242
+ }
2243
+
2244
+ const cookie = Object.entries(combined_cookies)
2245
+ .map(([name, value]) => `${name}=${value}`)
2246
+ .join('; ');
2247
+
1711
2248
  if (cookie) {
1712
2249
  opts.headers.set('cookie', cookie);
1713
2250
  }
@@ -1732,9 +2269,8 @@ async function load_node({
1732
2269
  new Request(new URL(requested, event.url).href, { ...opts, credentials: undefined }),
1733
2270
  options,
1734
2271
  {
1735
- getClientAddress: state.getClientAddress,
1736
- initiator: route,
1737
- prerender: state.prerender
2272
+ ...state,
2273
+ initiator: route
1738
2274
  }
1739
2275
  );
1740
2276
 
@@ -1771,6 +2307,14 @@ async function load_node({
1771
2307
  response = await options.hooks.externalFetch.call(null, external_request);
1772
2308
  }
1773
2309
 
2310
+ const set_cookie = response.headers.get('set-cookie');
2311
+ if (set_cookie) {
2312
+ new_cookies.push(
2313
+ ...splitCookiesString_1(set_cookie)
2314
+ .map((str) => parseString_1(str))
2315
+ );
2316
+ }
2317
+
1774
2318
  const proxy = new Proxy(response, {
1775
2319
  get(response, key, _receiver) {
1776
2320
  async function text() {
@@ -1779,9 +2323,8 @@ async function load_node({
1779
2323
  /** @type {import('types').ResponseHeaders} */
1780
2324
  const headers = {};
1781
2325
  for (const [key, value] of response.headers) {
1782
- if (key === 'set-cookie') {
1783
- set_cookie_headers = set_cookie_headers.concat(value);
1784
- } else if (key !== 'etag') {
2326
+ // TODO skip others besides set-cookie and etag?
2327
+ if (key !== 'set-cookie' && key !== 'etag') {
1785
2328
  headers[key] = value;
1786
2329
  }
1787
2330
  }
@@ -1848,7 +2391,9 @@ async function load_node({
1848
2391
 
1849
2392
  return proxy;
1850
2393
  },
1851
- stuff: { ...stuff }
2394
+ stuff: { ...stuff },
2395
+ status: is_error ? status ?? null : null,
2396
+ error: is_error ? error ?? null : null
1852
2397
  };
1853
2398
 
1854
2399
  if (options.dev) {
@@ -1860,11 +2405,6 @@ async function load_node({
1860
2405
  });
1861
2406
  }
1862
2407
 
1863
- if (is_error) {
1864
- /** @type {import('types').ErrorLoadInput} */ (load_input).status = status;
1865
- /** @type {import('types').ErrorLoadInput} */ (load_input).error = error;
1866
- }
1867
-
1868
2408
  loaded = await module.load.call(null, load_input);
1869
2409
 
1870
2410
  if (!loaded) {
@@ -1905,7 +2445,11 @@ async function load_node({
1905
2445
  loaded: normalize(loaded),
1906
2446
  stuff: loaded.stuff || stuff,
1907
2447
  fetched,
1908
- set_cookie_headers,
2448
+ set_cookie_headers: new_cookies.map((new_cookie) => {
2449
+ const { name, value, ...options } = new_cookie;
2450
+ // @ts-expect-error
2451
+ return serialize_1(name, value, options);
2452
+ }),
1909
2453
  uses_credentials
1910
2454
  };
1911
2455
  }
@@ -9,7 +9,7 @@ import { s } from './misc.js';
9
9
  import { d as deep_merge } from './object.js';
10
10
  import { n as normalize_path, r as resolve, i as is_root_relative } from './url.js';
11
11
  import { svelte } from '@sveltejs/vite-plugin-svelte';
12
- import { pathToFileURL, URL } from 'url';
12
+ import { pathToFileURL, URL as URL$1 } from 'url';
13
13
  import { installFetch } from '../install-fetch.js';
14
14
  import 'sade';
15
15
  import 'child_process';
@@ -293,7 +293,6 @@ import root from '__GENERATED__/root.svelte';
293
293
  import { respond } from '${runtime}/server/index.js';
294
294
  import { set_paths, assets, base } from '${runtime}/paths.js';
295
295
  import { set_prerendering } from '${runtime}/env.js';
296
- import * as user_hooks from ${s(hooks)};
297
296
 
298
297
  const template = ({ head, body, assets, nonce }) => ${s(template)
299
298
  .replace('%svelte.head%', '" + head + "')
@@ -463,6 +462,13 @@ async function build_server(
463
462
  const default_config = {
464
463
  build: {
465
464
  target: 'es2020'
465
+ },
466
+ ssr: {
467
+ // when developing against the Kit src code, we want to ensure that
468
+ // our dependencies are bundled so that apps don't need to install
469
+ // them as peerDependencies
470
+ noExternal: []
471
+
466
472
  }
467
473
  };
468
474
 
@@ -1096,7 +1102,7 @@ async function prerender({ config, entries, files, log }) {
1096
1102
  for (const [dependency_path, result] of dependencies) {
1097
1103
  // this seems circuitous, but using new URL allows us to not care
1098
1104
  // whether dependency_path is encoded or not
1099
- const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
1105
+ const encoded_dependency_path = new URL$1(dependency_path, 'http://localhost').pathname;
1100
1106
  const decoded_dependency_path = decodeURI(encoded_dependency_path);
1101
1107
 
1102
1108
  const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
@@ -1118,7 +1124,7 @@ async function prerender({ config, entries, files, log }) {
1118
1124
  const resolved = resolve(encoded, href);
1119
1125
  if (!is_root_relative(resolved)) continue;
1120
1126
 
1121
- const parsed = new URL(resolved, 'http://localhost');
1127
+ const parsed = new URL$1(resolved, 'http://localhost');
1122
1128
 
1123
1129
  if (parsed.search) ;
1124
1130
 
@@ -789,32 +789,23 @@ function write_tsconfig(config) {
789
789
  JSON.stringify(
790
790
  {
791
791
  compilerOptions: {
792
- moduleResolution: 'node',
793
- module: 'es2020',
794
- lib: ['es2020', 'DOM'],
795
- target: 'es2020',
796
- // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
797
- // to enforce using \`import type\` instead of \`import\` for Types.
798
- importsNotUsedAsValues: 'error',
799
- // TypeScript doesn't know about import usages in the template because it only sees the
800
- // script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
801
- preserveValueImports: true,
802
- isolatedModules: true,
803
- resolveJsonModule: true,
804
- // To have warnings/errors of the Svelte compiler at the correct position,
805
- // enable source maps by default.
806
- sourceMap: true,
807
- esModuleInterop: true,
808
- skipLibCheck: true,
809
- forceConsistentCasingInFileNames: true,
792
+ // generated options
810
793
  baseUrl: config_relative('.'),
811
- allowJs: true,
812
- checkJs: true,
813
794
  paths: {
814
795
  $lib: [project_relative(config.kit.files.lib)],
815
796
  '$lib/*': [project_relative(config.kit.files.lib + '/*')]
816
797
  },
817
- rootDirs: [config_relative('.'), './types']
798
+ rootDirs: [config_relative('.'), './types'],
799
+
800
+ // essential options
801
+ // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
802
+ // to enforce using \`import type\` instead of \`import\` for Types.
803
+ importsNotUsedAsValues: 'error',
804
+ // Vite compiles modules one at a time
805
+ isolatedModules: true,
806
+ // TypeScript doesn't know about import usages in the template because it only sees the
807
+ // script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
808
+ preserveValueImports: true
818
809
  },
819
810
  include,
820
811
  exclude: [config_relative('node_modules/**'), './**']
package/dist/cli.js CHANGED
@@ -870,7 +870,7 @@ async function launch(port, https, base) {
870
870
  exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}${base}`);
871
871
  }
872
872
 
873
- const prog = sade('svelte-kit').version('1.0.0-next.312');
873
+ const prog = sade('svelte-kit').version('1.0.0-next.316');
874
874
 
875
875
  prog
876
876
  .command('dev')
@@ -1049,7 +1049,7 @@ async function check_port(port) {
1049
1049
  function welcome({ port, host, https, open, base, loose, allow, cwd }) {
1050
1050
  if (open) launch(port, https, base);
1051
1051
 
1052
- console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.312'}\n`));
1052
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.316'}\n`));
1053
1053
 
1054
1054
  const protocol = https ? 'https:' : 'http:';
1055
1055
  const exposed = typeof host !== 'undefined' && host !== 'localhost' && host !== '127.0.0.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.312",
3
+ "version": "1.0.0-next.316",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -23,6 +23,7 @@
23
23
  "@types/mime": "^2.0.3",
24
24
  "@types/node": "^16.11.11",
25
25
  "@types/sade": "^1.7.3",
26
+ "@types/set-cookie-parser": "^2.4.2",
26
27
  "amphtml-validator": "^1.0.35",
27
28
  "cookie": "^0.5.0",
28
29
  "cross-env": "^7.0.3",
@@ -36,6 +37,7 @@
36
37
  "port-authority": "^1.1.2",
37
38
  "rollup": "^2.60.2",
38
39
  "selfsigned": "^2.0.0",
40
+ "set-cookie-parser": "^2.4.8",
39
41
  "sirv": "^2.0.0",
40
42
  "svelte": "^3.44.2",
41
43
  "svelte-check": "^2.5.0",
@@ -83,8 +85,8 @@
83
85
  "check:all": "tsc && pnpm run -r check --filter ./",
84
86
  "format": "npm run check-format -- --write",
85
87
  "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
86
- "test": "npm run test:unit && npm run test:typings && npm run test:packaging",
87
- "test:all": "pnpm run -r test --workspace-concurrency 1 --filter ./",
88
+ "test": "npm run test:unit && npm run test:typings && npm run test:packaging && npm run test:integration",
89
+ "test:integration": "pnpm run -r test --workspace-concurrency 1 --filter ./test",
88
90
  "test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\" -i packaging",
89
91
  "test:typings": "tsc --project test/typings",
90
92
  "test:packaging": "uvu src/packaging \"(spec\\.js|test[\\\\/]index\\.js)\"",
@@ -103,9 +103,9 @@ declare module '$app/navigation' {
103
103
  ): Promise<void>;
104
104
  /**
105
105
  * Causes any `load` functions belonging to the currently active page to re-run if they `fetch` the resource in question. Returns a `Promise` that resolves when the page is subsequently updated.
106
- * @param href The invalidated resource
106
+ * @param dependency The invalidated resource
107
107
  */
108
- export function invalidate(href: string): Promise<void>;
108
+ export function invalidate(dependency: string | ((href: string) => boolean)): Promise<void>;
109
109
  /**
110
110
  * Programmatically prefetches the given page, which means
111
111
  * 1. ensuring that the code for the page is loaded, and
package/types/index.d.ts CHANGED
@@ -7,10 +7,7 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces';
7
7
  import {
8
8
  AdapterEntry,
9
9
  CspDirectives,
10
- ErrorLoadInput,
11
10
  JSONValue,
12
- LoadInput,
13
- LoadOutput,
14
11
  Logger,
15
12
  MaybePromise,
16
13
  Prerendered,
@@ -157,13 +154,6 @@ export interface Config {
157
154
  preprocess?: any;
158
155
  }
159
156
 
160
- export interface ErrorLoad<
161
- Params extends Record<string, string> = Record<string, string>,
162
- Props extends Record<string, any> = Record<string, any>
163
- > {
164
- (input: ErrorLoadInput<Params>): MaybePromise<LoadOutput<Props>>;
165
- }
166
-
167
157
  export interface ExternalFetch {
168
158
  (req: Request): Promise<Response>;
169
159
  }
@@ -196,6 +186,31 @@ export interface Load<
196
186
  (input: LoadInput<Params, InputProps>): MaybePromise<LoadOutput<OutputProps>>;
197
187
  }
198
188
 
189
+ export interface LoadInput<
190
+ Params extends Record<string, string> = Record<string, string>,
191
+ Props extends Record<string, any> = Record<string, any>
192
+ > {
193
+ fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
194
+ params: Params;
195
+ props: Props;
196
+ routeId: string | null;
197
+ session: App.Session;
198
+ stuff: Partial<App.Stuff>;
199
+ url: URL;
200
+ status: number | null;
201
+ error: Error | null;
202
+ }
203
+
204
+ export interface LoadOutput<Props extends Record<string, any> = Record<string, any>> {
205
+ status?: number;
206
+ error?: string | Error;
207
+ redirect?: string;
208
+ props?: Props;
209
+ stuff?: Partial<App.Stuff>;
210
+ maxage?: number;
211
+ dependencies?: string[];
212
+ }
213
+
199
214
  export interface Navigation {
200
215
  from: URL;
201
216
  to: URL;
@@ -118,6 +118,7 @@ export type NormalizedLoadOutput = {
118
118
  props?: Record<string, any> | Promise<Record<string, any>>;
119
119
  stuff?: Record<string, any>;
120
120
  maxage?: number;
121
+ dependencies?: string[];
121
122
  };
122
123
 
123
124
  export interface PageData {
@@ -134,12 +134,6 @@ export interface CspDirectives {
134
134
  >;
135
135
  }
136
136
 
137
- export interface ErrorLoadInput<Params extends Record<string, string> = Record<string, string>>
138
- extends LoadInput<Params> {
139
- status?: number;
140
- error?: Error;
141
- }
142
-
143
137
  export type HttpMethod = 'get' | 'head' | 'post' | 'put' | 'delete' | 'patch';
144
138
 
145
139
  export interface JSONObject {
@@ -156,28 +150,6 @@ export type JSONValue =
156
150
  | JSONValue[]
157
151
  | JSONObject;
158
152
 
159
- export interface LoadInput<
160
- Params extends Record<string, string> = Record<string, string>,
161
- Props extends Record<string, any> = Record<string, any>
162
- > {
163
- fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
164
- params: Params;
165
- props: Props;
166
- routeId: string | null;
167
- session: App.Session;
168
- stuff: Partial<App.Stuff>;
169
- url: URL;
170
- }
171
-
172
- export interface LoadOutput<Props extends Record<string, any> = Record<string, any>> {
173
- status?: number;
174
- error?: string | Error;
175
- redirect?: string;
176
- props?: Props;
177
- stuff?: Partial<App.Stuff>;
178
- maxage?: number;
179
- }
180
-
181
153
  export interface Logger {
182
154
  (msg: string): void;
183
155
  success(msg: string): void;