@sveltejs/kit 1.0.0-next.533 → 1.0.0-next.534

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.533",
3
+ "version": "1.0.0-next.534",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -13,7 +13,7 @@
13
13
  "@sveltejs/vite-plugin-svelte": "^1.1.0",
14
14
  "@types/cookie": "^0.5.1",
15
15
  "cookie": "^0.5.0",
16
- "devalue": "^4.1.0",
16
+ "devalue": "^4.2.0",
17
17
  "kleur": "^4.1.5",
18
18
  "magic-string": "^0.26.7",
19
19
  "mime": "^3.0.0",
@@ -200,6 +200,8 @@ function update_types(config, routes, route, to_delete = new Set()) {
200
200
  .join('; ')} }`
201
201
  );
202
202
 
203
+ declarations.push(`type RouteId = '${route.id}';`);
204
+
203
205
  // These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future
204
206
  if (route.layout || route.leaf) {
205
207
  // If T extends the empty object, void is also allowed as a return type
@@ -249,17 +251,25 @@ function update_types(config, routes, route, to_delete = new Set()) {
249
251
  }
250
252
 
251
253
  if (route.leaf.server) {
252
- exports.push(`export type Action = Kit.Action<RouteParams>`);
253
- exports.push(`export type Actions = Kit.Actions<RouteParams>`);
254
+ exports.push(
255
+ `export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>`
256
+ );
257
+ exports.push(
258
+ `export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>`
259
+ );
254
260
  }
255
261
  }
256
262
 
257
263
  if (route.layout) {
258
264
  let all_pages_have_load = true;
259
265
  const layout_params = new Set();
266
+ const ids = ['RouteId'];
267
+
260
268
  route.layout.child_pages?.forEach((page) => {
261
269
  const leaf = routes.get(page);
262
270
  if (leaf) {
271
+ if (leaf.route.page) ids.push(`"${leaf.route.id}"`);
272
+
263
273
  for (const name of leaf.route.names) {
264
274
  layout_params.add(name);
265
275
  }
@@ -280,6 +290,13 @@ function update_types(config, routes, route, to_delete = new Set()) {
280
290
  }
281
291
  });
282
292
 
293
+ if (route.id === '/') {
294
+ // root layout is used for fallback error page, where ID can be null
295
+ ids.push('null');
296
+ }
297
+
298
+ declarations.push(`type LayoutRouteId = ${ids.join(' | ')}`);
299
+
283
300
  declarations.push(
284
301
  `type LayoutParams = RouteParams & { ${Array.from(layout_params).map(
285
302
  (param) => `${param}?: string`
@@ -306,11 +323,11 @@ function update_types(config, routes, route, to_delete = new Set()) {
306
323
  }
307
324
 
308
325
  if (route.endpoint) {
309
- exports.push(`export type RequestHandler = Kit.RequestHandler<RouteParams>;`);
326
+ exports.push(`export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;`);
310
327
  }
311
328
 
312
329
  if (route.leaf?.server || route.layout?.server || route.endpoint) {
313
- exports.push(`export type RequestEvent = Kit.RequestEvent<RouteParams>;`);
330
+ exports.push(`export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;`);
314
331
  }
315
332
 
316
333
  const output = [imports.join('\n'), declarations.join('\n'), exports.join('\n')]
@@ -336,6 +353,8 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true
336
353
  const params = `${is_page ? 'Route' : 'Layout'}Params`;
337
354
  const prefix = is_page ? 'Page' : 'Layout';
338
355
 
356
+ const route_id = is_page ? 'RouteId' : 'LayoutRouteId';
357
+
339
358
  /** @type {string[]} */
340
359
  const declarations = [];
341
360
  /** @type {string[]} */
@@ -367,7 +386,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true
367
386
  ? `Partial<App.PageData> & Record<string, any> | void`
368
387
  : `OutputDataShape<${parent_type}>`;
369
388
  exports.push(
370
- `export type ${prefix}ServerLoad<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.ServerLoad<${params}, ${parent_type}, OutputData>;`
389
+ `export type ${prefix}ServerLoad<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.ServerLoad<${params}, ${parent_type}, OutputData, ${route_id}>;`
371
390
  );
372
391
 
373
392
  exports.push(`export type ${prefix}ServerLoadEvent = Parameters<${prefix}ServerLoad>[0];`);
@@ -414,7 +433,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true
414
433
  ? `Partial<App.PageData> & Record<string, any> | void`
415
434
  : `OutputDataShape<${parent_type}>`;
416
435
  exports.push(
417
- `export type ${prefix}Load<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.Load<${params}, ${prefix}ServerData, ${parent_type}, OutputData>;`
436
+ `export type ${prefix}Load<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.Load<${params}, ${prefix}ServerData, ${parent_type}, OutputData, ${route_id}>;`
418
437
  );
419
438
 
420
439
  exports.push(`export type ${prefix}LoadEvent = Parameters<${prefix}Load>[0];`);
@@ -74,7 +74,7 @@ export class Server {
74
74
  get request() {
75
75
  throw new Error('request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details');
76
76
  }
77
- }) ?? { message: event.routeId != null ? 'Internal Error' : 'Not Found' };
77
+ }) ?? { message: event.route.id != null ? 'Internal Error' : 'Not Found' };
78
78
  },
79
79
  hooks: null,
80
80
  manifest,
@@ -439,7 +439,7 @@ export async function dev(vite, vite_config, svelte_config) {
439
439
  'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
440
440
  );
441
441
  }
442
- }) ?? { message: event.routeId != null ? 'Internal Error' : 'Not Found' }
442
+ }) ?? { message: event.route.id != null ? 'Internal Error' : 'Not Found' }
443
443
  );
