@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/package.json +62 -36
  2. package/src/cli.js +15 -12
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +66 -72
  5. package/src/core/adapt/index.js +17 -6
  6. package/src/core/config/index.js +134 -80
  7. package/src/core/config/options.js +295 -273
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +146 -16
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +8 -20
  12. package/src/core/postbuild/crawl.js +22 -6
  13. package/src/core/postbuild/entities.js +8 -2
  14. package/src/core/postbuild/fallback.js +4 -2
  15. package/src/core/postbuild/prerender.js +222 -70
  16. package/src/core/postbuild/queue.js +2 -1
  17. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  18. package/src/core/sync/create_manifest_data/index.js +82 -62
  19. package/src/core/sync/sync.js +40 -31
  20. package/src/core/sync/ts.js +1 -1
  21. package/src/core/sync/utils.js +22 -2
  22. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +107 -58
  23. package/src/core/sync/write_client_manifest.js +14 -29
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +37 -45
  26. package/src/core/sync/write_tsconfig/index.js +274 -0
  27. package/src/core/sync/write_tsconfig/test-app/package.json +7 -0
  28. package/src/core/sync/write_tsconfig/utils.js +77 -0
  29. package/src/core/sync/write_tsconfig/validate.js +128 -0
  30. package/src/core/sync/write_types/index.js +127 -123
  31. package/src/core/utils.js +30 -5
  32. package/src/exports/env/index.js +77 -0
  33. package/src/exports/env/public.d.ts +55 -0
  34. package/src/exports/hooks/index.js +3 -9
  35. package/src/exports/hooks/public.d.ts +195 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +52 -29
  38. package/src/exports/internal/client.js +5 -0
  39. package/src/exports/internal/env.js +8 -5
  40. package/src/exports/internal/index.js +1 -90
  41. package/src/exports/internal/{event.js → server/event.js} +2 -3
  42. package/src/exports/internal/server/index.js +37 -0
  43. package/src/exports/internal/server/telemetry.js +95 -0
  44. package/src/exports/internal/shared.js +90 -0
  45. package/src/exports/node/index.js +64 -22
  46. package/src/exports/params/index.js +70 -0
  47. package/src/exports/params/public.d.ts +63 -0
  48. package/src/exports/public.d.ts +50 -1733
  49. package/src/exports/remote/index.js +11 -0
  50. package/src/exports/remote/public.d.ts +519 -0
  51. package/src/exports/url.js +86 -0
  52. package/src/exports/vite/build/build_server.js +54 -65
  53. package/src/exports/vite/build/remote.js +24 -19
  54. package/src/exports/vite/build/utils.js +0 -8
  55. package/src/exports/vite/dev/index.js +218 -146
  56. package/src/exports/vite/index.js +1590 -858
  57. package/src/exports/vite/module_ids.js +2 -2
  58. package/src/exports/vite/preview/index.js +40 -32
  59. package/src/exports/vite/public.d.ts +588 -0
  60. package/src/exports/vite/utils.js +84 -46
  61. package/src/pathname.js +55 -0
  62. package/src/runner.js +15 -0
  63. package/src/runtime/app/env/internal.js +4 -4
  64. package/src/runtime/app/env/types.d.ts +1 -1
  65. package/src/runtime/app/environment/index.js +3 -3
  66. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  67. package/src/runtime/app/forms/public.d.ts +2 -0
  68. package/src/runtime/app/forms/types.d.ts +56 -0
  69. package/src/runtime/app/internal/transport.js +53 -0
  70. package/src/runtime/app/manifest/index.js +1 -0
  71. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  72. package/src/runtime/app/navigation/public.d.ts +237 -0
  73. package/src/runtime/app/paths/client.js +37 -37
  74. package/src/runtime/app/paths/index.js +1 -1
  75. package/src/runtime/app/paths/internal/client.js +34 -2
  76. package/src/runtime/app/paths/internal/server.js +6 -23
  77. package/src/runtime/app/paths/internal.d.ts +3 -0
  78. package/src/runtime/app/paths/public.d.ts +1 -29
  79. package/src/runtime/app/paths/server.js +36 -17
  80. package/src/runtime/app/paths/types.d.ts +11 -19
  81. package/src/runtime/app/server/index.js +2 -2
  82. package/src/runtime/app/server/public.d.ts +201 -0
  83. package/src/runtime/app/server/remote/command.js +13 -11
  84. package/src/runtime/app/server/remote/form.js +61 -39
  85. package/src/runtime/app/server/remote/prerender.js +45 -45
  86. package/src/runtime/app/server/remote/query.js +106 -108
  87. package/src/runtime/app/server/remote/requested.js +27 -19
  88. package/src/runtime/app/server/remote/shared.js +76 -76
  89. package/src/runtime/app/service-worker/index.js +24 -0
  90. package/src/runtime/app/state/client.js +4 -2
  91. package/src/runtime/app/state/index.js +7 -5
  92. package/src/runtime/app/state/public.d.ts +72 -0
  93. package/src/runtime/app/state/server.js +3 -0
  94. package/src/runtime/app/stores.js +15 -78
  95. package/src/runtime/client/bundle.js +1 -1
  96. package/src/runtime/client/client-entry.js +3 -0
  97. package/src/runtime/client/client.js +1336 -648
  98. package/src/runtime/client/constants.js +3 -6
  99. package/src/runtime/client/entry.js +24 -3
  100. package/src/runtime/client/fetcher.js +25 -25
  101. package/src/runtime/client/ndjson.js +1 -1
  102. package/src/runtime/client/parse.js +1 -1
  103. package/src/runtime/client/payload.js +17 -0
  104. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  105. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  106. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  107. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  108. package/src/runtime/client/remote-functions/query/index.js +10 -17
  109. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  110. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  111. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  112. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  113. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  114. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  115. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  116. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  117. package/src/runtime/client/snapshots.js +147 -0
  118. package/src/runtime/client/state.svelte.js +94 -55
  119. package/src/runtime/client/stream.js +3 -2
  120. package/src/runtime/client/types.d.ts +13 -9
  121. package/src/runtime/client/utils.js +22 -110
  122. package/src/runtime/components/root.svelte +56 -0
  123. package/src/runtime/env/dynamic/private.js +7 -0
  124. package/src/runtime/env/dynamic/public.js +7 -0
  125. package/src/runtime/env/static/private.js +6 -0
  126. package/src/runtime/env/static/public.js +6 -0
  127. package/src/runtime/form-utils.js +172 -61
  128. package/src/runtime/pathname.js +20 -32
  129. package/src/runtime/props.svelte.js +72 -0
  130. package/src/runtime/server/constants.js +0 -3
  131. package/src/runtime/server/cookie.js +72 -55
  132. package/src/runtime/server/csrf.js +65 -0
  133. package/src/runtime/server/data/index.js +49 -53
  134. package/src/runtime/server/dev.js +22 -0
  135. package/src/runtime/server/endpoint.js +6 -7
  136. package/src/runtime/server/env_module.js +0 -5
  137. package/src/runtime/server/errors.js +160 -0
  138. package/src/runtime/server/fetch.js +32 -39
  139. package/src/runtime/server/index.js +90 -64
  140. package/src/runtime/server/internal.js +71 -0
  141. package/src/runtime/server/page/actions.js +85 -64
  142. package/src/runtime/server/page/crypto.js +2 -2
  143. package/src/runtime/server/page/csp.js +88 -104
  144. package/src/runtime/server/page/data_serializer.js +24 -25
  145. package/src/runtime/server/page/index.js +36 -53
  146. package/src/runtime/server/page/load_data.js +50 -57
  147. package/src/runtime/server/page/render.js +173 -239
  148. package/src/runtime/server/page/respond_with_error.js +17 -31
  149. package/src/runtime/server/page/serialize_data.js +2 -13
  150. package/src/runtime/server/page/server_routing.js +85 -26
  151. package/src/runtime/server/remote-functions.js +640 -0
  152. package/src/runtime/server/respond.js +190 -131
  153. package/src/runtime/server/sourcemaps.js +183 -0
  154. package/src/runtime/server/state.js +53 -0
  155. package/src/runtime/server/utils.js +13 -155
  156. package/src/runtime/shared.js +20 -40
  157. package/src/runtime/utils.js +3 -0
  158. package/src/types/ambient-private.d.ts +11 -1
  159. package/src/types/ambient.d.ts +87 -36
  160. package/src/types/global-private.d.ts +25 -25
  161. package/src/types/internal.d.ts +163 -157
  162. package/src/types/private.d.ts +41 -1
  163. package/src/utils/error.js +28 -4
  164. package/src/utils/escape.js +9 -25
  165. package/src/utils/features.js +1 -1
  166. package/src/utils/filesystem.js +1 -23
  167. package/src/utils/fork.js +7 -2
  168. package/src/utils/hash.js +21 -0
  169. package/src/utils/http.js +8 -7
  170. package/src/utils/import.js +9 -2
  171. package/src/utils/imports.js +83 -0
  172. package/src/utils/mime.js +9 -0
  173. package/src/utils/page_nodes.js +6 -7
  174. package/src/utils/params.js +67 -0
  175. package/src/utils/regex.js +9 -0
  176. package/src/utils/routing.js +145 -73
  177. package/src/utils/shared-iterator.js +5 -0
  178. package/src/utils/streaming.js +14 -4
  179. package/src/utils/url.js +33 -2
  180. package/src/version.js +1 -1
  181. package/types/index.d.ts +2616 -2433
  182. package/types/index.d.ts.map +120 -106
  183. package/src/core/sync/write_ambient.js +0 -18
  184. package/src/core/sync/write_root.js +0 -148
  185. package/src/core/sync/write_tsconfig.js +0 -250
  186. package/src/exports/internal/server.js +0 -22
  187. package/src/exports/node/polyfills.js +0 -30
  188. package/src/runtime/server/app.js +0 -9
  189. package/src/runtime/server/remote.js +0 -457
  190. package/src/runtime/shared-server.js +0 -7
  191. package/src/runtime/telemetry/otel.js +0 -21
  192. package/src/runtime/telemetry/record_span.js +0 -65
  193. package/src/types/synthetic/$lib.md +0 -5
  194. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  195. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -21,24 +21,12 @@ const escape_html_dict = {
21
21
  '<': '&lt;'
22
22
  };
