@sveltejs/kit 3.0.0-next.1 → 3.0.0-next.11

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 (144) hide show
  1. package/package.json +33 -21
  2. package/src/constants.js +2 -0
  3. package/src/core/adapt/builder.js +32 -49
  4. package/src/core/adapt/index.js +1 -4
  5. package/src/core/config/index.js +137 -71
  6. package/src/core/config/options.js +236 -244
  7. package/src/core/config/types.d.ts +1 -1
  8. package/src/core/env.js +121 -5
  9. package/src/core/generate_manifest/index.js +12 -15
  10. package/src/core/postbuild/analyse.js +7 -19
  11. package/src/core/postbuild/crawl.js +22 -6
  12. package/src/core/postbuild/fallback.js +2 -1
  13. package/src/core/postbuild/prerender.js +114 -48
  14. package/src/core/postbuild/queue.js +2 -1
  15. package/src/core/sync/create_manifest_data/index.js +53 -50
  16. package/src/core/sync/sync.js +0 -2
  17. package/src/core/sync/write_client_manifest.js +8 -15
  18. package/src/core/sync/write_env.js +2 -1
  19. package/src/core/sync/write_non_ambient.js +12 -9
  20. package/src/core/sync/write_server.js +14 -15
  21. package/src/core/sync/write_tsconfig.js +16 -8
  22. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  23. package/src/core/sync/write_types/index.js +28 -24
  24. package/src/core/utils.js +30 -5
  25. package/src/exports/env/index.js +77 -0
  26. package/src/exports/hooks/index.js +3 -9
  27. package/src/exports/hooks/sequence.js +3 -2
  28. package/src/exports/index.js +35 -13
  29. package/src/exports/internal/client.js +5 -0
  30. package/src/exports/internal/env.js +8 -5
  31. package/src/exports/internal/index.js +1 -90
  32. package/src/exports/internal/{event.js → server/event.js} +1 -2
  33. package/src/exports/internal/server/index.js +33 -0
  34. package/src/exports/internal/shared.js +89 -0
  35. package/src/exports/node/index.js +62 -15
  36. package/src/exports/params.js +68 -0
  37. package/src/exports/public.d.ts +363 -189
  38. package/src/exports/url.js +86 -0
  39. package/src/exports/vite/build/build_server.js +53 -59
  40. package/src/exports/vite/build/remote.js +18 -11
  41. package/src/exports/vite/build/utils.js +0 -8
  42. package/src/exports/vite/dev/index.js +92 -50
  43. package/src/exports/vite/index.js +903 -681
  44. package/src/exports/vite/module_ids.js +0 -2
  45. package/src/exports/vite/preview/index.js +32 -24
  46. package/src/exports/vite/static_analysis/index.js +2 -4
  47. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  48. package/src/exports/vite/utils.js +40 -35
  49. package/src/runtime/app/env/internal.js +4 -4
  50. package/src/runtime/app/env/types.d.ts +1 -1
  51. package/src/runtime/app/environment/index.js +3 -3
  52. package/src/runtime/app/forms.js +24 -7
  53. package/src/runtime/app/paths/client.js +4 -10
  54. package/src/runtime/app/paths/internal/client.js +4 -2
  55. package/src/runtime/app/paths/internal/server.js +2 -23
  56. package/src/runtime/app/paths/public.d.ts +0 -28
  57. package/src/runtime/app/paths/server.js +10 -6
  58. package/src/runtime/app/server/index.js +1 -1
  59. package/src/runtime/app/server/remote/command.js +0 -3
  60. package/src/runtime/app/server/remote/form.js +27 -15
  61. package/src/runtime/app/server/remote/prerender.js +29 -36
  62. package/src/runtime/app/server/remote/query.js +101 -99
  63. package/src/runtime/app/server/remote/requested.js +22 -14
  64. package/src/runtime/app/server/remote/shared.js +30 -26
  65. package/src/runtime/app/state/client.js +1 -2
  66. package/src/runtime/app/stores.js +13 -76
  67. package/src/runtime/client/bundle.js +1 -1
  68. package/src/runtime/client/client-entry.js +3 -0
  69. package/src/runtime/client/client.js +411 -318
  70. package/src/runtime/client/entry.js +24 -3
  71. package/src/runtime/client/fetcher.js +3 -2
  72. package/src/runtime/client/ndjson.js +6 -33
  73. package/src/runtime/client/payload.js +17 -0
  74. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  75. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  76. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  77. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  78. package/src/runtime/client/remote-functions/query/index.js +7 -14
  79. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  80. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  81. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  82. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  83. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  84. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  85. package/src/runtime/client/sse.js +32 -0
  86. package/src/runtime/client/state.svelte.js +65 -49
  87. package/src/runtime/client/stream.js +39 -0
  88. package/src/runtime/client/types.d.ts +11 -7
  89. package/src/runtime/client/utils.js +0 -96
  90. package/src/runtime/components/root.svelte +66 -0
  91. package/src/runtime/env/dynamic/private.js +7 -0
  92. package/src/runtime/env/dynamic/public.js +7 -0
  93. package/src/runtime/env/static/private.js +6 -0
  94. package/src/runtime/env/static/public.js +6 -0
  95. package/src/runtime/form-utils.js +100 -22
  96. package/src/runtime/server/cookie.js +69 -52
  97. package/src/runtime/server/csrf.js +65 -0
  98. package/src/runtime/server/data/index.js +15 -19
  99. package/src/runtime/server/env_module.js +0 -5
  100. package/src/runtime/server/errors.js +135 -0
  101. package/src/runtime/server/fetch.js +1 -1
  102. package/src/runtime/server/index.js +22 -12
  103. package/src/runtime/server/internal.js +25 -0
  104. package/src/runtime/server/page/actions.js +43 -27
  105. package/src/runtime/server/page/data_serializer.js +10 -10
  106. package/src/runtime/server/page/index.js +12 -17
  107. package/src/runtime/server/page/load_data.js +2 -2
  108. package/src/runtime/server/page/render.js +114 -193
  109. package/src/runtime/server/page/respond_with_error.js +11 -11
  110. package/src/runtime/server/page/server_routing.js +27 -18
  111. package/src/runtime/server/remote-functions.js +596 -0
  112. package/src/runtime/server/respond.js +100 -68
  113. package/src/runtime/server/sourcemaps.js +183 -0
  114. package/src/runtime/server/utils.js +2 -134
  115. package/src/runtime/shared.js +83 -13
  116. package/src/runtime/telemetry/otel.js +1 -1
  117. package/src/runtime/types.d.ts +8 -0
  118. package/src/types/ambient-private.d.ts +2 -0
  119. package/src/types/ambient.d.ts +10 -5
  120. package/src/types/global-private.d.ts +13 -25
  121. package/src/types/internal.d.ts +95 -80
  122. package/src/types/private.d.ts +41 -1
  123. package/src/utils/error.js +24 -4
  124. package/src/utils/escape.js +9 -25
  125. package/src/utils/features.js +1 -1
  126. package/src/utils/fork.js +7 -2
  127. package/src/utils/import.js +6 -1
  128. package/src/utils/imports.js +83 -0
  129. package/src/utils/mime.js +9 -0
  130. package/src/utils/page_nodes.js +3 -5
  131. package/src/utils/params.js +66 -0
  132. package/src/utils/routing.js +90 -44
  133. package/src/utils/shared-iterator.js +5 -0
  134. package/src/utils/streaming.js +14 -4
  135. package/src/utils/url.js +20 -2
  136. package/src/version.js +1 -1
  137. package/types/index.d.ts +537 -557
  138. package/types/index.d.ts.map +24 -39
  139. package/src/core/sync/write_root.js +0 -148
  140. package/src/exports/internal/server.js +0 -22
  141. package/src/runtime/server/remote.js +0 -457
  142. package/src/runtime/shared-server.js +0 -7
  143. package/src/types/synthetic/$lib.md +0 -5
  144. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -61,7 +61,10 @@ export namespace Csp {
61
61
  | 'unsafe-eval'
62
62
  | 'unsafe-hashes'
63
63
  | 'unsafe-inline'
64
+ | 'unsafe-allow-redirects'
65
+ | 'unsafe-webtransport-hashes'
64
66
  | 'wasm-unsafe-eval'
67
+ | 'trusted-types-eval'
65
68
  | 'none';
66
69
  type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
67
70
  type FrameSource = HostSource | SchemeSource | 'self' | 'none';
@@ -70,7 +73,16 @@ export namespace Csp {
70
73
  type HostProtocolSchemes = `${string}://` | '';
71
74
  type HttpDelineator = '/' | '?' | '#' | '\\';
72
75
  type PortScheme = `:${number}` | '' | ':*';
73
- type SchemeSource = 'http:' | 'https:' | 'data:' | 'mediastream:' | 'blob:' | 'filesystem:';
76
+ type SchemeSource =
77
+ | 'http:'
78
+ | 'https:'
79
+ | 'ws:'
80
+ | 'wss:'
81
+ | 'data:'
82
+ | 'mediastream:'
83
+ | 'blob:'
84
+ | 'filesystem:'
85
+ | (`${string}:` & {});
74
86
  type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
75
87
  type Sources = Source[];
76
88
  }
@@ -147,10 +159,18 @@ export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |
147
159
  export interface Logger {
148
160
  (msg: string): void;
149
161
  success(msg: string): void;
162
+ /** Print a bold red message to stderr */
150
163
  error(msg: string): void;
164
+ /** Print a bold yellow message to stderr */
151
165
  warn(msg: string): void;
166
+ /** Print faded text to stdout if `verbose === true` */
152
167
  minor(msg: string): void;
168
+ /** Print to stdout if `verbose === true` */
153
169
  info(msg: string): void;
170
+ /** Print to stderr without formatting */
171
+ err(msg: string): void;
172
+ /** Print a bold red message, followed by a stack trace for each error (following `.cause` chains) */
173
+ prettyError(error: unknown, caller?: string): void;
154
174
  }
155
175
 
156
176
  export type MaybePromise<T> = T | Promise<T>;
@@ -212,6 +232,10 @@ export interface PrerenderUnseenRoutesHandler {
212
232
  (details: { routes: string[]; message: string }): void;
213
233
  }
214
234
 
235
+ export interface PrerenderInvalidUrlHandler {
236
+ (details: { href: string; referrer: string | null; message: string }): void;
237
+ }
238
+
215
239
  export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
216
240
  export type PrerenderMissingIdHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderMissingIdHandler;
217
241
  export type PrerenderUnseenRoutesHandlerValue =
@@ -224,6 +248,11 @@ export type PrerenderEntryGeneratorMismatchHandlerValue =
224
248
  | 'warn'
225
249
  | 'ignore'
226
250
  | PrerenderEntryGeneratorMismatchHandler;
251
+ export type PrerenderInvalidUrlHandlerValue =
252
+ | 'fail'
253
+ | 'warn'
254
+ | 'ignore'
255
+ | PrerenderInvalidUrlHandler;
227
256
 
228
257
  export type PrerenderOption = boolean | 'auto';
229
258
 
@@ -252,3 +281,14 @@ export type DeepPartial<T> = T extends Record<PropertyKey, unknown> | unknown[]
252
281
  : T | undefined;
253
282
 
254
283
  export type IsAny<T> = 0 extends 1 & T ? true : false;
284
+
285
+ export type HasNonOptionalBoolean<T> =
286
+ IsAny<T> extends true
287
+ ? never
288
+ : [T] extends [boolean]
289
+ ? true
290
+ : T extends Array<infer U>
291
+ ? HasNonOptionalBoolean<U>
292
+ : T extends Record<string, any>
293
+ ? { [K in keyof T]: HasNonOptionalBoolean<T[K]> }[keyof T]
294
+ : never;
@@ -1,5 +1,17 @@
1
1
  import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
2
2
 
3
+ /**
4
+ * For times when you need to throw an error, but without
5
+ * displaying a useless stack trace (since the developer
6
+ * can't do anything useful with it)
7
+ * @param {string} message
8
+ */
9
+ export function stackless(message) {
10
+ const error = new Error(message);
11
+ error.stack = '';
12
+ return error;
13
+ }
14
+
3
15
  /**
4
16
  * @param {unknown} err
5
17
  * @return {Error}
@@ -18,16 +30,24 @@ export function coalesce_to_error(err) {
18
30
  * @param {unknown} error
19
31
  */
20
32
  export function normalize_error(error) {
21
- return /** @type {import('../exports/internal/index.js').Redirect | HttpError | SvelteKitError | Error} */ (
33
+ return /** @type {import('../exports/internal/shared.js').Redirect | HttpError | SvelteKitError | Error} */ (
22
34
  error
23
35
  );
24
36
  }
25
37
 
26
38
  /**
27
- * @param {unknown} error
39
+ * @param {any} transformed
40
+ * @param {any} [error]
28
41
  */
29
- export function get_status(error) {
30
- return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
42
+ export function get_status(transformed, error) {
43
+ const err = error ?? transformed;
44
+ const status = err instanceof HttpError || err instanceof SvelteKitError ? err.status : 500;
45
+
46
+ if (error == null || typeof transformed?.status !== 'number') {
47
+ return status;
48
+ } else {
49
+ return transformed.status;
50
+ }
31
51
  }
32
52
 
33
53
  /**
@@ -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
  */
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
  });
@@ -2,7 +2,12 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  /**
5
- * Resolves a peer dependency relative to the current working directory. Duplicated with `packages/adapter-auto`
5
+ * Resolves a peer dependency relative to the current working directory.
6
+ *
7
+ * Mainly used to resolve the correct Vite package when an app's SvelteKit is a
8
+ * linked local repository.
9
+ *
10
+ * Duplicated with `packages/adapter-auto`
6
11
  * @param {string} dependency
7
12
  * @param {string} root
8
13
  */
@@ -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 };
@@ -69,7 +69,7 @@ export class PageNodes {
69
69
  }