444
444
  },
445
445
  hooks,
@@ -163,16 +163,16 @@ export function create_client({ target, base, trailing_slash }) {
163
163
 
164
164
  /**
165
165
  * @param {string | URL} url
166
- * @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any; invalidateAll?: boolean }} opts
166
+ * @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts
167
167
  * @param {string[]} redirect_chain
168
168
  * @param {{}} [nav_token]
169
169
  */
170
170
  async function goto(
171
171
  url,
172
172
  {
173
- noscroll = false,
173
+ noScroll = false,
174
174
  replaceState = false,
175
- keepfocus = false,
175
+ keepFocus = false,
176
176
  state = {},
177
177
  invalidateAll = false
178
178
  },
@@ -185,8 +185,8 @@ export function create_client({ target, base, trailing_slash }) {
185
185
 
186
186
  return navigate({
187
187
  url,
188
- scroll: noscroll ? scroll_state() : null,
189
- keepfocus,
188
+ scroll: noScroll ? scroll_state() : null,
189
+ keepfocus: keepFocus,
190
190
  redirect_chain,
191
191
  details: {
192
192
  state,
@@ -232,8 +232,12 @@ export function create_client({ target, base, trailing_slash }) {
232
232
  if (!navigation_result) {
233
233
  navigation_result = await server_fallback(
234
234
  url,
235
- null,
236
- handle_error(new Error(`Not found: ${url.pathname}`), { url, params: {}, routeId: null }),
235
+ { id: null },
236
+ handle_error(new Error(`Not found: ${url.pathname}`), {
237
+ url,
238
+ params: {},
239
+ route: { id: null }
240
+ }),
237
241
  404
238
242
  );
239
243
  }
@@ -249,9 +253,9 @@ export function create_client({ target, base, trailing_slash }) {
249
253
  if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
250
254
  navigation_result = await load_root_error_page({
251
255
  status: 500,
252
- error: handle_error(new Error('Redirect loop'), { url, params: {}, routeId: null }),
256
+ error: handle_error(new Error('Redirect loop'), { url, params: {}, route: { id: null } }),
253
257
  url,
254
- routeId: null
258
+ route: { id: null }
255
259
  });
256
260
  } else {
257
261
  goto(
@@ -382,7 +386,7 @@ export function create_client({ target, base, trailing_slash }) {
382
386
  from: null,
383
387
  to: add_url_properties('to', {
384
388
  params: current.params,
385
- routeId: current.route?.id ?? null,
389
+ route: { id: current.route?.id ?? null },
386
390
  url: new URL(location.href)
387
391
  }),
388
392
  type: 'load'
@@ -464,7 +468,7 @@ export function create_client({ target, base, trailing_slash }) {
464
468
  result.props.page = {
465
469
  error,
466
470
  params,
467
- routeId: route && route.id,
471
+ route,
468
472
  status,
469
473
  url,
470
474
  form,
@@ -502,12 +506,12 @@ export function create_client({ target, base, trailing_slash }) {
502
506
  * parent: () => Promise<Record<string, any>>;
503
507
  * url: URL;
504
508
  * params: Record<string, string>;
505
- * routeId: string | null;
509
+ * route: { id: string | null };
506
510
  * server_data_node: import('./types').DataNode | null;
507
511
  * }} options
508
512
  * @returns {Promise<import('./types').BranchNode>}
509
513
  */
510
- async function load_node({ loader, parent, url, params, routeId, server_data_node }) {
514
+ async function load_node({ loader, parent, url, params, route, server_data_node }) {
511
515
  /** @type {Record<string, any> | null} */
512
516
  let data = null;
513
517
 
@@ -516,6 +520,7 @@ export function create_client({ target, base, trailing_slash }) {
516
520
  dependencies: new Set(),
517
521
  params: new Set(),
518
522
  parent: false,
523
+ route: false,
519
524
  url: false
520
525
  };
521
526
 
@@ -532,7 +537,12 @@ export function create_client({ target, base, trailing_slash }) {
532
537
 
533
538
  /** @type {import('types').LoadEvent} */
534
539
  const load_input = {
535
- routeId,
540
+ route: {
541
+ get id() {
542
+ uses.route = true;
543
+ return route.id;
544
+ }
545
+ },
536
546
  params: new Proxy(params, {
537
547
  get: (target, key) => {
538
548
  uses.params.add(/** @type {string} */ (key));
@@ -617,6 +627,12 @@ export function create_client({ target, base, trailing_slash }) {
617
627
  );
618
628
  },
619
629
  enumerable: false
630
+ },
631
+ routeId: {
632
+ get() {
633
+ throw new Error('routeId has been replaced by route.id');
634
+ },
635
+ enumerable: false
620
636
  }
621
637
  });
622
638
 
@@ -643,17 +659,19 @@ export function create_client({ target, base, trailing_slash }) {
643
659
  }
644
660
 
645
661
  /**
646
- * @param {boolean} url_changed
647
662
  * @param {boolean} parent_changed
663
+ * @param {boolean} route_changed
664
+ * @param {boolean} url_changed
648
665
  * @param {import('types').Uses | undefined} uses
649
666
  * @param {Record<string, string>} params
650
667
  */
651
- function has_changed(url_changed, parent_changed, uses, params) {
668
+ function has_changed(parent_changed, route_changed, url_changed, uses, params) {
652
669
  if (force_invalidation) return true;
653
670
 
654
671
  if (!uses) return false;
655
672
 
656
673
  if (uses.parent && parent_changed) return true;
674
+ if (uses.route && route_changed) return true;
657
675
  if (uses.url && url_changed) return true;
658
676
 
659
677
  for (const param of uses.params) {
@@ -681,6 +699,7 @@ export function create_client({ target, base, trailing_slash }) {
681
699
  dependencies: new Set(node.uses.dependencies ?? []),
682
700
  params: new Set(node.uses.params ?? []),
683
701
  parent: !!node.uses.parent,
702
+ route: !!node.uses.route,
684
703
  url: !!node.uses.url
685
704
  }
686
705
  };
@@ -713,6 +732,7 @@ export function create_client({ target, base, trailing_slash }) {
713
732
  let server_data = null;
714
733
 
715
734
  const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
735
+ const route_changed = current.route ? id !== current.route.id : false;
716
736
 
717
737
  const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
718
738
  const previous = current.branch[i];
@@ -720,7 +740,13 @@ export function create_client({ target, base, trailing_slash }) {
720
740
  const invalid =
721
741
  !!loader?.[0] &&
722
742
  (previous?.loader !== loader[1] ||
723
- has_changed(url_changed, acc.some(Boolean), previous.server?.uses, params));
743
+ has_changed(
744
+ acc.some(Boolean),
745
+ route_changed,
746
+ url_changed,
747
+ previous.server?.uses,
748
+ params
749
+ ));
724
750
 
725
751
  acc.push(invalid);
726
752
  return acc;
@@ -732,9 +758,9 @@ export function create_client({ target, base, trailing_slash }) {
732
758
  } catch (error) {
733
759
  return load_root_error_page({
734
760
  status: 500,
735
- error: handle_error(error, { url, params, routeId: route.id }),
761
+ error: handle_error(error, { url, params, route: { id: route.id } }),
736
762
  url,
737
- routeId: route.id
763
+ route
738
764
  });
739
765
  }
740
766
 
@@ -759,7 +785,7 @@ export function create_client({ target, base, trailing_slash }) {
759
785
  const valid =
760
786
  (!server_data_node || server_data_node.type === 'skip') &&
761
787
  loader[1] === previous?.loader &&
762
- !has_changed(url_changed, parent_changed, previous.shared?.uses, params);
788
+ !has_changed(parent_changed, route_changed, url_changed, previous.shared?.uses, params);
763
789
  if (valid) return previous;
764
790
 
765
791
  parent_changed = true;
@@ -773,7 +799,7 @@ export function create_client({ target, base, trailing_slash }) {
773
799
  loader: loader[1],
774
800
  url,
775
801
  params,
776
- routeId: route.id,
802
+ route,
777
803
  parent: async () => {
778
804
  const data = {};
779
805
  for (let j = 0; j < i; j += 1) {
@@ -821,7 +847,7 @@ export function create_client({ target, base, trailing_slash }) {
821
847
  status = err.status;
822
848
  error = err.body;
823
849
  } else {
824
- error = handle_error(err, { params, url, routeId: route.id });
850
+ error = handle_error(err, { params, url, route: { id: route.id } });
825
851
  }
826
852
 
827
853
  const error_load = await load_nearest_error_page(i, branch, errors);
@@ -837,7 +863,7 @@ export function create_client({ target, base, trailing_slash }) {
837
863
  } else {
838
864
  // if we get here, it's because the root `load` function failed,
839
865
  // and we need to fall back to the server
840
- return await server_fallback(url, route.id, error, status);
866
+ return await server_fallback(url, { id: route.id }, error, status);
841
867
  }
842
868
  }
843
869
  } else {
@@ -893,11 +919,11 @@ export function create_client({ target, base, trailing_slash }) {
893
919
  * status: number;
894
920
  * error: App.Error;
895
921
  * url: URL;
896
- * routeId: string | null
922
+ * route: { id: string | null }
897
923
  * }} opts
898
924
  * @returns {Promise<import('./types').NavigationFinished>}
899
925
  */
900
- async function load_root_error_page({ status, error, url, routeId }) {
926
+ async function load_root_error_page({ status, error, url, route }) {
901
927
  /** @type {Record<string, string>} */
902
928
  const params = {}; // error page does not have params
903
929
 
@@ -933,7 +959,7 @@ export function create_client({ target, base, trailing_slash }) {
933
959
  loader: default_layout_loader,
934
960
  url,
935
961
  params,
936
- routeId,
962
+ route,
937
963
  parent: () => Promise.resolve({}),
938
964
  server_data_node: create_data_node(server_data_node)
939
965
  });
@@ -1023,12 +1049,12 @@ export function create_client({ target, base, trailing_slash }) {
1023
1049
  const navigation = {
1024
1050
  from: add_url_properties('from', {
1025
1051
  params: current.params,
1026
- routeId: current.route?.id ?? null,
1052
+ route: { id: current.route?.id ?? null },
1027
1053
  url: current.url
1028
1054
  }),
1029
1055
  to: add_url_properties('to', {
1030
1056
  params: intent?.params ?? null,
1031
- routeId: intent?.route.id ?? null,
1057
+ route: { id: intent?.route?.id ?? null },
1032
1058
  url
1033
1059
  }),
1034
1060
  type
@@ -1080,12 +1106,12 @@ export function create_client({ target, base, trailing_slash }) {
1080
1106
  /**
1081
1107
  * Does a full page reload if it wouldn't result in an endless loop in the SPA case
1082
1108
  * @param {URL} url
1083
- * @param {string | null} routeId
1109
+ * @param {{ id: string | null }} route
1084
1110
  * @param {App.Error} error
1085
1111
  * @param {number} status
1086
1112
  * @returns {Promise<import('./types').NavigationFinished>}
1087
1113
  */
1088
- async function server_fallback(url, routeId, error, status) {
1114
+ async function server_fallback(url, route, error, status) {
1089
1115
  if (url.origin === location.origin && url.pathname === location.pathname && !hydrated) {
1090
1116
  // We would reload the same page we're currently on, which isn't hydrated,
1091
1117
  // which means no SSR, which means we would end up in an endless loop
@@ -1093,7 +1119,7 @@ export function create_client({ target, base, trailing_slash }) {
1093
1119
  status,
1094
1120
  error,
1095
1121
  url,
1096
- routeId
1122
+ route
1097
1123
  });
1098
1124
  }
1099
1125
  return await native_navigation(url);
@@ -1149,7 +1175,22 @@ export function create_client({ target, base, trailing_slash }) {
1149
1175
  }
1150
1176
  },
1151
1177
 
1152
- goto: (href, opts = {}) => goto(href, opts, []),
1178
+ goto: (href, opts = {}) => {
1179
+ // TODO remove for 1.0
1180
+ if ('keepfocus' in opts) {
1181
+ throw new Error(
1182
+ '`keepfocus` has been renamed to `keepFocus` (note the difference in casing)'
1183
+ );
1184
+ }
1185
+
1186
+ if ('noscroll' in opts) {
1187
+ throw new Error(
1188
+ '`noscroll` has been renamed to `noScroll` (note the difference in casing)'
1189
+ );
1190
+ }
1191
+
1192
+ return goto(href, opts, []);
1193
+ },
1153
1194
 
1154
1195
  invalidate: (resource) => {
1155
1196
  if (resource === undefined) {
@@ -1248,7 +1289,7 @@ export function create_client({ target, base, trailing_slash }) {
1248
1289
  const navigation = {
1249
1290
  from: add_url_properties('from', {
1250
1291
  params: current.params,
1251
- routeId: current.route?.id ?? null,
1292
+ route: { id: current.route?.id ?? null },
1252
1293
  url: current.url
1253
1294
  }),
1254
1295
  to: null,
@@ -1437,15 +1478,7 @@ export function create_client({ target, base, trailing_slash }) {
1437
1478
  });
1438
1479
  },
1439
1480
 
1440
- _hydrate: async ({
1441
- status,
1442
- error,
1443
- node_ids,
1444
- params,
1445
- routeId,
1446
- data: server_data_nodes,
1447
- form
1448
- }) => {
1481
+ _hydrate: async ({ status, error, node_ids, params, route, data: server_data_nodes, form }) => {
1449
1482
  hydrated = true;
1450
1483
 
1451
1484
  const url = new URL(location.href);
@@ -1461,7 +1494,7 @@ export function create_client({ target, base, trailing_slash }) {
1461
1494
  loader: nodes[n],
1462
1495
  url,
1463
1496
  params,
1464
- routeId,
1497
+ route,
1465
1498
  parent: async () => {
1466
1499
  const data = {};
1467
1500
  for (let j = 0; j < i; j += 1) {
@@ -1480,7 +1513,7 @@ export function create_client({ target, base, trailing_slash }) {
1480
1513
  status,
1481
1514
  error,
1482
1515
  form,
1483
- route: routes.find((route) => route.id === routeId) ?? null
1516
+ route: routes.find(({ id }) => id === route.id) ?? null
1484
1517
  });
1485
1518
  } catch (error) {
1486
1519
  if (error instanceof Redirect) {
@@ -1492,9 +1525,9 @@ export function create_client({ target, base, trailing_slash }) {
1492
1525
 
1493
1526
  result = await load_root_error_page({
1494
1527
  status: error instanceof HttpError ? error.status : 500,
1495
- error: handle_error(error, { url, params, routeId }),
1528
+ error: handle_error(error, { url, params, route }),
1496
1529
  url,
1497
- routeId
1530
+ route
1498
1531
  });
1499
1532
  }
1500
1533
 
@@ -1517,14 +1550,28 @@ async function load_data(url, invalid) {
1517
1550
  'x-sveltekit-invalidated': invalid.map((x) => (x ? '1' : '')).join(',')
1518
1551
  }
1519
1552
  });
1520
- const server_data = await res.text();
1553
+ const data = await res.json();
1521
1554
 
1522
1555
  if (!res.ok) {
1523
1556
  // error message is a JSON-stringified string which devalue can't handle at the top level
1524
- throw new Error(JSON.parse(server_data));
1557
+ throw new Error(data);
1525
1558
  }
1526
1559
 
1527
- return devalue.parse(server_data);
1560
+ // revive devalue-flattened data
1561
+ data.nodes?.forEach((/** @type {any} */ node) => {
1562
+ if (node?.type === 'data') {
1563
+ node.data = devalue.unflatten(node.data);
1564
+ node.uses = {
1565
+ dependencies: new Set(node.uses.dependencies ?? []),
1566
+ params: new Set(node.uses.params ?? []),
1567
+ parent: !!node.uses.parent,
1568
+ route: !!node.uses.route,
1569
+ url: !!node.uses.url
1570
+ };
1571
+ }
1572
+ });
1573
+
1574
+ return data;
1528
1575
  }
1529
1576
 
1530
1577
  /**
@@ -1538,7 +1585,7 @@ function handle_error(error, event) {
1538
1585
  }
1539
1586
  return (
1540
1587
  hooks.handleError({ error, event }) ??
1541
- /** @type {any} */ ({ message: event.routeId != null ? 'Internal Error' : 'Not Found' })
1588
+ /** @type {any} */ ({ message: event.route.id != null ? 'Internal Error' : 'Not Found' })
1542
1589
  );
1543
1590
  }
1544
1591
 
@@ -1574,6 +1621,15 @@ function add_url_properties(type, target) {
1574
1621
  });
1575
1622
  }
1576
1623
 
1624
+ Object.defineProperty(target, 'routeId', {
1625
+ get() {
1626
+ throw new Error(
1627
+ `The navigation shape changed - ${type}.routeId should now be ${type}.route.id`
1628
+ );
1629
+ },
1630
+ enumerable: false
1631
+ });
1632
+
1577
1633
  return target;
1578
1634
  }
1579
1635
 
@@ -28,7 +28,7 @@ export interface Client {
28
28
  error: App.Error;
29
29
  node_ids: number[];
30
30
  params: Record<string, string>;
31
- routeId: string | null;
31
+ route: { id: string | null };
32
32
  data: Array<import('types').ServerDataNode | null>;
33
33
  form: Record<string, any> | null;
34
34
  }): Promise<void>;
@@ -1,8 +1,9 @@
1
+ import * as devalue from 'devalue';
1
2
  import { HttpError, Redirect } from '../../control.js';
2
3
  import { normalize_error } from '../../../utils/error.js';
3
4
  import { once } from '../../../utils/functions.js';
4
5
  import { load_server_data } from '../page/load_data.js';
5
- import { data_response, handle_error_and_jsonify } from '../utils.js';
6
+ import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
6
7
  import { normalize_path, strip_data_suffix } from '../../../utils/url.js';
7
8
 
8
9
  /**
@@ -101,27 +102,42 @@ export async function render_data(event, route, options, state) {
101
102
  )
102
103
  );
103
104
 
104
- /** @type {import('types').ServerData} */
105
- const server_data = {
106
- type: 'data',
107
- nodes: nodes.slice(0, length)
108
- };
105
+ try {
106
+ const stubs = nodes.slice(0, length).map(serialize_data_node);
109
107
 
110
- return data_response(server_data, event);
108
+ const json = `{"type":"data","nodes":[${stubs.join(',')}]}`;
109
+ return json_response(json);
110
+ } catch (e) {
111
+ const error = /** @type {any} */ (e);
112
+ return json_response(JSON.stringify(clarify_devalue_error(event, error)), 500);
113
+ }
111
114
  } catch (e) {
112
115
  const error = normalize_error(e);
113
116
 
114
117
  if (error instanceof Redirect) {
115
- /** @type {import('types').ServerData} */
116
- const server_data = {
117
- type: 'redirect',
118
- location: error.location
119
- };
120
-
121
- return data_response(server_data, event);
118
+ return json_response(
119
+ JSON.stringify({
120
+ type: 'redirect',
121
+ location: error.location
122
+ })
123
+ );
122
124
  } else {
123
125
  // TODO make it clearer that this was an unexpected error
124
- return data_response(handle_error_and_jsonify(event, options, error), event);
126
+ return json_response(JSON.stringify(handle_error_and_jsonify(event, options, error)));
125
127
  }
126
128
  }
127
129
  }
130
+
131
+ /**
132
+ * @param {string} json
133
+ * @param {number} [status]
134
+ */
135
+ function json_response(json, status = 200) {
136
+ return new Response(json, {
137
+ status,
138
+ headers: {
139
+ 'content-type': 'application/json',
140
+ 'cache-control': 'private, no-store'
141
+ }
142
+ });
143
+ }
@@ -34,7 +34,7 @@ export async function render_endpoint(event, mod, state) {
34
34
  if (state.prerendering && !prerender) {
35
35
  if (state.initiator) {
36
36
  // if request came from a prerendered page, bail
37
- throw new Error(`${event.routeId} is not prerenderable`);
37
+ throw new Error(`${event.route.id} is not prerenderable`);
38
38
  } else {
39
39
  // if request came direct from the crawler, signal that
40
40
  // this route cannot be prerendered, but don't bail
@@ -121,7 +121,7 @@ export async function respond(request, options, state) {
121
121
  params,
122
122
  platform: state.platform,
123
123
  request,
124
- routeId: route && route.id,
124
+ route: { id: route?.id ?? null },
125
125
  setHeaders: (new_headers) => {
126
126
  for (const key in new_headers) {
127
127
  const lower = key.toLowerCase();
@@ -178,7 +178,8 @@ export async function respond(request, options, state) {
178
178
  path: removed('path', 'url.pathname'),
179
179
  query: removed('query', 'url.searchParams'),
180
180
  body: body_getter,
181
- rawBody: body_getter
181
+ rawBody: body_getter,
182
+ routeId: removed('routeId', 'route.id')
182
183
  });
183
184
 
184
185
  /** @type {import('types').RequiredResolveOptions} */
@@ -305,8 +306,8 @@ export async function respond(request, options, state) {
305
306
  }
306
307
  add_cookies_to_headers(response.headers, Object.values(new_cookies));
307
308
 
308
- if (state.prerendering && event.routeId !== null) {
309
- response.headers.set('x-sveltekit-routeid', encodeURI(event.routeId));
309
+ if (state.prerendering && event.route.id !== null) {
310
+ response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
310
311
  }
311
312
 
312
313
  return response;
@@ -41,10 +41,10 @@ export async function handle_action_json_request(event, options, server) {
41
41
  const data = await call_action(event, actions);
42
42
 
43
43
  if (data instanceof ValidationError) {
44
- check_serializability(data.data, /** @type {string} */ (event.routeId), 'data');
44
+ check_serializability(data.data, /** @type {string} */ (event.route.id), 'data');
45
45
  return action_json({ type: 'invalid', status: data.status, data: data.data });
46
46
  } else {
47
- check_serializability(data, /** @type {string} */ (event.routeId), 'data');
47
+ check_serializability(data, /** @type {string} */ (event.route.id), 'data');
48
48
  return action_json({
49
49
  type: 'success',
50
50
  status: data ? 200 : 204,
@@ -7,7 +7,8 @@ import {
7
7
  get_option,
8
8
  redirect_response,
9
9
  static_error_page,
10
- handle_error_and_jsonify
10
+ handle_error_and_jsonify,
11
+ serialize_data_node
11
12
  } from '../utils.js';
12
13
  import {
13
14
  handle_action_json_request,
@@ -208,7 +209,7 @@ export async function render_page(event, route, page, options, state, resolve_op
208
209
 
209
210
  if (err instanceof Redirect) {
210
211
  if (state.prerendering && should_prerender_data) {
211
- const body = devalue.stringify({
212
+ const body = JSON.stringify({
212
213
  type: 'redirect',
213
214
  location: err.location
214
215
  });
@@ -263,10 +264,9 @@ export async function render_page(event, route, page, options, state, resolve_op
263
264
  }
264
265
 
265
266
  if (state.prerendering && should_prerender_data) {
266
- const body = devalue.stringify({
267
- type: 'data',
268
- nodes: branch.map((branch_node) => branch_node?.server_data)
269
- });
267
+ const body = `{"type":"data","nodes":[${branch
268
+ .map((node) => serialize_data_node(node?.server_data))
269
+ .join(',')}]}`;
270
270
 
271
271
  state.prerendering.dependencies.set(data_pathname, {
272
272
  response: new Response(body),
@@ -17,6 +17,7 @@ export async function load_server_data({ event, state, node, parent }) {
17
17
  dependencies: new Set(),
18
18
  params: new Set(),
19
19
  parent: false,
20
+ route: false,
20
21
  url: false
21
22
  };
22
23
 
@@ -47,6 +48,12 @@ export async function load_server_data({ event, state, node, parent }) {
47
48
  uses.parent = true;
48
49
  return parent();
49
50
  },
51
+ route: {
52
+ get id() {
53
+ uses.route = true;
54
+ return event.route.id;
55
+ }
56
+ },
50
57
  url
51
58
  });
52
59
 
@@ -55,12 +62,7 @@ export async function load_server_data({ event, state, node, parent }) {
55
62
  return {
56
63
  type: 'data',
57
64
  data,
58
- uses: {
59
- dependencies: uses.dependencies.size > 0 ? Array.from(uses.dependencies) : undefined,
60
- params: uses.params.size > 0 ? Array.from(uses.params) : undefined,
61
- parent: uses.parent ? 1 : undefined,
62
- url: uses.url ? 1 : undefined
63
- }
65
+ uses
64
66
  };
65
67
  }
66
68
 
@@ -99,7 +101,7 @@ export async function load_data({
99
101
  url: event.url,
100
102
  params: event.params,
101
103
  data: server_data_node?.data ?? null,
102
- routeId: event.routeId,
104
+ route: event.route,
103
105
  fetch: async (input, init) => {
104
106
  const response = await event.fetch(input, init);
105
107
 
@@ -199,7 +201,7 @@ export async function load_data({
199
201
  const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
200
202
  if (!included) {
201
203
  throw new Error(
202
- `Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.routeId})`
204
+ `Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.route})`
203
205
  );
204
206
  }
205
207
  }
@@ -4,6 +4,7 @@ import { hash } from '../../hash.js';
4
4
  import { serialize_data } from './serialize_data.js';
5
5
  import { s } from '../../../utils/misc.js';
6
6
  import { Csp } from './csp.js';
7
+ import { clarify_devalue_error } from '../utils.js';
7
8
 
8
9
  // TODO rename this function/module
9
10
 
@@ -92,7 +93,7 @@ export async function render_response({
92
93
  props.page = {
93
94
  error,
94
95
  params: /** @type {Record<string, any>} */ (event.params),
95
- routeId: event.routeId,
96
+ route: event.route,
96
97
  status,
97
98
  url: event.url,
98
99
  data,
@@ -170,33 +171,33 @@ export async function render_response({
170
171
  const serialized = { data: '', form: 'null' };
171
172
 
172
173
  try {
173
- serialized.data = devalue.uneval(branch.map(({ server_data }) => server_data));
174
- } catch (e) {
175
- // If we're here, the data could not be serialized with devalue
176
- // TODO if we wanted to get super fancy we could track down the origin of the `load`
177
- // function, but it would mean passing more stuff around than we currently do
178
- const error = /** @type {any} */ (e);
179
- const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path);
180
- if (match) {
181
- throw new Error(
182
- `Data returned from \`load\` while rendering ${event.routeId} is not serializable: ${error.message} (data.${match[2]})`
183
- );
184
- }
174
+ serialized.data = `[${branch
175
+ .map(({ server_data }) => {
176
+ if (server_data?.type === 'data') {
177
+ const data = devalue.uneval(server_data.data);
185
178
 
186
- const nonPojoError = /pojo/i.exec(error.message);
179
+ const uses = [];
180
+ if (server_data.uses.dependencies.size > 0) {
181
+ uses.push(`dependencies:${s(Array.from(server_data.uses.dependencies))}`);
182
+ }
187
183
 
188
- if (nonPojoError) {
189
- const constructorName = branch.find(({ server_data }) => server_data?.data?.constructor?.name)
190
- ?.server_data?.data?.constructor?.name;
184
+ if (server_data.uses.params.size > 0) {
185
+ uses.push(`params:${s(Array.from(server_data.uses.params))}`);
186
+ }
191
187
 
192
- throw new Error(
193
- `Data returned from \`load\` (while rendering ${event.routeId}) must be a plain object${
194
- constructorName ? ` rather than an instance of ${constructorName}` : ''
195
- }`
196
- );
197
- }
188
+ if (server_data.uses.parent) uses.push(`parent:1`);
189
+ if (server_data.uses.route) uses.push(`route:1`);
190
+ if (server_data.uses.url) uses.push(`url:1`);
198
191
 
199
- throw error;
192
+ return `{type:"data",data:${data},uses:{${uses.join(',')}}}`;
193
+ }
194
+
195
+ return s(server_data);
196
+ })
197
+ .join(',')}]`;
198
+ } catch (e) {
199
+ const error = /** @type {any} */ (e);
200
+ throw new Error(clarify_devalue_error(event, error));
200
201
  }
201
202
 
202
203
  if (form_value) {
@@ -249,7 +250,7 @@ export async function render_response({
249
250
  error: ${devalue.uneval(error)},
250
251
  node_ids: [${branch.map(({ node }) => node.index).join(', ')}],
251
252
  params: ${devalue.uneval(event.params)},
252
- routeId: ${s(event.routeId)},
253
+ route: ${s(event.route)},
253
254
  data: ${serialized.data},
254
255
  form: ${serialized.form}
255
256
  }` : 'null'},
@@ -67,28 +67,6 @@ export function allowed_methods(mod) {
67
67
  return allowed;
68
68
  }
69
69
 
70
- /**
71
- * @param {any} data
72
- * @param {import('types').RequestEvent} event
73
- */
74
- export function data_response(data, event) {
75
- const headers = {
76
- 'content-type': 'application/json',
77
- 'cache-control': 'private, no-store'
78
- };
79
-
80
- try {
81
- return new Response(devalue.stringify(data), { headers });
82
- } catch (e) {
83
- const error = /** @type {any} */ (e);
84
- const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path);
85
- const message = match
86
- ? `Data returned from \`load\` while rendering ${event.routeId} is not serializable: ${error.message} (data.${match[2]})`
87
- : error.message;
88
- return new Response(JSON.stringify(message), { headers, status: 500 });
89
- }
90
- }
91
-
92
70
  /**
93
71
  * @template {'prerender' | 'ssr' | 'csr'} Option
94
72
  * @template {Option extends 'prerender' ? import('types').PrerenderOption : boolean} Value
@@ -179,3 +157,47 @@ export function redirect_response(status, location) {
179
157
  });
180
158
  return response;
181
159
  }
160
+
161
+ /**
162
+ * @param {import('types').RequestEvent} event
163
+ * @param {Error & { path: string }} error
164
+ */
165
+ export function clarify_devalue_error(event, error) {
166
+ if (error.path) {
167
+ return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (data${error.path})`;
168
+ }
169
+
170
+ if (error.path === '') {
171
+ return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
172
+ }
173
+
174
+ // belt and braces — this should never happen
175
+ return error.message;
176
+ }
177
+
178
+ /** @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | import('types').ServerErrorNode | null} node */
179
+ export function serialize_data_node(node) {
180
+ if (!node) return 'null';
181
+
182
+ if (node.type === 'error' || node.type === 'skip') {
183
+ return JSON.stringify(node);
184
+ }
185
+
186
+ const stringified = devalue.stringify(node.data);
187
+
188
+ const uses = [];
189
+
190
+ if (node.uses.dependencies.size > 0) {
191
+ uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
192
+ }
193
+
194
+ if (node.uses.params.size > 0) {
195
+ uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
196
+ }
197
+
198
+ if (node.uses.parent) uses.push(`"parent":1`);
199
+ if (node.uses.route) uses.push(`"route":1`);
200
+ if (node.uses.url) uses.push(`"url":1`);
201
+
202
+ return `{"type":"data","data":${stringified},"uses":{${uses.join(',')}}}`;
203
+ }
@@ -194,11 +194,11 @@ declare module '$app/navigation' {
194
194
  /**
195
195
  * If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
196
196
  */
197
- noscroll?: boolean;
197
+ noScroll?: boolean;
198
198
  /**
199
199
  * If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
200
200
  */
201
- keepfocus?: boolean;
201
+ keepFocus?: boolean;
202
202
  /**
203
203
  * The state of the new/updated history entry
204
204
  */
package/types/index.d.ts CHANGED
@@ -265,16 +265,18 @@ export interface Load<
265
265
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
266
266
  InputData extends Record<string, unknown> | null = Record<string, any> | null,
267
267
  ParentData extends Record<string, unknown> = Record<string, any>,
268
- OutputData extends Record<string, unknown> | void = Record<string, any> | void
268
+ OutputData extends Record<string, unknown> | void = Record<string, any> | void,
269
+ RouteId extends string | null = string | null
269
270
  > {
270
- (event: LoadEvent<Params, InputData, ParentData>): MaybePromise<OutputData>;
271
+ (event: LoadEvent<Params, InputData, ParentData, RouteId>): MaybePromise<OutputData>;
271
272
  }
272
273
 
273
274
  export interface LoadEvent<
274
275
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
275
276
  Data extends Record<string, unknown> | null = Record<string, any> | null,
276
- ParentData extends Record<string, unknown> = Record<string, any>
277
- > extends NavigationEvent<Params> {
277
+ ParentData extends Record<string, unknown> = Record<string, any>,
278
+ RouteId extends string | null = string | null
279
+ > extends NavigationEvent<Params, RouteId> {
278
280
  /**
279
281
  * `fetch` is equivalent to the [native `fetch` web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch), with a few additional features:
280
282
  *
@@ -364,16 +366,22 @@ export interface LoadEvent<
364
366
  }
365
367
 
366
368
  export interface NavigationEvent<
367
- Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
369
+ Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
370
+ RouteId extends string | null = string | null
368
371
  > {
369
372
  /**
370
373
  * The parameters of the current page - e.g. for a route like `/blog/[slug]`, the `slug` parameter
371
374
  */
372
375
  params: Params;
373
376
  /**
374
- * The route ID of the current page - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`
377
+ * Info about the current route
375
378
  */
376
- routeId: string | null;
379
+ route: {
380
+ /**
381
+ * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `blog/[slug]`
382
+ */
383
+ id: RouteId;
384
+ };
377
385
  /**
378
386
  * The URL of the current page
379
387
  */
@@ -382,7 +390,7 @@ export interface NavigationEvent<
382
390
 
383
391
  export interface NavigationTarget {
384
392
  params: Record<string, string> | null;
385
- routeId: string | null;
393
+ route: { id: string | null };
386
394
  url: URL;
387
395
  }
388
396
 
@@ -398,7 +406,10 @@ export interface Navigation {
398
406
  /**
399
407
  * The shape of the `$page` store
400
408
  */
401
- export interface Page<Params extends Record<string, string> = Record<string, string>> {
409
+ export interface Page<
410
+ Params extends Record<string, string> = Record<string, string>,
411
+ RouteId extends string | null = string | null
412
+ > {
402
413
  /**
403
414
  * The URL of the current page
404
415
  */
@@ -408,9 +419,14 @@ export interface Page<Params extends Record<string, string> = Record<string, str
408
419
  */
409
420
  params: Params;
410
421
  /**
411
- * The route ID of the current page - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`
422
+ * Info about the current route
412
423
  */
413
- routeId: string | null;
424
+ route: {
425
+ /**
426
+ * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `blog/[slug]`
427
+ */
428
+ id: RouteId;
429
+ };
414
430
  /**
415
431
  * Http status code of the current page
416
432
  */
@@ -434,7 +450,8 @@ export interface ParamMatcher {
434
450
  }
435
451
 
436
452
  export interface RequestEvent<
437
- Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
453
+ Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
454
+ RouteId extends string | null = string | null
438
455
  > {
439
456
  /**
440
457
  * Get or set cookies related to the current request
@@ -471,9 +488,14 @@ export interface RequestEvent<
471
488
  */
472
489
  request: Request;
473
490
  /**
474
- * The route ID of the current page - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`
491
+ * Info about the current route
475
492
  */
476
- routeId: string | null;
493
+ route: {
494
+ /**
495
+ * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `blog/[slug]`
496
+ */
497
+ id: RouteId;
498
+ };
477
499
  /**
478
500
  * If you need to set headers for the response, you can do so using the this method. This is useful if you want the page to be cached, for example:
479
501
  *
@@ -509,9 +531,10 @@ export interface RequestEvent<
509
531
  * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://kit.svelte.dev/docs/types#generated-types) instead.
510
532
  */
511
533
  export interface RequestHandler<
512
- Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
534
+ Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
535
+ RouteId extends string | null = string | null
513
536
  > {
514
- (event: RequestEvent<Params>): MaybePromise<Response>;
537
+ (event: RequestEvent<Params, RouteId>): MaybePromise<Response>;
515
538
  }
516
539
 
517
540
  export interface ResolveOptions {
@@ -555,15 +578,17 @@ export interface SSRManifest {
555
578
  export interface ServerLoad<
556
579
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
557
580
  ParentData extends Record<string, any> = Record<string, any>,
558
- OutputData extends Record<string, any> | void = Record<string, any> | void
581
+ OutputData extends Record<string, any> | void = Record<string, any> | void,
582
+ RouteId extends string | null = string | null
559
583
  > {
560
- (event: ServerLoadEvent<Params, ParentData>): MaybePromise<OutputData>;
584
+ (event: ServerLoadEvent<Params, ParentData, RouteId>): MaybePromise<OutputData>;
561
585
  }
562
586
 
563
587
  export interface ServerLoadEvent<
564
588
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
565
- ParentData extends Record<string, any> = Record<string, any>
566
- > extends RequestEvent<Params> {
589
+ ParentData extends Record<string, any> = Record<string, any>,
590
+ RouteId extends string | null = string | null
591
+ > extends RequestEvent<Params, RouteId> {
567
592
  /**
568
593
  * `await parent()` returns data from parent `+layout.server.js` `load` functions.
569
594
  *
@@ -612,15 +637,17 @@ export interface ServerLoadEvent<
612
637
 
613
638
  export interface Action<
614
639
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
615
- OutputData extends Record<string, any> | void = Record<string, any> | void
640
+ OutputData extends Record<string, any> | void = Record<string, any> | void,
641
+ RouteId extends string | null = string | null
616
642
  > {
617
- (event: RequestEvent<Params>): MaybePromise<OutputData>;
643
+ (event: RequestEvent<Params, RouteId>): MaybePromise<OutputData>;
618
644
  }
619
645
 
620
646
  export type Actions<
621
647
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
622
- OutputData extends Record<string, any> | void = Record<string, any> | void
623
- > = Record<string, Action<Params, OutputData>>;
648
+ OutputData extends Record<string, any> | void = Record<string, any> | void,
649
+ RouteId extends string | null = string | null
650
+ > = Record<string, Action<Params, OutputData, RouteId>>;
624
651
 
625
652
  /**
626
653
  * When calling a form action via fetch, the response will be one of these shapes.
@@ -207,12 +207,7 @@ export type ServerData =
207
207
  export interface ServerDataNode {
208
208
  type: 'data';
209
209
  data: Record<string, any> | null;
210
- uses: {
211
- dependencies?: string[];
212
- params?: string[];
213
- parent?: number | void; // 1 or undefined
214
- url?: number | void; // 1 or undefined
215
- };
210
+ uses: Uses;
216
211
  }
217
212
 
218
213
  /**
@@ -362,6 +357,7 @@ export interface Uses {
362
357
  dependencies: Set<string>;
363
358
  params: Set<string>;
364
359
  parent: boolean;
360
+ route: boolean;
365
361
  url: boolean;
366
362
  }
367
363