23
23
 
24
- const surrogates = // high surrogate without paired low surrogate
25
- '[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
26
- // a valid surrogate pair, the only match with 2 code units
27
- // we match it so that we can match unpaired low surrogates in the same pass
28
- // TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
29
- '[\\ud800-\\udbff][\\udc00-\\udfff]|' +
30
- // unpaired low surrogate (see previous match)
31
- '[\\udc00-\\udfff]';
24
+ // `\p{Surrogate}` only matches unpaired surrogates with the `u` flag (a pair is one code point)
25
+ /** @param {Record<string, string>} dict */
26
+ const escape_regex = (dict) => new RegExp(`[${Object.keys(dict).join('')}]|\\p{Surrogate}`, 'gu');
32
27
 
33
- const escape_html_attr_regex = new RegExp(
34
- `[${Object.keys(escape_html_attr_dict).join('')}]|` + surrogates,
35
- 'g'
36
- );
37
-
38
- const escape_html_regex = new RegExp(
39
- `[${Object.keys(escape_html_dict).join('')}]|` + surrogates,
40
- 'g'
41
- );
28
+ const escape_html_attr_regex = escape_regex(escape_html_attr_dict);
29
+ const escape_html_regex = escape_regex(escape_html_dict);
42
30
 
43
31
  /**
44
32
  * Escapes unpaired surrogates (which are allowed in js strings but invalid in HTML) and
@@ -51,14 +39,10 @@ const escape_html_regex = new RegExp(
51
39
  */
