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

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/dist/ssr.js CHANGED
@@ -94,13 +94,14 @@ async function render_endpoint(request, route, match) {
94
94
  const response = await handler(request);
95
95
  const preface = `Invalid response from route ${request.url.pathname}`;
96
96
 
97
- if (!response) {
98
- return;
99
- }
100
97
  if (typeof response !== 'object') {
101
98
  return error(`${preface}: expected an object, got ${typeof response}`);
102
99
  }
103
100
 
101
+ if (response.fallthrough) {
102
+ return;
103
+ }
104
+
104
105
  let { status = 200, body, headers = {} } = response;
105
106
 
106
107
  headers = lowercase_keys(headers);
@@ -512,11 +513,13 @@ function escape(str, dict, unicode_encoder) {
512
513
  * branch: Array<import('./types').Loaded>;
513
514
  * options: import('types/internal').SSRRenderOptions;
514
515
  * $session: any;
515
- * page_config: { hydrate: boolean, router: boolean, ssr: boolean };
516
+ * page_config: { hydrate: boolean, router: boolean };
516
517
  * status: number;
517
- * error?: Error,
518
+ * error?: Error;
518
519
  * url: URL;
519
- * params: Record<string, string>
520
+ * params: Record<string, string>;
521
+ * ssr: boolean;
522
+ * stuff: Record<string, any>;
520
523
  * }} opts
521
524
  */
