@sveltejs/kit 3.0.0-next.4 → 3.0.0-next.7

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 (91) hide show
  1. package/package.json +23 -18
  2. package/src/core/adapt/builder.js +29 -43
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +135 -71
  5. package/src/core/config/options.js +291 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +120 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +8 -12
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +40 -23
  12. package/src/core/sync/create_manifest_data/index.js +29 -49
  13. package/src/core/sync/write_client_manifest.js +14 -14
  14. package/src/core/sync/write_non_ambient.js +10 -7
  15. package/src/core/sync/write_root.js +9 -30
  16. package/src/core/sync/write_server.js +13 -11
  17. package/src/core/sync/write_tsconfig.js +2 -4
  18. package/src/core/sync/write_types/index.js +11 -10
  19. package/src/exports/index.js +34 -12
  20. package/src/exports/internal/client.js +5 -0
  21. package/src/exports/internal/env.js +1 -1
  22. package/src/exports/internal/index.js +1 -90
  23. package/src/exports/internal/{event.js → server/event.js} +1 -2
  24. package/src/exports/internal/server/index.js +33 -0
  25. package/src/exports/internal/shared.js +89 -0
  26. package/src/exports/node/index.js +58 -7
  27. package/src/exports/params.js +63 -0
  28. package/src/exports/public.d.ts +299 -145
  29. package/src/exports/url.js +84 -0
  30. package/src/exports/vite/build/build_server.js +6 -1
  31. package/src/exports/vite/dev/index.js +38 -37
  32. package/src/exports/vite/index.js +498 -353
  33. package/src/exports/vite/preview/index.js +16 -8
  34. package/src/exports/vite/utils.js +7 -11
  35. package/src/runtime/app/env/types.d.ts +1 -1
  36. package/src/runtime/app/forms.js +22 -5
  37. package/src/runtime/app/paths/client.js +4 -10
  38. package/src/runtime/app/paths/public.d.ts +0 -28
  39. package/src/runtime/app/paths/server.js +8 -4
  40. package/src/runtime/app/server/remote/form.js +10 -3
  41. package/src/runtime/app/server/remote/query.js +9 -18
  42. package/src/runtime/app/server/remote/requested.js +8 -4
  43. package/src/runtime/app/server/remote/shared.js +5 -7
  44. package/src/runtime/app/state/client.js +1 -2
  45. package/src/runtime/app/stores.js +13 -76
  46. package/src/runtime/client/client.js +205 -167
  47. package/src/runtime/client/fetcher.js +3 -2
  48. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  49. package/src/runtime/client/remote-functions/form.svelte.js +122 -31
  50. package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
  51. package/src/runtime/client/remote-functions/query/index.js +3 -2
  52. package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
  53. package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
  54. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
  55. package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
  56. package/src/runtime/client/state.svelte.js +66 -49
  57. package/src/runtime/client/types.d.ts +10 -2
  58. package/src/runtime/client/utils.js +0 -96
  59. package/src/runtime/form-utils.js +99 -18
  60. package/src/runtime/server/cookie.js +22 -33
  61. package/src/runtime/server/csrf.js +65 -0
  62. package/src/runtime/server/data/index.js +7 -7
  63. package/src/runtime/server/env_module.js +0 -5
  64. package/src/runtime/server/index.js +2 -2
  65. package/src/runtime/server/page/actions.js +41 -26
  66. package/src/runtime/server/page/index.js +2 -1
  67. package/src/runtime/server/page/load_data.js +1 -1
  68. package/src/runtime/server/page/render.js +36 -47
  69. package/src/runtime/server/page/respond_with_error.js +7 -8
  70. package/src/runtime/server/page/server_routing.js +13 -9
  71. package/src/runtime/server/remote.js +85 -39
  72. package/src/runtime/server/respond.js +92 -58
  73. package/src/runtime/server/utils.js +7 -7
  74. package/src/runtime/telemetry/otel.js +1 -1
  75. package/src/types/ambient.d.ts +10 -5
  76. package/src/types/global-private.d.ts +4 -4
  77. package/src/types/internal.d.ts +17 -20
  78. package/src/types/private.d.ts +33 -1
  79. package/src/types/synthetic/$lib.md +1 -1
  80. package/src/utils/error.js +12 -4
  81. package/src/utils/mime.js +9 -0
  82. package/src/utils/params.js +66 -0
  83. package/src/utils/routing.js +84 -38
  84. package/src/utils/shared-iterator.js +5 -0
  85. package/src/utils/streaming.js +14 -4
  86. package/src/utils/url.js +20 -2
  87. package/src/version.js +1 -1
  88. package/types/index.d.ts +434 -269
  89. package/types/index.d.ts.map +19 -15
  90. package/src/exports/internal/server.js +0 -22
  91. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -17,7 +17,6 @@ import {
17
17
  RequestEvent,
18
18
  SSRManifest,
19
19
  Emulator,
20
- Adapter,
21
20
  ServerInit,
22
21
  ClientInit,
23
22
  Transport,
@@ -44,7 +43,6 @@ export interface ServerModule {
44
43
  export interface ServerInternalModule {
45
44
  set_assets(path: string): void;
46
45
  set_building(): void;
47
- set_env(environment: Record<string, string>): void;
48
46
  set_manifest(manifest: SSRManifest): void;
49
47
  set_prerendering(): void;
50
48
  set_read_implementation(implementation: (path: string) => ReadableStream): void;
@@ -101,6 +99,9 @@ export interface BuildData {
101
99
  routes?: SSRClientRoute[];
102
100
  stylesheets: string[];
103
101
  fonts: string[];
102
+ /**
103
+ * Whether the client uses public dynamic env vars — `$env/dynamic/public` or `$app/env/public`.
104
+ */
104
105
  uses_env_dynamic_public: boolean;
105
106
  /** Only set in case of `bundleStrategy === 'inline'`. */
106
107
  inline?: {
@@ -194,7 +195,7 @@ export class InternalServer extends Server {
194
195
  }
195
196
 
196
197
  export interface ManifestData {
197
- /** Static files from `kit.config.files.assets`. */
198
+ /** Static files from `config.files.assets`. */
198
199
  assets: Asset[];
199
200
  hooks: {
200
201
  client: string | null;
@@ -203,7 +204,7 @@ export interface ManifestData {
203
204
  };
204
205
  nodes: PageNode[];
205
206
  routes: RouteData[];
206
- matchers: Record<string, string>;
207
+ params: string | null;
207
208
  }
208
209
 
209
210
  export interface RemoteChunk {
@@ -246,7 +247,7 @@ export type RecursiveRequired<T> = {
246
247
  // Recursive implementation of TypeScript's Required utility type.
247
248
  // Will recursively continue until it reaches a primitive or Function
248
249
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
249
- [K in keyof T]-?: Extract<T[K], Function> extends never // If it does not have a Function type
250
+ [K in keyof T]-?: Extract<T[K], Function | (`${string}:` & {})> extends never // If it does not have a Function type
250
251
  ? RecursiveRequired<T[K]> // recursively continue through.
251
252
  : T[K]; // Use the exact type for everything else
252
253
  };
@@ -293,6 +294,7 @@ export interface RouteData {
293
294
 
294
295
  export type ServerRedirectNode = {
295
296
  type: 'redirect';
297
+ status: number;
296
298
  location: string;
297
299
  };
298
300
 
@@ -308,7 +310,7 @@ export type RemoteFunctionDataNode = {
308
310
  /** value */
309
311
  v?: any;
310
312
  /** error */
311
- e?: [status: number, error: any];
313
+ e?: App.Error;
312
314
  };
313
315
 
314
316
  export type RemoteFunctionData = {
@@ -389,10 +391,6 @@ export interface ServerDataSkippedNode {
389
391
  export interface ServerErrorNode {
390
392
  type: 'error';
391
393
  error: App.Error;
392
- /**
393
- * Only set for HttpErrors.
394
- */
395
- status?: number;
396
394
  }
397
395
 
398
396
  export interface ServerMetadataRoute {
@@ -419,23 +417,23 @@ export interface ServerMetadata {
419
417
  remotes: Map<string, Map<string, { type: RemoteInternals['type']; dynamic: boolean }>>;
420
418
  }
421
419
 
420
+ // TODO get rid of this in favor us using just import('svelte').Component<any, any, any>
422
421
  export interface SSRComponent {
423
422
  default: {
424
423
  render(
425
424
  props: Record<string, any>,
426
425
  opts: { context: Map<any, any>; csp?: { nonce?: string; hash?: boolean } }
427
- ): {
428
- html: string;
426
+ ): Promise<{
427
+ body: string;
429
428
  head: string;
430
429
  css: {
431
430
  code: string;
432
431
  map: any; // TODO
433
432
  };
434
- /** Until we require all Svelte versions that support hashes, this might not be defined */
435
- hashes?: {
433
+ hashes: {
436
434
  script: Array<`sha256-${string}`>;
437
435
  };
438
- };
436
+ }>;
439
437
  };
440
438
  }
441
439
 
@@ -495,7 +493,6 @@ export type SSRNodeLoader = () => Promise<SSRNode>;
495
493
 
496
494
  export interface SSROptions {
497
495
  app_template_contains_nonce: boolean;
498
- async: boolean;
499
496
  csp: ValidatedConfig['kit']['csp'];
500
497
  csrf_check_origin: boolean;
501
498
  csrf_trusted_origins: string[];
@@ -503,6 +500,7 @@ export interface SSROptions {
503
500
  hash_routing: boolean;
504
501
  hooks: ServerHooks;
505
502
  link_header_preload: ValidatedConfig['kit']['output']['linkHeaderPreload'];
503
+ paths_origin: string | undefined;
506
504
  root: SSRComponent['default'];
507
505
  service_worker: boolean;
508
506
  service_worker_options: RegistrationOptions;
@@ -599,13 +597,12 @@ export interface Uses {
599
597
  search_params: Set<string>;
600
598
  }
601
599
 
602
- export type ValidatedConfig = Config & {
600
+ export type ValidatedConfig = Omit<Config, 'kit'> & {
603
601
  kit: ValidatedKitConfig;
604
602
  extensions: string[];
605
603
  };
606
604
 
607
- // TODO: remove the omit in 4.0
608
- export type ValidatedKitConfig = Omit<RecursiveRequired<KitConfig>, 'adapter'>;
605
+ export type ValidatedKitConfig = RecursiveRequired<KitConfig>;
609
606
 
610
607
  export type BinaryFormMeta = {
611
608
  remote_refreshes?: string[];
@@ -729,7 +726,7 @@ export interface RequestState {
729
726
  string,
730
727
  {
731
728
  internals: RemoteInternals;
732
- promise: Promise<any>;
729
+ fn: () => Promise<any>;
733
730
  }
734
731
  >;
735
732
  /** Instances created via `myForm.for(...)` */
@@ -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
  }
@@ -212,6 +224,10 @@ export interface PrerenderUnseenRoutesHandler {
212
224
  (details: { routes: string[]; message: string }): void;
213
225
  }
214
226
 
227
+ export interface PrerenderInvalidUrlHandler {
228
+ (details: { href: string; referrer: string | null; message: string }): void;
229
+ }
230
+
215
231
  export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
216
232
  export type PrerenderMissingIdHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderMissingIdHandler;
217
233
  export type PrerenderUnseenRoutesHandlerValue =
@@ -224,6 +240,11 @@ export type PrerenderEntryGeneratorMismatchHandlerValue =
224
240
  | 'warn'
225
241
  | 'ignore'
226
242
  | PrerenderEntryGeneratorMismatchHandler;
243
+ export type PrerenderInvalidUrlHandlerValue =
244
+ | 'fail'
245
+ | 'warn'
246
+ | 'ignore'
247
+ | PrerenderInvalidUrlHandler;
227
248
 
228
249
  export type PrerenderOption = boolean | 'auto';
229
250
 
@@ -252,3 +273,14 @@ export type DeepPartial<T> = T extends Record<PropertyKey, unknown> | unknown[]
252
273
  : T | undefined;
253
274
 
254
275
  export type IsAny<T> = 0 extends 1 & T ? true : false;
276
+
277
+ export type HasNonOptionalBoolean<T> =
278
+ IsAny<T> extends true
279
+ ? never
280
+ : [T] extends [boolean]
281
+ ? true
282
+ : T extends Array<infer U>
283
+ ? HasNonOptionalBoolean<U>
284
+ : T extends Record<string, any>
285
+ ? { [K in keyof T]: HasNonOptionalBoolean<T[K]> }[keyof T]
286
+ : never;
@@ -1,4 +1,4 @@
1
- This is a simple alias to `src/lib`, or whatever directory is specified as [`config.kit.files.lib`](https://svelte.dev/docs/kit/configuration#files). It allows you to access common components and utility modules without `../../../../` nonsense.
1
+ This is a simple alias to `src/lib`, or whatever directory is specified as [`config.files.lib`](https://svelte.dev/docs/kit/configuration#files). It allows you to access common components and utility modules without `../../../../` nonsense.
2
2
 
3
3
  ### `$lib/server`
4
4
 
@@ -30,16 +30,24 @@ export function coalesce_to_error(err) {
30
30
  * @param {unknown} error
31
31
  */
32
32
  export function normalize_error(error) {
33
- return /** @type {import('../exports/internal/index.js').Redirect | HttpError | SvelteKitError | Error} */ (
33
+ return /** @type {import('../exports/internal/shared.js').Redirect | HttpError | SvelteKitError | Error} */ (
34
34
  error
35
35
  );
36
36
  }
37
37
 
38
38
  /**
39
- * @param {unknown} error
39
+ * @param {any} transformed
40
+ * @param {any} [error]
40
41
  */
41
- export function get_status(error) {
42
- 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
+ }
43
51
  }
44
52
 
45
53
  /**
@@ -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 };
@@ -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,5 +1,4 @@
1
1
  import { BROWSER } from 'esm-env';
2
- import { decode_params } from './url.js';
3
2
 
4
3
  const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
5
4
 
@@ -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;
@@ -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
  */
@@ -1,3 +1,5 @@
1
+ import { noop } from './functions.js';
2
+
1
3
  /**
2
4
  * Create an async iterator and a function to push values into it
3
5
  * @template T
@@ -30,10 +32,18 @@ export function create_async_iterator() {
30
32
  };
31
33
  },
32
34
  add: (promise) => {
33
- deferred.push(Promise.withResolvers());
34
- void promise.then((value) => {
35
- deferred[++resolved].resolve(value);
36
- });
35
+ const next = Promise.withResolvers();
36
+ void next.promise.catch(noop); // prevent unhandled rejection potentially crashing the process
37
+ deferred.push(next);
38
+
39
+ void promise.then(
40
+ (value) => {
41
+ deferred[++resolved].resolve(value);
42
+ },
43
+ (error) => {
44
+ deferred[++resolved].reject(error);
45
+ }
46
+ );
37
47
  }
38
48
  };
39
49
  }
package/src/utils/url.js CHANGED
@@ -4,7 +4,7 @@ import { BROWSER, DEV } from 'esm-env';
4
4
  * Matches a URI scheme. See https://www.rfc-editor.org/rfc/rfc3986#section-3.1
5
5
  * @type {RegExp}
6
6
  */
7
- export const SCHEME = /^[a-z][a-z\d+\-.]+:/i;
7
+ export const SCHEME = /^[a-z][a-z\d+\-.]*:/i;
8
8
 
9
9
  const internal = new URL('a://');
10
10
 
@@ -27,6 +27,24 @@ export function is_root_relative(path) {
27
27
  return path[0] === '/' && path[1] !== '/';
28
28
  }
29
29
 
30
+ /**
31
+ * @param {string} location
32
+ * @param {string} allowed
33
+ */
34
+ export function matches_external_allowlist_entry(location, allowed) {
35
+ if (location === allowed) return true;
36
+
37
+ try {
38
+ const allow = new URL(allowed);
39
+ const loc = new URL(location, allow);
40
+
41
+ // this is stricter than `loc.origin === allow.origin`, which can fail in `blob:` cases
42
+ return loc.protocol === allow.protocol && loc.host === allow.host;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
30
48
  /**
31
49
  * @param {string} path
32
50
  * @param {import('types').TrailingSlash} trailing_slash
@@ -119,6 +137,7 @@ export function make_trackable(url, callback, search_params_callback, allow_hash
119
137
  /**
120
138
  * URL properties that could change during the lifetime of the page,
121
139
  * which excludes things like `origin`
140
+ * @type {(keyof URL)[]}
122
141
  */
123
142
  const tracked_url_properties = ['href', 'pathname', 'search', 'toString', 'toJSON'];
124
143
  if (allow_hash) tracked_url_properties.push('hash');
@@ -127,7 +146,6 @@ export function make_trackable(url, callback, search_params_callback, allow_hash
127
146
  Object.defineProperty(tracked, property, {
128
147
  get() {
129
148
  callback();
130
- // @ts-expect-error
131
149
  return url[property];
132
150
  },
133
151
 
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // generated during release, do not modify
2
2
 
3
3
  /** @type {string} */
4
- export const VERSION = '3.0.0-next.4';
4
+ export const VERSION = '3.0.0-next.7';