52
40
  export function escape_html(str, is_attr) {
53
41
  const dict = is_attr ? escape_html_attr_dict : escape_html_dict;
54
- const escaped_str = str.replace(is_attr ? escape_html_attr_regex : escape_html_regex, (match) => {
55
- if (match.length === 2) {
56
- // valid surrogate pair
57
- return match;
58
- }
59
-
60
- return dict[match] ?? `&#${match.charCodeAt(0)};`;
61
- });
42
+ const escaped_str = str.replace(
43
+ is_attr ? escape_html_attr_regex : escape_html_regex,
44
+ (match) => dict[match] ?? `&#${match.charCodeAt(0)};`
45
+ );
62
46
 
63
47
  return escaped_str;
64
48
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @param {string} route_id
3
- * @param {any} config
3
+ * @param {Record<string, any>} config
4
4
  * @param {string} feature
5
5
  * @param {import('@sveltejs/kit').Adapter | undefined} adapter
6
6
  */
@@ -2,28 +2,6 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { posixify } from './os.js';
4
4
 
5
- /** @param {string} dir */
6
- export function mkdirp(dir) {
7
- try {
8
- fs.mkdirSync(dir, { recursive: true });
9
- } catch (/** @type {any} */ e) {
10
- if (e.code === 'EEXIST') {
11
- if (!fs.statSync(dir).isDirectory()) {
12
- throw new Error(`Cannot create directory ${dir}, a file already exists at this position`, {
13
- cause: e
14
- });
15
- }
16
- return;
17
- }
18
- throw e;
19
- }
20
- }
21
-
22
- /** @param {string} path */
23
- export function rimraf(path) {
24
- fs.rmSync(path, { force: true, recursive: true });
25
- }
26
-
27
5
  /**
28
6
  * @param {string} source
29
7
  * @param {string} target
@@ -58,7 +36,7 @@ export function copy(source, target, opts = {}) {
58
36
  go(path.join(from, file), path.join(to, file));
59
37
  });
60
38
  } else {
61
- mkdirp(path.dirname(to));
39
+ fs.mkdirSync(path.dirname(to), { recursive: true });
62
40
 
63
41
  if (opts.replace) {
64
42
  const data = fs.readFileSync(from, 'utf-8');
package/src/utils/fork.js CHANGED
@@ -38,7 +38,8 @@ export function forked(module, callback) {
38
38
  const worker = new Worker(fileURLToPath(module), {
39
39
  env: {
40
40
  ...process.env,
41
- SVELTEKIT_FORK: 'true'
41
+ SVELTEKIT_FORK: 'true',
42
+ FORCE_COLOR: '1'
42
43
  }
43
44
  });
44
45
 
@@ -60,9 +61,13 @@ export function forked(module, callback) {
60
61
  }
61
62
  );
62
63
 
64
+ worker.once('error', reject);
65
+
63
66
  worker.on('exit', (code) => {
64
67
  if (code) {
65
- reject(new Error(`Failed with code ${code}`));
68
+ const error = new Error(`Failed with code ${code}`);
69
+ error.stack = error.message;
70
+ reject(error);
66
71
  }
67
72
  });
68
73
  });
package/src/utils/hash.js CHANGED
@@ -20,3 +20,24 @@ export function hash(...values) {
20
20
 
21
21
  return (hash >>> 0).toString(36);
22
22
  }
23
+
24
+ /**
25
+ * Hash of the headers and body a `fetch` was called with. The server-side serializer and the
26
+ * client-side cache lookup must produce identical values for cached responses to be found.
27
+ * @param {HeadersInit | undefined} headers
28
+ * @param {import('types').StrictBody | null | undefined} body
29
+ */
30
+ export function hash_request(headers, body) {
31
+ /** @type {import('types').StrictBody[]} */
32
+ const values = [];
33
+
34
+ if (headers) {
35
+ values.push([...new Headers(headers)].join(','));
36
+ }
37
+
38
+ if (body) {
39
+ values.push(body);
40
+ }
41
+
42
+ return hash(...values);
43
+ }
package/src/utils/http.js CHANGED
@@ -11,7 +11,7 @@ export function negotiate(accept, types) {
11
11
  const parts = [];
12
12
 
13
13
  accept.split(',').forEach((str, i) => {
14
- const match = /([^/ \t]+)\/([^; \t]+)[ \t]*(?:;[ \t]*q=([0-9.]+))?/.exec(str);
14
+ const match = /^[ \t]*([^/ \t]+)\/([^; \t]+)[ \t]*(?:;[ \t]*q=([0-9.]+))?/.exec(str);
15
15
 
16
16
  // no match equals invalid header — ignore
17
17
  if (match) {
@@ -57,12 +57,13 @@ export function negotiate(accept, types) {
57
57
  }
58
58
 
59
59
  /**
60
- * Returns `true` if the request contains a `content-type` header with the given type
61
- * @param {Request} request
60
+ * Returns `true` if a `content-type` header value is one of the given types, ignoring
61
+ * parameters such as `charset` and comparing case-insensitively
62
+ * @param {string | null | undefined} header
62
63
  * @param {...string} types
63
64
  */
64
- function is_content_type(request, ...types) {
65
- const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
65
+ export function matches_content_type(header, ...types) {
66
+ const type = header?.split(';', 1)[0].trim() ?? '';
66
67
  return types.includes(type.toLowerCase());
67
68
  }
68
69
 
@@ -72,8 +73,8 @@ function is_content_type(request, ...types) {
72
73
  export function is_form_content_type(request) {
73
74
  // These content types must be protected against CSRF
74
75
  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
75
- return is_content_type(
76
- request,
76
+ return matches_content_type(
77
+ request.headers.get('content-type'),
77
78
  'application/x-www-form-urlencoded',
78
79
  'multipart/form-data',
79
80
  'text/plain',
@@ -1,8 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
3
4
 
4
5
  /**
5
- * Resolves a peer dependency relative to the current working directory. Duplicated with `packages/adapter-auto`
6
+ * Resolves a peer dependency relative to the current working directory.
7
+ *
8
+ * Mainly used to resolve the correct Vite package when an app's SvelteKit is a
9
+ * linked local repository.
10
+ *
11
+ * Duplicated with `packages/adapter-auto`
6
12
  * @param {string} dependency
7
13
  * @param {string} root
8
14
  */
@@ -46,8 +52,9 @@ function resolve_peer(dependency, root) {
46
52
  */
47
53
  export async function import_peer(dependency, root) {
48
54
  try {
49
- return await import(/* @vite-ignore */ resolve_peer(dependency, root));
55
+ return await import(/* @vite-ignore */ pathToFileURL(resolve_peer(dependency, root)).href);
50
56
  } catch {
57
+ // eslint-disable-next-line kit-node-custom/require-path-to-file-url -- bare package specifier, not a path
51
58
  return await import(/* @vite-ignore */ dependency);
52
59
  }
53
60
  }
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Reads the `imports` field from the package.json at the given directory.
6
+ * @param {string} cwd
7
+ * @returns {Record<string, string | Record<string, string>> | undefined}
8
+ */
9
+ export function read_package_imports(cwd) {
10
+ const pkg_path = path.resolve(cwd, 'package.json');
11
+ if (fs.existsSync(pkg_path)) {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(pkg_path, 'utf-8')).imports;
14
+ } catch {
15
+ // malformed package.json — ignore, the user will see other errors
16
+ }
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Normalizes an import value to a string path, handling both string values
22
+ * and conditional export objects (using the `default` key).
23
+ * @param {string | Record<string, string>} value
24
+ * @returns {string | null}
25
+ */
26
+ export function normalize_import_value(value) {
27
+ if (typeof value === 'string') {
28
+ return value.replace(/^\.\//, '');
29
+ }
30
+ if (value && typeof value === 'object' && typeof value.default === 'string') {
31
+ return value.default.replace(/^\.\//, '');
32
+ }
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Returns the `#`-prefixed import keys from the package.json `imports` field.
38
+ * @param {string} cwd
39
+ * @returns {string[]}
40
+ */
41
+ export function get_hash_import_keys(cwd) {
42
+ const imports = read_package_imports(cwd);
43
+ if (!imports) return [];
44
+ return Object.keys(imports).filter((key) => key.startsWith('#'));
45
+ }
46
+
47
+ /**
48
+ * Computes alias entries for `normalize_id` from the package.json `imports` field.
49
+ * Each entry maps a `#xx` alias to its absolute target path. Entries are sorted
50
+ * by path length descending so that longer/more-specific paths are matched first.
51
+ *
52
+ * @param {string} root
53
+ * @param {(p: string) => string} [normalize] - optional path normalizer (e.g. `vite.normalizePath`)
54
+ * @returns {Array<{ alias: string, path: string }>}
55
+ */
56
+ export function get_import_aliases(root, normalize) {
57
+ const imports = read_package_imports(root);
58
+ if (!imports) return [];
59
+
60
+ /** @type {Array<{ alias: string, path: string }>} */
61
+ const aliases = [];
62
+
63
+ for (const [key, raw_value] of Object.entries(imports)) {
64
+ if (!key.startsWith('#')) continue;
65
+
66
+ const value = normalize_import_value(raw_value);
67
+ if (!value) continue;
68
+
69
+ // For `#xx/*` imports, the target directory is the value without `/*`
70
+ // For `#xx` imports, the target is the file itself
71
+ const target = value.endsWith('/*') ? value.slice(0, -2) : value;
72
+ if (target.includes('*')) continue; // not sure if this is even allowed in Node, anyway it's too niche/complex to support here
73
+ const abs = path.resolve(root, target);
74
+ const alias = key.endsWith('/*') ? key.slice(0, -2) : key;
75
+
76
+ aliases.push({ alias, path: normalize ? normalize(abs) : abs });
77
+ }
78
+
79
+ // Sort by path length descending so longer/more-specific paths match first
80
+ aliases.sort((a, b) => b.path.length - a.path.length);
81
+
82
+ return aliases;
83
+ }
@@ -0,0 +1,9 @@
1
+ import { mimes, lookup } from 'mrmime';
2
+
3
+ // `mrmime` does not include `.ico` in its database. The IANA-registered
4
+ // type is `image/vnd.microsoft.icon`, but `image/x-icon` is the
5
+ // semi-official type that is universally supported by browsers and other
6
+ // tools, so we use that.
7
+ mimes['ico'] = 'image/x-icon';
8
+
9
+ export { lookup };
@@ -1,3 +1,4 @@
1
+ /** @import { UniversalNode, ServerNode } from 'types' */
1
2
  import {
2
3
  validate_layout_exports,
3
4
  validate_layout_server_exports,
@@ -42,10 +43,10 @@ export class PageNodes {
42
43
  /**
43
44
  * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
44
45
  * @param {Option} option
45
- * @returns {Value | undefined}
46
+ * @returns {(UniversalNode | ServerNode)[Option] | undefined}
46
47
  */
47
48
  #get_option(option) {
48
- /** @typedef {(import('types').UniversalNode | import('types').ServerNode)[Option]} Value */
49
+ /** @typedef {(UniversalNode | ServerNode)[Option]} Value */
49
50
 
50
51
  return this.data.reduce((value, node) => {
51
52
  return node?.universal?.[option] ?? node?.server?.[option] ?? value;
@@ -69,7 +70,7 @@ export class PageNodes {
69
70
  }
70
71
 
71
72
  get_config() {
72
- /** @type {any} */
73
+ /** @type {Record<string, any>} */
73
74
  let current = {};
74
75
 
75
76
  for (const node of this.data) {
@@ -77,13 +78,11 @@ export class PageNodes {
77
78
 
78
79
  current = {
79
80
  ...current,
80
- // TODO: should we override the server config value with the universal value similar to other page options?
81
- ...node?.universal?.config,
82
- ...node?.server?.config
81
+ ...node?.server?.config,
82
+ ...node?.universal?.config
83
83
  };
84
84
  }
85
85
 
86
- // TODO 3.0 always return `current`? then we can get rid of `?? {}` in other places
87
86
  return Object.keys(current).length ? current : undefined;
88
87
  }
89
88
 
@@ -0,0 +1,67 @@
1
+ /** @import { ParamMatcher } from '@sveltejs/kit/params' */
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ /**
6
+ * @param {import('types').RouteData[]} routes
7
+ * @returns {Set<string>}
8
+ */
9
+ export function collect_matcher_names(routes) {
10
+ /** @type {Set<string>} */
11
+ const names = new Set();
12
+
13
+ for (const route of routes) {
14
+ for (const param of route.params) {
15
+ if (param.matcher) names.add(param.matcher);
16
+ }
17
+ }
18
+
19
+ return names;
20
+ }
21
+
22
+ /**
23
+ * @param {Record<string, unknown>} params
24
+ * @param {Set<string>} names
25
+ * @param {string} [file]
26
+ */
27
+ export function validate_param_matchers(params, names, file) {
28
+ for (const name of names) {
29
+ if (!Object.hasOwn(params, name)) {
30
+ throw new Error(`No matcher found for parameter '${name}'${file ? ` in ${file}` : ''}`);
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param {{
37
+ * routes: import('types').RouteData[];
38
+ * params_path: string | null;
39
+ * root: string;
40
+ * load?: (file: string) => Promise<Record<string, unknown>>;
41
+ * }} opts
42
+ * @returns {Promise<Record<string, ParamMatcher> | null>}
43
+ */
44
+ export async function load_and_validate_params({ routes, params_path, root, load }) {
45
+ const names = collect_matcher_names(routes);
46
+
47
+ if (names.size === 0) return null;
48
+
49
+ if (!params_path) {
50
+ throw new Error(`No matcher found for parameter '${names.values().next().value}'`);
51
+ }
52
+
53
+ const file = path.resolve(root, params_path);
54
+ const module = load ? await load(file) : await import(pathToFileURL(file).href);
55
+
56
+ if (!module.params || typeof module.params !== 'object') {
57
+ throw new Error(`${params_path} does not export \`params\` from \`defineParams\``);
58
+ }
59
+
60
+ validate_param_matchers(
61
+ /** @type {Record<string, unknown>} */ (module.params),
62
+ names,
63
+ params_path
64
+ );
65
+
66
+ return /** @type {Record<string, ParamMatcher>} */ (module.params);
67
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Escapes characters that have special meaning in a regular expression.
3
+ * @param {string} str
4
+ * @returns {string} escaped string
5
+ */
6
+ export function escape_for_regexp(str) {
7
+ // TODO replace with `RegExp.escape(str)` when we require Node >= 24
8
+ return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
9
+ }