522
525
  async function render_response({
@@ -527,7 +530,9 @@ async function render_response({
527
530
  status,
528
531
  error,
529
532
  url,
530
- params
533
+ params,
534
+ ssr,
535
+ stuff
531
536
  }) {
532
537
  const css = new Set(options.manifest._.entry.css);
533
538
  const js = new Set(options.manifest._.entry.js);
@@ -545,7 +550,7 @@ async function render_response({
545
550
  error.stack = options.get_stack(error);
546
551
  }
547
552
 
548
- if (page_config.ssr) {
553
+ if (ssr) {
549
554
  branch.forEach(({ node, loaded, fetched, uses_credentials }) => {
550
555
  if (node.css) node.css.forEach((url) => css.add(url));
551
556
  if (node.js) node.js.forEach((url) => js.add(url));
@@ -568,7 +573,7 @@ async function render_response({
568
573
  navigating: writable(null),
569
574
  session
570
575
  },
571
- page: { url, params, status, error },
576
+ page: { url, params, status, error, stuff },
572
577
  components: branch.map(({ node }) => node.module.default)
573
578
  };
574
579
 
@@ -648,9 +653,9 @@ async function render_response({
648
653
  throw new Error(`Failed to serialize session data: ${error.message}`);
649
654
  })},
650
655
  route: ${!!page_config.router},
651
- spa: ${!page_config.ssr},
656
+ spa: ${!ssr},
652
657
  trailing_slash: ${s(options.trailing_slash)},
653
- hydrate: ${page_config.ssr && page_config.hydrate ? `{
658
+ hydrate: ${ssr && page_config.hydrate ? `{
654
659
  status: ${status},
655
660
  error: ${serialize_error(error)},
656
661
  nodes: [
@@ -834,7 +839,6 @@ function normalize(loaded) {
834
839
  * $session: any;
835
840
  * stuff: Record<string, any>;
836
841
  * prerender_enabled: boolean;
837
- * is_leaf: boolean;
838
842
  * is_error: boolean;
839
843
  * status?: number;
840
844
  * error?: Error;
@@ -852,7 +856,6 @@ async function load_node({
852
856
  $session,
853
857
  stuff,
854
858
  prerender_enabled,
855
- is_leaf,
856
859
  is_error,
857
860
  status,
858
861
  error
@@ -1112,16 +1115,16 @@ async function load_node({
1112
1115
  }
1113
1116
 
1114
1117
  loaded = await module.load.call(null, load_input);
1118
+
1119
+ if (!loaded) {
1120
+ throw new Error(`load function must return a value${options.dev ? ` (${node.entry})` : ''}`);
1121
+ }
1115
1122
  } else {
1116
1123
  loaded = {};
1117
1124
  }
1118
1125
 
1119
- // if leaf node (i.e. page component) has a load function
1120
- // that returns nothing, we fall through to the next one
1121
- if (!loaded && is_leaf && !is_error) return;
1122
-
1123
- if (!loaded) {
1124
- throw new Error(`${node.entry} - load must return a value except for page fall through`);
1126
+ if (loaded.fallthrough && !is_error) {
1127
+ return;
1125
1128
  }
1126
1129
 
1127
1130
  return {
@@ -1149,9 +1152,18 @@ async function load_node({
1149
1152
  * $session: any;
1150
1153
  * status: number;
1151
1154
  * error: Error;
1155
+ * ssr: boolean;
1152
1156
  * }} opts
1153
1157
  */
1154
- async function respond_with_error({ request, options, state, $session, status, error }) {
1158
+ async function respond_with_error({
1159
+ request,
1160
+ options,
1161
+ state,
1162
+ $session,
1163
+ status,
1164
+ error,
1165
+ ssr
1166
+ }) {
1155
1167
  try {
1156
1168
  const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
1157
1169
  const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
@@ -1159,8 +1171,7 @@ async function respond_with_error({ request, options, state, $session, status, e
1159
1171
  /** @type {Record<string, string>} */
1160
1172
  const params = {}; // error page has no params
1161
1173
 
1162
- // error pages don't fall through, so we know it's not undefined
1163
- const loaded = /** @type {Loaded} */ (
1174
+ const layout_loaded = /** @type {Loaded} */ (
1164
1175
  await load_node({
1165
1176
  request,
1166
1177
  options,
@@ -1172,46 +1183,42 @@ async function respond_with_error({ request, options, state, $session, status, e
1172
1183
  $session,
1173
1184
  stuff: {},
1174
1185
  prerender_enabled: is_prerender_enabled(options, default_error, state),
1175
- is_leaf: false,
1176
1186
  is_error: false
1177
1187
  })
1178
1188
  );
1179
1189
 
1180
- const branch = [
1181
- loaded,
1182
- /** @type {Loaded} */ (
1183
- await load_node({
1184
- request,
1185
- options,
1186
- state,
1187
- route: null,
1188
- url: request.url,
1189
- params,
1190
- node: default_error,
1191
- $session,
1192
- stuff: loaded ? loaded.stuff : {},
1193
- prerender_enabled: is_prerender_enabled(options, default_error, state),
1194
- is_leaf: false,
1195
- is_error: true,
1196
- status,
1197
- error
1198
- })
1199
- )
1200
- ];
1190
+ const error_loaded = /** @type {Loaded} */ (
1191
+ await load_node({
1192
+ request,
1193
+ options,
1194
+ state,
1195
+ route: null,
1196
+ url: request.url,
1197
+ params,
1198
+ node: default_error,
1199
+ $session,
1200
+ stuff: layout_loaded ? layout_loaded.stuff : {},
1201
+ prerender_enabled: is_prerender_enabled(options, default_error, state),
1202
+ is_error: true,
1203
+ status,
1204
+ error
1205
+ })
1206
+ );
1201
1207
 
1202
1208
  return await render_response({
1203
1209
  options,
1204
1210
  $session,
1205
1211
  page_config: {
1206
1212
  hydrate: options.hydrate,
1207
- router: options.router,
1208
- ssr: options.ssr
1213
+ router: options.router
1209
1214
  },
1215
+ stuff: error_loaded.stuff,
1210
1216
  status,
1211
1217
  error,
1212
- branch,
1218
+ branch: [layout_loaded, error_loaded],
1213
1219
  url: request.url,
1214
- params
1220
+ params,
1221
+ ssr
1215
1222
  });
1216
1223
  } catch (err) {
1217
1224
  const error = coalesce_to_error(err);
@@ -1253,15 +1260,30 @@ function is_prerender_enabled(options, node, state) {
1253
1260
  * $session: any;
1254
1261
  * route: import('types/internal').SSRPage;
1255
1262
  * params: Record<string, string>;
1263
+ * ssr: boolean;
1256
1264
  * }} opts
1257
1265
  * @returns {Promise<ServerResponse | undefined>}
1258
1266
  */
1259
1267
  async function respond$1(opts) {
1260
- const { request, options, state, $session, route } = opts;
1268
+ const { request, options, state, $session, route, ssr } = opts;
1261
1269
 
1262
1270
  /** @type {Array<SSRNode | undefined>} */
1263
1271
  let nodes;
1264
1272
 
1273
+ if (!ssr) {
1274
+ return await render_response({
1275
+ ...opts,
1276
+ branch: [],
1277
+ page_config: {
1278
+ hydrate: true,
1279
+ router: true
1280
+ },
1281
+ status: 200,
1282
+ url: request.url,
1283
+ stuff: {}
1284
+ });
1285
+ }
1286
+
1265
1287
  try {
1266
1288
  nodes = await Promise.all(
1267
1289
  route.a.map((n) => options.manifest._.nodes[n] && options.manifest._.nodes[n]())
@@ -1277,7 +1299,8 @@ async function respond$1(opts) {
1277
1299
  state,
1278
1300
  $session,
1279
1301
  status: 500,
1280
- error
1302
+ error,
1303
+ ssr
1281
1304
  });
1282
1305
  }
1283
1306
 
@@ -1307,9 +1330,9 @@ async function respond$1(opts) {
1307
1330
  /** @type {string[]} */
1308
1331
  let set_cookie_headers = [];
1309
1332
 
1310
- ssr: if (page_config.ssr) {
1311
- let stuff = {};
1333
+ let stuff = {};
1312
1334
 
1335
+ ssr: if (ssr) {
1313
1336
  for (let i = 0; i < nodes.length; i += 1) {
1314
1337
  const node = nodes[i];
1315
1338
 
@@ -1324,7 +1347,6 @@ async function respond$1(opts) {
1324
1347
  node,
1325
1348
  stuff,
1326
1349
  prerender_enabled: is_prerender_enabled(options, node, state),
1327
- is_leaf: i === nodes.length - 1,
1328
1350
  is_error: false
1329
1351
  });
1330
1352
 
@@ -1373,7 +1395,6 @@ async function respond$1(opts) {
1373
1395
  }
1374
1396
 
1375
1397
  try {
1376
- // there's no fallthough on an error page, so we know it's not undefined
1377
1398
  const error_loaded = /** @type {import('./types').Loaded} */ (
1378
1399
  await load_node({
1379
1400
  ...opts,
@@ -1381,7 +1402,6 @@ async function respond$1(opts) {
1381
1402
  node: error_node,
1382
1403
  stuff: node_loaded.stuff,
1383
1404
  prerender_enabled: is_prerender_enabled(options, error_node, state),
1384
- is_leaf: false,
1385
1405
  is_error: true,
1386
1406
  status,
1387
1407
  error
@@ -1394,6 +1414,7 @@ async function respond$1(opts) {
1394
1414
 
1395
1415
  page_config = get_page_config(error_node.module, options);
1396
1416
  branch = branch.slice(0, j + 1).concat(error_loaded);
1417
+ stuff = { ...node_loaded.stuff, ...error_loaded.stuff };
1397
1418
  break ssr;
1398
1419
  } catch (err) {
1399
1420
  const e = coalesce_to_error(err);
@@ -1415,7 +1436,8 @@ async function respond$1(opts) {
1415
1436
  state,
1416
1437
  $session,
1417
1438
  status,
1418
- error
1439
+ error,
1440
+ ssr
1419
1441
  }),
1420
1442
  set_cookie_headers
1421
1443
  );
@@ -1435,6 +1457,7 @@ async function respond$1(opts) {
1435
1457
  return with_cookies(
1436
1458
  await render_response({
1437
1459
  ...opts,
1460
+ stuff,
1438
1461
  url: request.url,
1439
1462
  page_config,
1440
1463
  status,
@@ -1464,8 +1487,14 @@ async function respond$1(opts) {
1464
1487
  * @param {SSRRenderOptions} options
1465
1488
  */
1466
1489
  function get_page_config(leaf, options) {
1490
+ // TODO remove for 1.0
1491
+ if ('ssr' in leaf) {
1492
+ throw new Error(
1493
+ '`export const ssr` has been removed — use the handle hook instead: https://kit.svelte.dev/docs#hooks-handle'
1494
+ );
1495
+ }
1496
+
1467
1497
  return {
1468
- ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr,
1469
1498
  router: 'router' in leaf ? !!leaf.router : options.router,
1470
1499
  hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate
1471
1500
  };
@@ -1488,9 +1517,10 @@ function with_cookies(response, set_cookie_headers) {
1488
1517
  * @param {RegExpExecArray} match
1489
1518
  * @param {import('types/internal').SSRRenderOptions} options
1490
1519
  * @param {import('types/internal').SSRRenderState} state
1520
+ * @param {boolean} ssr
1491
1521
  * @returns {Promise<import('types/hooks').ServerResponse | undefined>}
1492
1522
  */
1493
- async function render_page(request, route, match, options, state) {
1523
+ async function render_page(request, route, match, options, state, ssr) {
1494
1524
  if (state.initiator === route) {
1495
1525
  // infinite request cycle detected
1496
1526
  return {
@@ -1510,7 +1540,8 @@ async function render_page(request, route, match, options, state) {
1510
1540
  state,
1511
1541
  $session,
1512
1542
  route,
1513
- params
1543
+ params,
1544
+ ssr
1514
1545
  });
1515
1546
 
1516
1547
  if (response) {
@@ -1748,6 +1779,28 @@ async function respond(incoming, options, state = {}) {
1748
1779
  locals: {}
1749
1780
  };
1750
1781
 
1782
+ const { parameter, allowed } = options.method_override;
1783
+ const method_override = incoming.url.searchParams.get(parameter)?.toUpperCase();
1784
+
1785
+ if (method_override) {
1786
+ if (request.method.toUpperCase() === 'POST') {
1787
+ if (allowed.includes(method_override)) {
1788
+ request.method = method_override;
1789
+ } else {
1790
+ const verb = allowed.length === 0 ? 'enabled' : 'allowed';
1791
+ const body = `${parameter}=${method_override} is not ${verb}. See https://kit.svelte.dev/docs#configuration-methodoverride`;
1792
+
1793
+ return {
1794
+ status: 400,
1795
+ headers: {},
1796
+ body
1797
+ };
1798
+ }
1799
+ } else {
1800
+ throw new Error(`${parameter}=${method_override} is only allowed with POST requests`);
1801
+ }
1802
+ }
1803
+
1751
1804
  // TODO remove this for 1.0
1752
1805
  /**
1753
1806
  * @param {string} property
@@ -1765,19 +1818,25 @@ async function respond(incoming, options, state = {}) {
1765
1818
  print_error('path', 'pathname');
1766
1819
  print_error('query', 'searchParams');
1767
1820
 
1821
+ let ssr = true;
1822
+
1768
1823
  try {
1769
1824
  return await options.hooks.handle({
1770
1825
  request,
1771
- resolve: async (request) => {
1826
+ resolve: async (request, opts) => {
1827
+ if (opts && 'ssr' in opts) ssr = /** @type {boolean} */ (opts.ssr);
1828
+
1772
1829
  if (state.prerender && state.prerender.fallback) {
1773
1830
  return await render_response({
1774
1831
  url: request.url,
1775
1832
  params: request.params,
1776
1833
  options,
1777
1834
  $session: await options.hooks.getSession(request),
1778
- page_config: { ssr: false, router: true, hydrate: true },
1835
+ page_config: { router: true, hydrate: true },
1836
+ stuff: {},
1779
1837
  status: 200,
1780
- branch: []
1838
+ branch: [],
1839
+ ssr: false
1781
1840
  });
1782
1841
  }
1783
1842
 
@@ -1790,7 +1849,7 @@ async function respond(incoming, options, state = {}) {
1790
1849
  const response =
1791
1850
  route.type === 'endpoint'
1792
1851
  ? await render_endpoint(request, route, match)
1793
- : await render_page(request, route, match, options, state);
1852
+ : await render_page(request, route, match, options, state, ssr);
1794
1853
 
1795
1854
  if (response) {
1796
1855
  // inject ETags for 200 responses
@@ -1830,7 +1889,8 @@ async function respond(incoming, options, state = {}) {
1830
1889
  state,
1831
1890
  $session,
1832
1891
  status: 404,
1833
- error: new Error(`Not found: ${request.url.pathname}`)
1892
+ error: new Error(`Not found: ${request.url.pathname}`),
1893
+ ssr
1834
1894
  });
1835
1895
  }
1836
1896
  }
@@ -1848,7 +1908,8 @@ async function respond(incoming, options, state = {}) {
1848
1908
  state,
1849
1909
  $session,
1850
1910
  status: 500,
1851
- error
1911
+ error,
1912
+ ssr
1852
1913
  });
1853
1914
  } catch (/** @type {unknown} */ e) {
1854
1915
  const error = coalesce_to_error(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.220",
3
+ "version": "1.0.0-next.224",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -35,7 +35,7 @@
35
35
  "node-fetch": "^3.1.0",
36
36
  "port-authority": "^1.1.2",
37
37
  "rollup": "^2.60.2",
38
- "selfsigned": "^1.10.11",
38
+ "selfsigned": "^2.0.0",
39
39
  "sirv": "^2.0.0",
40
40
  "svelte": "^3.44.2",
41
41
  "svelte-check": "^2.2.10",
@@ -110,6 +110,7 @@ declare module '$app/stores' {
110
110
  export const page: Readable<{
111
111
  url: URL;
112
112
  params: Record<string, string>;
113
+ stuff: Record<string, any>;
113
114
  status: number;
114
115
  error: Error | null;
115
116
  }>;
package/types/config.d.ts CHANGED
@@ -131,6 +131,10 @@ export interface Config {
131
131
  };
132
132
  host?: string;
133
133
  hydrate?: boolean;
134
+ methodOverride?: {
135
+ parameter?: string;
136
+ allowed?: string[];
137
+ };
134
138
  package?: {
135
139
  dir?: string;
136
140
  emitTypes?: boolean;
@@ -154,7 +158,6 @@ export interface Config {
154
158
  register?: boolean;
155
159
  files?: (filepath: string) => boolean;
156
160
  };
157
- ssr?: boolean;
158
161
  target?: string;
159
162
  trailingSlash?: TrailingSlash;
160
163
  vite?: ViteConfig | (() => ViteConfig);
@@ -1,5 +1,5 @@
1
1
  import { ServerRequest } from './hooks';
2
- import { JSONString, MaybePromise, ResponseHeaders } from './helper';
2
+ import { JSONString, MaybePromise, ResponseHeaders, Either, Fallthrough } from './helper';
3
3
 
4
4
  type DefaultBody = JSONString | Uint8Array;
5
5
 
@@ -14,5 +14,7 @@ export interface RequestHandler<
14
14
  Input = unknown,
15
15
  Output extends DefaultBody = DefaultBody
16
16
  > {
17
- (request: ServerRequest<Locals, Input>): MaybePromise<void | EndpointOutput<Output>>;
17
+ (request: ServerRequest<Locals, Input>): MaybePromise<
18
+ Either<EndpointOutput<Output>, Fallthrough>
19
+ >;
18
20
  }
package/types/helper.d.ts CHANGED
@@ -39,3 +39,15 @@ export type RecursiveRequired<T> = {
39
39
  ? Extract<T[K], Function> // only take the Function type.
40
40
  : T[K]; // Use the exact type for everything else
41
41
  };
42
+
43
+ type Only<T, U> = {
44
+ [P in keyof T]: T[P];
45
+ } & {
46
+ [P in keyof U]?: never;
47
+ };
48
+
49
+ export type Either<T, U> = Only<T, U> | Only<U, T>;
50
+
51
+ export interface Fallthrough {
52
+ fallthrough: true;
53
+ }
package/types/hooks.d.ts CHANGED
@@ -23,10 +23,14 @@ export interface GetSession<Locals = Record<string, any>, Body = unknown, Sessio
23
23
  (request: ServerRequest<Locals, Body>): MaybePromise<Session>;
24
24
  }
25
25
 
26
+ export interface ResolveOpts {
27
+ ssr?: boolean;
28
+ }
29
+
26
30
  export interface Handle<Locals = Record<string, any>, Body = unknown> {
27
31
  (input: {
28
32
  request: ServerRequest<Locals, Body>;
29
- resolve(request: ServerRequest<Locals, Body>): MaybePromise<ServerResponse>;
33
+ resolve(request: ServerRequest<Locals, Body>, opts?: ResolveOpts): MaybePromise<ServerResponse>;
30
34
  }): MaybePromise<ServerResponse>;
31
35
  }
32
36
 
@@ -35,7 +39,10 @@ export interface Handle<Locals = Record<string, any>, Body = unknown> {
35
39
  export interface InternalHandle<Locals = Record<string, any>, Body = unknown> {
36
40
  (input: {
37
41
  request: ServerRequest<Locals, Body>;
38
- resolve(request: ServerRequest<Locals, Body>): MaybePromise<ServerResponse | undefined>;
42
+ resolve(
43
+ request: ServerRequest<Locals, Body>,
44
+ opts?: ResolveOpts
45
+ ): MaybePromise<ServerResponse | undefined>;
39
46
  }): MaybePromise<ServerResponse | undefined>;
40
47
  }
41
48
 
package/types/index.d.ts CHANGED
@@ -13,5 +13,6 @@ export {
13
13
  Handle,
14
14
  HandleError,
15
15
  ServerRequest as Request,
16
- ServerResponse as Response
16
+ ServerResponse as Response,
17
+ ResolveOpts
17
18
  } from './hooks';
@@ -10,6 +10,7 @@ import {
10
10
  ServerResponse
11
11
  } from './hooks';
12
12
  import { Load } from './page';
13
+ import { Either, Fallthrough } from './helper';
13
14
 
14
15
  type PageId = string;
15
16
 
@@ -43,11 +44,9 @@ export interface Logger {
43
44
  }
44
45
 
45
46
  export interface SSRComponent {
46
- ssr?: boolean;
47
47
  router?: boolean;
48
48
  hydrate?: boolean;
49
49
  prerender?: boolean;
50
- preload?: any; // TODO remove for 1.0
51
50
  load: Load;
52
51
  default: {
53
52
  render(props: Record<string, any>): {
@@ -132,6 +131,7 @@ export interface SSRRenderOptions {
132
131
  hooks: Hooks;
133
132
  hydrate: boolean;
134
133
  manifest: SSRManifest;
134
+ method_override: MethodOverride;
135
135
  paths: {
136
136
  base: string;
137
137
  assets: string;
@@ -142,7 +142,6 @@ export interface SSRRenderOptions {
142
142
  root: SSRComponent['default'];
143
143
  router: boolean;
144
144
  service_worker?: string;
145
- ssr: boolean;
146
145
  target: string;
147
146
  template({ head, body, assets }: { head: string; body: string; assets: string }): string;
148
147
  trailing_slash: TrailingSlash;
@@ -219,13 +218,20 @@ export interface BuildData {
219
218
  entries: string[];
220
219
  }
221
220
 
222
- export interface NormalizedLoadOutput {
223
- status: number;
224
- error?: Error;
225
- redirect?: string;
226
- props?: Record<string, any> | Promise<Record<string, any>>;
227
- stuff?: Record<string, any>;
228
- maxage?: number;
229
- }
221
+ export type NormalizedLoadOutput = Either<
222
+ {
223
+ status: number;
224
+ error?: Error;
225
+ redirect?: string;
226
+ props?: Record<string, any> | Promise<Record<string, any>>;
227
+ stuff?: Record<string, any>;
228
+ maxage?: number;
229
+ },
230
+ Fallthrough
231
+ >;
230
232
 
231
233
  export type TrailingSlash = 'never' | 'always' | 'ignore';
234
+ export interface MethodOverride {
235
+ parameter: string;
236
+ allowed: string[];
237
+ }
package/types/page.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { InferValue, MaybePromise, Rec } from './helper';
1
+ import { InferValue, MaybePromise, Rec, Either, Fallthrough } from './helper';
2
2
 
3
3
  export interface LoadInput<
4
4
  PageParams extends Rec<string> = Rec<string>,
@@ -51,10 +51,12 @@ export interface Load<
51
51
  InferValue<Input, 'stuff', Rec>,
52
52
  InferValue<Input, 'session', any>
53
53
  >
54
- ): MaybePromise<void | LoadOutput<
55
- InferValue<Output, 'props', Rec>,
56
- InferValue<Output, 'stuff', Rec>
57
- >>;
54
+ ): MaybePromise<
55
+ Either<
56
+ LoadOutput<InferValue<Output, 'props', Rec>, InferValue<Output, 'stuff', Rec>>,
57
+ Fallthrough
58
+ >
59
+ >;
58
60
  }
59
61
 
60
62
  export interface ErrorLoad<