70
70
 
71
71
  get_config() {
72
- /** @type {any} */
72
+ /** @type {Record<string, any>} */
73
73
  let current = {};
74
74
 
75
75
  for (const node of this.data) {
@@ -77,13 +77,11 @@ export class PageNodes {
77
77
 
78
78
  current = {
79
79
  ...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
80
+ ...node?.server?.config,
81
+ ...node?.universal?.config
83
82
  };
84
83
  }
85
84
 
86
- // TODO 3.0 always return `current`? then we can get rid of `?? {}` in other places
87
85
  return Object.keys(current).length ? current : undefined;
88
86
  }
89
87
 
@@ -0,0 +1,66 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ /**
5
+ * @param {import('types').RouteData[]} routes
6
+ * @returns {Set<string>}
7
+ */
8
+ export function collect_matcher_names(routes) {
9
+ /** @type {Set<string>} */
10
+ const names = new Set();
11
+
12
+ for (const route of routes) {
13
+ for (const param of route.params) {
14
+ if (param.matcher) names.add(param.matcher);
15
+ }
16
+ }
17
+
18
+ return names;
19
+ }
20
+
21
+ /**
22
+ * @param {Record<string, unknown>} params
23
+ * @param {Set<string>} names
24
+ * @param {string} [file]
25
+ */
26
+ export function validate_param_matchers(params, names, file) {
27
+ for (const name of names) {
28
+ if (!(name in params)) {
29
+ throw new Error(`No matcher found for parameter '${name}'${file ? ` in ${file}` : ''}`);
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @param {{
36
+ * routes: import('types').RouteData[];
37
+ * params_path: string | null;
38
+ * root: string;
39
+ * load?: (file: string) => Promise<Record<string, unknown>>;
40
+ * }} opts
41
+ * @returns {Promise<Record<string, import('@sveltejs/kit').ParamMatcher> | null>}
42
+ */
43
+ export async function load_and_validate_params({ routes, params_path, root, load }) {
44
+ const names = collect_matcher_names(routes);
45
+
46
+ if (names.size === 0) return null;
47
+
48
+ if (!params_path) {
49
+ throw new Error(`No matcher found for parameter '${names.values().next().value}'`);
50
+ }
51
+
52
+ const file = path.resolve(root, params_path);
53
+ const module = load ? await load(file) : await import(pathToFileURL(file).href);
54
+
55
+ if (!module.params || typeof module.params !== 'object') {
56
+ throw new Error(`${params_path} does not export \`params\` from \`defineParams\``);
57
+ }
58
+
59
+ validate_param_matchers(
60
+ /** @type {Record<string, unknown>} */ (module.params),
61
+ names,
62
+ params_path
63
+ );
64
+
65
+ return /** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */ (module.params);
66
+ }
@@ -1,7 +1,6 @@
1
1
  import { BROWSER } from 'esm-env';
2
- import { decode_params } from './url.js';
3
2
 
4
- const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
3
+ const param_pattern = /^(\[)?(\.\.\.)?([\w-]+)(?:=([\w-]+))?(\])?$/;
5
4
 
6
5
  const root_group_pattern = /^\/\((?:[^)]+)\)$/;
7
6
 
@@ -20,7 +19,7 @@ export function parse_route_id(id) {
20
19
  `^${get_route_segments(id)
21
20
  .map((segment) => {
22
21
  // special case — /[...rest]/ could contain zero segments
23
- const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
22
+ const rest_match = /^\[\.\.\.([\w-]+)(?:=([\w-]+))?\]$/.exec(segment);
24
23
  if (rest_match) {
25
24
  params.push({
26
25
  name: rest_match[1],
@@ -32,7 +31,7 @@ export function parse_route_id(id) {
32
31
  return '(?:/([^]*))?';
33
32
  }
34
33
  // special case — /[[optional]]/ could contain zero segments
35
- const optional_match = /^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(segment);
34
+ const optional_match = /^\[\[([\w-]+)(?:=([\w-]+))?\]\]$/.exec(segment);
36
35
  if (optional_match) {
37
36
  params.push({
38
37
  name: optional_match[1],
@@ -73,7 +72,7 @@ export function parse_route_id(id) {
73
72
  const match = /** @type {RegExpExecArray} */ (param_pattern.exec(content));
74
73
  if (!BROWSER && !match) {
75
74
  throw new Error(
76
- `Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.`
75
+ `Invalid param: ${content}. Params and matcher names can only have underscores, hyphens, and alphanumeric characters.`
77
76
  );
78
77
  }
79
78
 
@@ -104,7 +103,7 @@ export function parse_route_id(id) {
104
103
  return { pattern, params };
105
104
  }
106
105
 
107
- const optional_param_regex = /\/\[\[\w+?(?:=\w+)?\]\]/;
106
+ const optional_param_regex = /\/\[\[[\w-]+?(?:=[\w-]+)?\]\]/;
108
107
 
109
108
  /**
110
109
  * Removes optional params from a route ID.
@@ -134,13 +133,43 @@ export function get_route_segments(route) {
134
133
  return route.slice(1).split('/').filter(affects_path);
135
134
  }
136
135
 
136
+ /**
137
+ * @param {import('@sveltejs/kit').ParamMatcher} matcher
138
+ * @param {string} value
139
+ * @returns {{ success: true, value: any } | { success: false }}
140
+ */
141
+ function run_matcher(matcher, value) {
142
+ const result = matcher['~standard'].validate(value);
143
+
144
+ if (result instanceof Promise) {
145
+ throw new Error('Async param matchers are not supported');
146
+ }
147
+
148
+ if (result.issues) {
149
+ return { success: false };
150
+ }
151
+
152
+ const parsed = result.value;
153
+
154
+ if (
155
+ typeof parsed !== 'string' &&
156
+ typeof parsed !== 'number' &&
157
+ typeof parsed !== 'boolean' &&
158
+ typeof parsed !== 'bigint'
159
+ ) {
160
+ throw new Error('Param matcher must return a string, number, boolean, or bigint');
161
+ }
162
+
163
+ return { success: true, value: parsed };
164
+ }
165
+
137
166
  /**
138
167
  * @param {RegExpMatchArray} match
139
168
  * @param {import('types').RouteParam[]} params
140
169
  * @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
141
170
  */
142
171
  export function exec(match, params, matchers) {
143
- /** @type {Record<string, string>} */
172
+ /** @type {Record<string, any>} */
144
173
  const result = {};
145
174
 
146
175
  const values = match.slice(1);
@@ -173,37 +202,41 @@ export function exec(match, params, matchers) {
173
202
  }
174
203
  }
175
204
 
176
- if (!param.matcher || matchers[param.matcher](value)) {
177
- result[param.name] = value;
205
+ const decoded = decodeURIComponent(value);
178
206
 
179
- // Now that the params match, reset the buffer if the next param isn't the [...rest]
180
- // and the next value is defined, otherwise the buffer will cause us to skip values
181
- const next_param = params[i + 1];
182
- const next_value = values[i + 1];
183
- if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
184
- buffered = 0;
185
- }
207
+ if (param.matcher) {
208
+ const outcome = run_matcher(matchers[param.matcher], decoded);
209
+
210
+ if (!outcome.success) {
211
+ // in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
212
+ // keep track of the number of skipped optional parameters and continue
213
+ if (param.optional && param.chained) {
214
+ buffered++;
215
+ continue;
216
+ }
186
217
 
187
- // There are no more params and no more values, but all non-empty values have been matched
188
- if (
189
- !next_param &&
190
- !next_value &&
191
- Object.keys(result).length === values_needing_match.length
192
- ) {
193
- buffered = 0;
218
+ // otherwise, if the matcher returns `false`, the route did not match
219
+ return;
194
220
  }
195
- continue;
221
+
222
+ result[param.name] = outcome.value;
223
+ } else {
224
+ result[param.name] = decoded;
196
225
  }
197
226
 
198
- // in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
199
- // keep track of the number of skipped optional parameters and continue
200
- if (param.optional && param.chained) {
201
- buffered++;
202
- continue;
227
+ // Now that the params match, reset the buffer if the next param isn't the [...rest]
228
+ // and the next value is defined, otherwise the buffer will cause us to skip values
229
+ const next_param = params[i + 1];
230
+ const next_value = values[i + 1];
231
+ if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
232
+ buffered = 0;
203
233
  }
204
234
 
205
- // otherwise, if the matcher returns `false`, the route did not match
206
- return;
235
+ // There are no more params and no more values, but all non-empty values have been matched
236
+ if (!next_param && !next_value && Object.keys(result).length === values_needing_match.length) {
237
+ buffered = 0;
238
+ }
239
+ continue;
207
240
  }
208
241
 
209
242
  if (buffered) return;
@@ -227,7 +260,7 @@ function escape(str) {
227
260
  );
228
261
  }
229
262
 
230
- const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
263
+ const basic_param_pattern = /\[(\[)?(\.\.\.)?([\w-]+?)(?:=([\w-]+))?\]\]?/g;
231
264
 
232
265
  /**
233
266
  * Populate a route ID with params to resolve a pathname.
@@ -242,7 +275,7 @@ const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
242
275
  * ); // `/blog/hello-world/something/else`
243
276
  * ```
244
277
  * @param {string} id
245
- * @param {Record<string, string | undefined>} params
278
+ * @param {Record<string, import('@sveltejs/kit').ParamValue | undefined>} params
246
279
  * @returns {string}
247
280
  */
248
281
  export function resolve_route(id, params) {
@@ -254,20 +287,33 @@ export function resolve_route(id, params) {
254
287
  segments
255
288
  .map((segment) =>
256
289
  segment.replace(basic_param_pattern, (_, optional, rest, name) => {
257
- const param_value = params[name];
290
+ const value = params[name];
258
291
 
259
- // This is nested so TS correctly narrows the type
260
- if (!param_value) {
292
+ if (value === undefined || value === '') {
261
293
  if (optional) return '';
262
- if (rest && param_value !== undefined) return '';
294
+ if (rest && value !== undefined) return '';
263
295
  throw new Error(`Missing parameter '${name}' in route ${id}`);
264
296
  }
265
297
 
266
- if (param_value.startsWith('/') || param_value.endsWith('/'))
267
- throw new Error(
268
- `Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
269
- );
270
- return param_value;
298
+ if (typeof value === 'string') {
299
+ if (value.startsWith('/') || value.endsWith('/')) {
300
+ throw new Error(
301
+ `Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
302
+ );
303
+ }
304
+
305
+ return value;
306
+ }
307
+
308
+ if (
309
+ typeof value === 'number' ||
310
+ typeof value === 'boolean' ||
311
+ typeof value === 'bigint'
312
+ ) {
313
+ return String(value);
314
+ }
315
+
316
+ throw new Error('Parameter values must be a string, number, boolean, or bigint');
271
317
  })
272
318
  )
273
319
  .filter(Boolean)
@@ -290,7 +336,7 @@ export function has_server_load(node) {
290
336
  * @param {string} path - The decoded pathname to match
291
337
  * @param {Route[]} routes
292
338
  * @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
293
- * @returns {{ route: Route, params: Record<string, string> } | null}
339
+ * @returns {{ route: Route, params: Record<string, any> } | null}
294
340
  */
295
341
  export function find_route(path, routes, matchers) {
296
342
  for (const route of routes) {
@@ -301,7 +347,7 @@ export function find_route(path, routes, matchers) {
301
347
  if (matched) {
302
348
  return {
303
349
  route,
304
- params: decode_params(matched)
350
+ params: matched
305
351
  };
306
352
  }
307
353
  }
@@ -52,6 +52,11 @@ export class SharedIterator {
52
52
  /** @type {unknown} */
53
53
  #terminal_error = undefined;
54
54
 
55
+ /** Whether `done()` or `fail()` has been broadcast. */
56
+ get closed() {
57
+ return this.#closed;
58
+ }
59
+
55
60
  /**
56
61
  * @param {(instance: SharedIterator<T>) => (() => void)} [start]
57
62
  */