@sveltejs/kit 1.0.0-next.298 → 1.0.0-next.299

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.
@@ -88,10 +88,8 @@ function is_pojo(body) {
88
88
 
89
89
  return true;
90
90
  }
91
- /**
92
- * @param {import('types').RequestEvent} event
93
- * @returns string
94
- */
91
+
92
+ /** @param {import('types').RequestEvent} event */
95
93
  function normalize_request_method(event) {
96
94
  const method = event.request.method.toLowerCase();
97
95
  return method === 'delete' ? 'del' : method; // 'delete' is a reserved word
@@ -132,7 +130,7 @@ function is_text(content_type) {
132
130
  /**
133
131
  * @param {import('types').RequestEvent} event
134
132
  * @param {{ [method: string]: import('types').RequestHandler }} mod
135
- * @returns {Promise<Response | undefined>}
133
+ * @returns {Promise<Response>}
136
134
  */
137
135
  async function render_endpoint(event, mod) {
138
136
  const method = normalize_request_method(event);
@@ -143,8 +141,17 @@ async function render_endpoint(event, mod) {
143
141
  if (!handler && method === 'head') {
144
142
  handler = mod.get;
145
143
  }
144
+
146
145
  if (!handler) {
147
- return;
146
+ return event.request.headers.get('x-sveltekit-load')
147
+ ? // TODO would be nice to avoid these requests altogether,
148
+ // by noting whether or not page endpoints export `get`
149
+ new Response(undefined, {
150
+ status: 204
151
+ })
152
+ : new Response('Method not allowed', {
153
+ status: 405
154
+ });
148
155
  }
149
156
 
150
157
  const response = await handler(event);
@@ -154,8 +161,12 @@ async function render_endpoint(event, mod) {
154
161
  return error(`${preface}: expected an object, got ${typeof response}`);
155
162
  }
156
163
 
164
+ // TODO remove for 1.0
165
+ // @ts-expect-error
157
166
  if (response.fallthrough) {
158
- return;
167
+ throw new Error(
168
+ 'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
169
+ );
159
170
  }
160
171
 
161
172
  const { status = 200, body = {} } = response;
@@ -1081,9 +1092,8 @@ const updated = {
1081
1092
  * $session: any;
1082
1093
  * page_config: { hydrate: boolean, router: boolean };
1083
1094
  * status: number;
1084
- * error?: Error;
1085
- * url: URL;
1086
- * params: Record<string, string>;
1095
+ * error: Error | null;
1096
+ * event: import('types').RequestEvent;
1087
1097
  * resolve_opts: import('types').RequiredResolveOptions;
1088
1098
  * stuff: Record<string, any>;
1089
1099
  * }} opts
@@ -1095,9 +1105,8 @@ async function render_response({
1095
1105
  $session,
1096
1106
  page_config,
1097
1107
  status,
1098
- error,
1099
- url,
1100
- params,
1108
+ error = null,
1109
+ event,
1101
1110
  resolve_opts,
1102
1111
  stuff
1103
1112
  }) {
@@ -1155,12 +1164,14 @@ async function render_response({
1155
1164
  session,
1156
1165
  updated
1157
1166
  },
1167
+ /** @type {import('types').Page} */
1158
1168
  page: {
1159
- url: state.prerender ? create_prerendering_url_proxy(url) : url,
1160
- params,
1161
- status,
1162
1169
  error,
1163
- stuff
1170
+ params: event.params,
1171
+ routeId: event.routeId,
1172
+ status,
1173
+ stuff,
1174
+ url: state.prerender ? create_prerendering_url_proxy(event.url) : event.url
1164
1175
  },
1165
1176
  components: branch.map(({ node }) => node.module.default)
1166
1177
  };
@@ -1236,7 +1247,8 @@ async function render_response({
1236
1247
  .map(({ node }) => `import(${s(options.prefix + node.entry)})`)
1237
1248
  .join(',\n\t\t\t\t\t\t')}
1238
1249
  ],
1239
- params: ${devalue(params)}
1250
+ params: ${devalue(event.params)},
1251
+ routeId: ${s(event.routeId)}
1240
1252
  }` : 'null'}
1241
1253
  });
1242
1254
  `;
@@ -1352,7 +1364,7 @@ async function render_response({
1352
1364
  }
1353
1365
  }
1354
1366
 
1355
- const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
1367
+ const segments = event.url.pathname.slice(options.paths.base.length).split('/').slice(2);
1356
1368
  const assets =
1357
1369
  options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
1358
1370
 
@@ -1541,8 +1553,6 @@ function normalize_path(path, trailing_slash) {
1541
1553
  * options: import('types').SSROptions;
1542
1554
  * state: import('types').SSRState;
1543
1555
  * route: import('types').SSRPage | null;
1544
- * url: URL;
1545
- * params: Record<string, string>;
1546
1556
  * node: import('types').SSRNode;
1547
1557
  * $session: any;
1548
1558
  * stuff: Record<string, any>;
@@ -1551,15 +1561,13 @@ function normalize_path(path, trailing_slash) {
1551
1561
  * status?: number;
1552
1562
  * error?: Error;
1553
1563
  * }} opts
1554
- * @returns {Promise<import('./types').Loaded | undefined>} undefined for fallthrough
1564
+ * @returns {Promise<import('./types').Loaded>}
1555
1565
  */
1556
1566
  async function load_node({
1557
1567
  event,
1558
1568
  options,
1559
1569
  state,
1560
1570
  route,
1561
- url,
1562
- params,
1563
1571
  node,
1564
1572
  $session,
1565
1573
  stuff,
@@ -1580,7 +1588,7 @@ async function load_node({
1580
1588
  */
1581
1589
  let set_cookie_headers = [];
1582
1590
 
1583
- /** @type {import('types').Either<import('types').Fallthrough, import('types').LoadOutput>} */
1591
+ /** @type {import('types').LoadOutput} */
1584
1592
  let loaded;
1585
1593
 
1586
1594
  /** @type {import('types').ShadowData} */
@@ -1593,8 +1601,6 @@ async function load_node({
1593
1601
  )
1594
1602
  : {};
1595
1603
 
1596
- if (shadow.fallthrough) return;
1597
-
1598
1604
  if (shadow.cookies) {
1599
1605
  set_cookie_headers.push(...shadow.cookies);
1600
1606
  }
@@ -1612,9 +1618,10 @@ async function load_node({
1612
1618
  } else if (module.load) {
1613
1619
  /** @type {import('types').LoadInput | import('types').ErrorLoadInput} */
1614
1620
  const load_input = {
1615
- url: state.prerender ? create_prerendering_url_proxy(url) : url,
1616
- params,
1621
+ url: state.prerender ? create_prerendering_url_proxy(event.url) : event.url,
1622
+ params: event.params,
1617
1623
  props: shadow.body || {},
1624
+ routeId: event.routeId,
1618
1625
  get session() {
1619
1626
  uses_credentials = true;
1620
1627
  return $session;
@@ -1692,7 +1699,10 @@ async function load_node({
1692
1699
  headers: type ? { 'content-type': type } : {}
1693
1700
  });
1694
1701
  } else {
1695
- response = await fetch(`${url.origin}/${file}`, /** @type {RequestInit} */ (opts));
1702
+ response = await fetch(
1703
+ `${event.url.origin}/${file}`,
1704
+ /** @type {RequestInit} */ (opts)
1705
+ );
1696
1706
  }
1697
1707
  } else if (is_root_relative(resolved)) {
1698
1708
  if (opts.credentials !== 'omit') {
@@ -1719,7 +1729,6 @@ async function load_node({
1719
1729
  }
1720
1730
 
1721
1731
  response = await respond(new Request(new URL(requested, event.url).href, opts), options, {
1722
- fetched: requested,
1723
1732
  getClientAddress: state.getClientAddress,
1724
1733
  initiator: route,
1725
1734
  prerender: state.prerender
@@ -1855,8 +1864,17 @@ async function load_node({
1855
1864
  loaded = await module.load.call(null, load_input);
1856
1865
 
1857
1866
  if (!loaded) {
1867
+ // TODO do we still want to enforce this now that there's no fallthrough?
1858
1868
  throw new Error(`load function must return a value${options.dev ? ` (${node.entry})` : ''}`);
1859
1869
  }
1870
+
1871
+ // TODO remove for 1.0
1872
+ // @ts-expect-error
1873
+ if (loaded.fallthrough) {
1874
+ throw new Error(
1875
+ 'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
1876
+ );
1877
+ }
1860
1878
  } else if (shadow.body) {
1861
1879
  loaded = {
1862
1880
  props: shadow.body
@@ -1865,10 +1883,6 @@ async function load_node({
1865
1883
  loaded = {};
1866
1884
  }
1867
1885
 
1868
- if (loaded.fallthrough && !is_error) {
1869
- return;
1870
- }
1871
-
1872
1886
  // generate __data.json files when prerendering
1873
1887
  if (shadow.body && state.prerender) {
1874
1888
  const pathname = `${event.url.pathname.replace(/\/$/, '')}/__data.json`;
@@ -1931,7 +1945,13 @@ async function load_shadow_data(route, event, options, prerender) {
1931
1945
  if (!is_get) {
1932
1946
  const result = await handler(event);
1933
1947
 
1934
- if (result.fallthrough) return result;
1948
+ // TODO remove for 1.0
1949
+ // @ts-expect-error
1950
+ if (result.fallthrough) {
1951
+ throw new Error(
1952
+ 'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
1953
+ );
1954
+ }
1935
1955
 
1936
1956
  const { status, headers, body } = validate_shadow_output(result);
1937
1957
  data.status = status;
@@ -1956,7 +1976,13 @@ async function load_shadow_data(route, event, options, prerender) {
1956
1976
  if (get) {
1957
1977
  const result = await get(event);
1958
1978
 
1959
- if (result.fallthrough) return result;
1979
+ // TODO remove for 1.0
1980
+ // @ts-expect-error
1981
+ if (result.fallthrough) {
1982
+ throw new Error(
1983
+ 'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
1984
+ );
1985
+ }
1960
1986
 
1961
1987
  const { status, headers, body } = validate_shadow_output(result);
1962
1988
  add_cookies(/** @type {string[]} */ (data.cookies), headers);
@@ -2058,17 +2084,12 @@ async function respond_with_error({
2058
2084
  const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
2059
2085
  const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
2060
2086
 
2061
- /** @type {Record<string, string>} */
2062
- const params = {}; // error page has no params
2063
-
2064
2087
  const layout_loaded = /** @type {Loaded} */ (
2065
2088
  await load_node({
2066
2089
  event,
2067
2090
  options,
2068
2091
  state,
2069
2092
  route: null,
2070
- url: event.url, // TODO this is redundant, no?
2071
- params,
2072
2093
  node: default_layout,
2073
2094
  $session,
2074
2095
  stuff: {},
@@ -2083,8 +2104,6 @@ async function respond_with_error({
2083
2104
  options,
2084
2105
  state,
2085
2106
  route: null,
2086
- url: event.url,
2087
- params,
2088
2107
  node: default_error,
2089
2108
  $session,
2090
2109
  stuff: layout_loaded ? layout_loaded.stuff : {},
@@ -2107,8 +2126,7 @@ async function respond_with_error({
2107
2126
  status,
2108
2127
  error,
2109
2128
  branch: [layout_loaded, error_loaded],
2110
- url: event.url,
2111
- params,
2129
+ event,
2112
2130
  resolve_opts
2113
2131
  });
2114
2132
  } catch (err) {
@@ -2137,9 +2155,8 @@ async function respond_with_error({
2137
2155
  * $session: any;
2138
2156
  * resolve_opts: import('types').RequiredResolveOptions;
2139
2157
  * route: import('types').SSRPage;
2140
- * params: Record<string, string>;
2141
2158
  * }} opts
2142
- * @returns {Promise<Response | undefined>}
2159
+ * @returns {Promise<Response>}
2143
2160
  */
2144
2161
  async function respond$1(opts) {
2145
2162
  const { event, options, state, $session, route, resolve_opts } = opts;
@@ -2156,7 +2173,8 @@ async function respond$1(opts) {
2156
2173
  router: true
2157
2174
  },
2158
2175
  status: 200,
2159
- url: event.url,
2176
+ error: null,
2177
+ event,
2160
2178
  stuff: {}
2161
2179
  });
2162
2180
  }
@@ -2204,8 +2222,8 @@ async function respond$1(opts) {
2204
2222
  /** @type {number} */
2205
2223
  let status = 200;
2206
2224
 
2207
- /** @type {Error|undefined} */
2208
- let error;
2225
+ /** @type {Error | null} */
2226
+ let error = null;
2209
2227
 
2210
2228
  /** @type {string[]} */
2211
2229
  let set_cookie_headers = [];
@@ -2223,15 +2241,12 @@ async function respond$1(opts) {
2223
2241
  try {
2224
2242
  loaded = await load_node({
2225
2243
  ...opts,
2226
- url: event.url,
2227
2244
  node,
2228
2245
  stuff,
2229
2246
  is_error: false,
2230
2247
  is_leaf: i === nodes.length - 1
2231
2248
  });
2232
2249
 
2233
- if (!loaded) return;
2234
-
2235
2250
  set_cookie_headers = set_cookie_headers.concat(loaded.set_cookie_headers);
2236
2251
 
2237
2252
  if (loaded.loaded.redirect) {
@@ -2278,7 +2293,6 @@ async function respond$1(opts) {
2278
2293
  const error_loaded = /** @type {import('./types').Loaded} */ (
2279
2294
  await load_node({
2280
2295
  ...opts,
2281
- url: event.url,
2282
2296
  node: error_node,
2283
2297
  stuff: node_loaded.stuff,
2284
2298
  is_error: true,
@@ -2338,7 +2352,7 @@ async function respond$1(opts) {
2338
2352
  await render_response({
2339
2353
  ...opts,
2340
2354
  stuff,
2341
- url: event.url,
2355
+ event,
2342
2356
  page_config,
2343
2357
  status,
2344
2358
  error,
@@ -2399,7 +2413,7 @@ function with_cookies(response, set_cookie_headers) {
2399
2413
  * @param {import('types').SSROptions} options
2400
2414
  * @param {import('types').SSRState} state
2401
2415
  * @param {import('types').RequiredResolveOptions} resolve_opts
2402
- * @returns {Promise<Response | undefined>}
2416
+ * @returns {Promise<Response>}
2403
2417
  */
2404
2418
  async function render_page(event, route, options, state, resolve_opts) {
2405
2419
  if (state.initiator === route) {
@@ -2422,29 +2436,14 @@ async function render_page(event, route, options, state, resolve_opts) {
2422
2436
 
2423
2437
  const $session = await options.hooks.getSession(event);
2424
2438
 
2425
- const response = await respond$1({
2439
+ return respond$1({
2426
2440
  event,
2427
2441
  options,
2428
2442
  state,
2429
2443
  $session,
2430
2444
  resolve_opts,
2431
- route,
2432
- params: event.params // TODO this is redundant
2445
+ route
2433
2446
  });
2434
-
2435
- if (response) {
2436
- return response;
2437
- }
2438
-
2439
- if (state.fetched) {
2440
- // we came here because of a bad request in a `load` function.
2441
- // rather than render the error page — which could lead to an
2442
- // infinite loop, if the `load` belonged to the root layout,
2443
- // we respond with a bare-bones 500
2444
- return new Response(`Bad request in load function: failed to fetch ${state.fetched}`, {
2445
- status: 500
2446
- });
2447
- }
2448
2447
  }
2449
2448
 
2450
2449
  /**
@@ -2499,6 +2498,36 @@ function negotiate(accept, types) {
2499
2498
  return accepted;
2500
2499
  }
2501
2500
 
2501
+ /** @param {string} key */
2502
+
2503
+ /**
2504
+ * @param {RegExpMatchArray} match
2505
+ * @param {string[]} names
2506
+ * @param {string[]} types
2507
+ * @param {Record<string, import('types').ParamValidator>} validators
2508
+ */
2509
+ function exec(match, names, types, validators) {
2510
+ /** @type {Record<string, string>} */
2511
+ const params = {};
2512
+
2513
+ for (let i = 0; i < names.length; i += 1) {
2514
+ const name = names[i];
2515
+ const type = types[i];
2516
+ const value = match[i + 1] || '';
2517
+
2518
+ if (type) {
2519
+ const validator = validators[type];
2520
+ if (!validator) throw new Error(`Missing "${type}" param validator`); // TODO do this ahead of time?
2521
+
2522
+ if (!validator(value)) return;
2523
+ }
2524
+
2525
+ params[name] = value;
2526
+ }
2527
+
2528
+ return params;
2529
+ }
2530
+
2502
2531
  const DATA_SUFFIX = '/__data.json';
2503
2532
 
2504
2533
  /** @param {{ html: string }} opts */
@@ -2506,11 +2535,11 @@ const default_transform = ({ html }) => html;
2506
2535
 
2507
2536
  /** @type {import('types').Respond} */
2508
2537
  async function respond(request, options, state) {
2509
- const url = new URL(request.url);
2538
+ let url = new URL(request.url);
2510
2539
 
2511
2540
  const normalized = normalize_path(url.pathname, options.trailing_slash);
2512
2541
 
2513
- if (normalized !== url.pathname) {
2542
+ if (normalized !== url.pathname && !state.prerender?.fallback) {
2514
2543
  return new Response(undefined, {
2515
2544
  status: 301,
2516
2545
  headers: {
@@ -2544,6 +2573,50 @@ async function respond(request, options, state) {
2544
2573
  }
2545
2574
  }
2546
2575
 
2576
+ let decoded = decodeURI(url.pathname);
2577
+
2578
+ /** @type {import('types').SSRRoute | null} */
2579
+ let route = null;
2580
+
2581
+ /** @type {Record<string, string>} */
2582
+ let params = {};
2583
+
2584
+ if (options.paths.base) {
2585
+ if (!decoded.startsWith(options.paths.base)) {
2586
+ return new Response(undefined, { status: 404 });
2587
+ }
2588
+ decoded = decoded.slice(options.paths.base.length) || '/';
2589
+ }
2590
+
2591
+ const is_data_request = decoded.endsWith(DATA_SUFFIX);
2592
+
2593
+ if (is_data_request) {
2594
+ decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2595
+
2596
+ const normalized = normalize_path(
2597
+ url.pathname.slice(0, -DATA_SUFFIX.length),
2598
+ options.trailing_slash
2599
+ );
2600
+
2601
+ url = new URL(url.origin + normalized + url.search);
2602
+ }
2603
+
2604
+ if (!state.prerender || !state.prerender.fallback) {
2605
+ const validators = await options.manifest._.validators();
2606
+
2607
+ for (const candidate of options.manifest._.routes) {
2608
+ const match = candidate.pattern.exec(decoded);
2609
+ if (!match) continue;
2610
+
2611
+ const matched = exec(match, candidate.names, candidate.types, validators);
2612
+ if (matched) {
2613
+ route = candidate;
2614
+ params = decode_params(matched);
2615
+ break;
2616
+ }
2617
+ }
2618
+ }
2619
+
2547
2620
  /** @type {import('types').RequestEvent} */
2548
2621
  const event = {
2549
2622
  get clientAddress() {
@@ -2562,9 +2635,10 @@ async function respond(request, options, state) {
2562
2635
  return event.clientAddress;
2563
2636
  },
2564
2637
  locals: {},
2565
- params: {},
2638
+ params,
2566
2639
  platform: state.platform,
2567
2640
  request,
2641
+ routeId: route && route.id,
2568
2642
  url
2569
2643
  };
2570
2644
 
@@ -2607,6 +2681,8 @@ async function respond(request, options, state) {
2607
2681
  transformPage: default_transform
2608
2682
  };
2609
2683
 
2684
+ // TODO match route before calling handle?
2685
+
2610
2686
  try {
2611
2687
  const response = await options.hooks.handle({
2612
2688
  event,
@@ -2620,14 +2696,14 @@ async function respond(request, options, state) {
2620
2696
 
2621
2697
  if (state.prerender && state.prerender.fallback) {
2622
2698
  return await render_response({
2623
- url: event.url,
2624
- params: event.params,
2699
+ event,
2625
2700
  options,
2626
2701
  state,
2627
2702
  $session: await options.hooks.getSession(event),
2628
2703
  page_config: { router: true, hydrate: true },
2629
2704
  stuff: {},
2630
2705
  status: 200,
2706
+ error: null,
2631
2707
  branch: [],
2632
2708
  resolve_opts: {
2633
2709
  ...resolve_opts,
@@ -2636,75 +2712,28 @@ async function respond(request, options, state) {
2636
2712
  });
2637
2713
  }
2638
2714
 
2639
- let decoded = decodeURI(event.url.pathname);
2640
-
2641
- if (options.paths.base) {
2642
- if (!decoded.startsWith(options.paths.base)) {
2643
- return new Response(undefined, { status: 404 });
2644
- }
2645
- decoded = decoded.slice(options.paths.base.length) || '/';
2646
- }
2647
-
2648
- const is_data_request = decoded.endsWith(DATA_SUFFIX);
2649
-
2650
- if (is_data_request) {
2651
- decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2652
-
2653
- const normalized = normalize_path(
2654
- url.pathname.slice(0, -DATA_SUFFIX.length),
2655
- options.trailing_slash
2656
- );
2657
-
2658
- event.url = new URL(event.url.origin + normalized + event.url.search);
2659
- }
2660
-
2661
- // `key` will be set if this request came from a client-side navigation
2662
- // to a page with a matching endpoint
2663
- const key = request.headers.get('x-sveltekit-load');
2664
-
2665
- for (const route of options.manifest._.routes) {
2666
- if (key) {
2667
- // client is requesting data for a specific endpoint
2668
- if (route.type !== 'page') continue;
2669
- if (route.key !== key) continue;
2670
- }
2671
-
2672
- const match = route.pattern.exec(decoded);
2673
- if (!match) continue;
2674
-
2675
- event.params = route.params ? decode_params(route.params(match)) : {};
2676
-
2677
- /** @type {Response | undefined} */
2715
+ if (route) {
2716
+ /** @type {Response} */
2678
2717
  let response;
2679
2718
 
2680
2719
  if (is_data_request && route.type === 'page' && route.shadow) {
2681
2720
  response = await render_endpoint(event, await route.shadow());
2682
2721
 
2683
2722
  // loading data for a client-side transition is a special case
2684
- if (key) {
2685
- if (response) {
2686
- // since redirects are opaque to the browser, we need to repackage
2687
- // 3xx responses as 200s with a custom header
2688
- if (response.status >= 300 && response.status < 400) {
2689
- const location = response.headers.get('location');
2690
-
2691
- if (location) {
2692
- const headers = new Headers(response.headers);
2693
- headers.set('x-sveltekit-location', location);
2694
- response = new Response(undefined, {
2695
- status: 204,
2696
- headers
2697
- });
2698
- }
2723
+ if (request.headers.has('x-sveltekit-load')) {
2724
+ // since redirects are opaque to the browser, we need to repackage
2725
+ // 3xx responses as 200s with a custom header
2726
+ if (response.status >= 300 && response.status < 400) {
2727
+ const location = response.headers.get('location');
2728
+
2729
+ if (location) {
2730
+ const headers = new Headers(response.headers);
2731
+ headers.set('x-sveltekit-location', location);
2732
+ response = new Response(undefined, {
2733
+ status: 204,
2734
+ headers
2735
+ });
2699
2736
  }
2700
- } else {
2701
- // fallthrough
2702
- response = new Response(undefined, {
2703
- status: 204,
2704
- headers: {
2705
- 'content-type': 'application/json'
2706
- }
2707
- });
2708
2737
  }
2709
2738
  }
2710
2739
  } else {
@@ -11,6 +11,7 @@ import { update, init } from './sync.js';
11
11
  import { getRequest, setResponse } from '../node.js';
12
12
  import { sequence } from '../hooks.js';
13
13
  import { p as posixify } from './filesystem.js';
14
+ import { p as parse_route_id } from './routing.js';
14
15
  import 'sade';
15
16
  import 'child_process';
16
17
  import 'net';
@@ -116,12 +117,15 @@ async function create_plugin(config, cwd) {
116
117
  };
117
118
  }),
118
119
  routes: manifest_data.routes.map((route) => {
120
+ const { pattern, names, types } = parse_route_id(route.id);
121
+
119
122
  if (route.type === 'page') {
120
123
  return {
121
124
  type: 'page',
122
- key: route.key,
123
- pattern: route.pattern,
124
- params: get_params(route.params),
125
+ id: route.id,
126
+ pattern,
127
+ names,
128
+ types,
125
129
  shadow: route.shadow
126
130
  ? async () => {
127
131
  const url = path__default.resolve(cwd, /** @type {string} */ (route.shadow));
@@ -135,14 +139,34 @@ async function create_plugin(config, cwd) {
135
139
 
136
140
  return {
137
141
  type: 'endpoint',
138
- pattern: route.pattern,
139
- params: get_params(route.params),
142
+ id: route.id,
143
+ pattern,
144
+ names,
145
+ types,
140
146
  load: async () => {
141
147
  const url = path__default.resolve(cwd, route.file);
142
148
  return await vite.ssrLoadModule(url);
143
149
  }
144
150
  };
145
- })
151
+ }),
152
+ validators: async () => {
153
+ /** @type {Record<string, import('types').ParamValidator>} */
154
+ const validators = {};
155
+
156
+ for (const key in manifest_data.validators) {
157
+ const file = manifest_data.validators[key];
158
+ const url = path__default.resolve(cwd, file);
159
+ const module = await vite.ssrLoadModule(url);
160
+
161
+ if (module.validate) {
162
+ validators[key] = module.validate;
163
+ } else {
164
+ throw new Error(`${file} does not export a \`validate\` function`);
165
+ }
166
+ }
167
+
168
+ return validators;
169
+ }
146
170
  }
147
171
  };
148
172
  }
@@ -355,29 +379,6 @@ async function create_plugin(config, cwd) {
355
379
  };
356
380
  }
357
381
 
358
- /** @param {string[]} array */
359
- function get_params(array) {
360
- // given an array of params like `['x', 'y', 'z']` for
361
- // src/routes/[x]/[y]/[z]/svelte, create a function
362
- // that turns a RegExpExecArray into ({ x, y, z })
363
-
364
- /** @param {RegExpExecArray} match */
365
- const fn = (match) => {
366
- /** @type {Record<string, string>} */
367
- const params = {};
368
- array.forEach((key, i) => {
369
- if (key.startsWith('...')) {
370
- params[key.slice(3)] = match[i + 1] || '';
371
- } else {
372
- params[key] = match[i + 1];
373
- }
374
- });
375
- return params;
376
- };
377
-
378
- return fn;
379
- }
380
-
381
382
  /** @param {import('http').ServerResponse} res */
382
383
  function not_found(res) {
383
384
  res.statusCode = 404;