@sveltejs/kit 3.0.0-next.6 → 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 (64) hide show
  1. package/package.json +7 -2
  2. package/src/core/adapt/builder.js +11 -39
  3. package/src/core/config/index.js +76 -71
  4. package/src/core/config/options.js +280 -285
  5. package/src/core/config/types.d.ts +1 -1
  6. package/src/core/env.js +85 -1
  7. package/src/core/generate_manifest/index.js +12 -15
  8. package/src/core/sync/create_manifest_data/index.js +6 -44
  9. package/src/core/sync/write_client_manifest.js +7 -14
  10. package/src/core/sync/write_non_ambient.js +10 -7
  11. package/src/core/sync/write_root.js +9 -30
  12. package/src/core/sync/write_server.js +0 -1
  13. package/src/core/sync/write_types/index.js +11 -10
  14. package/src/exports/index.js +3 -3
  15. package/src/exports/internal/client.js +5 -0
  16. package/src/exports/internal/index.js +1 -91
  17. package/src/exports/internal/{event.js → server/event.js} +1 -2
  18. package/src/exports/internal/server/index.js +33 -0
  19. package/src/exports/internal/shared.js +89 -0
  20. package/src/exports/node/index.js +1 -1
  21. package/src/exports/params.js +63 -0
  22. package/src/exports/public.d.ts +82 -32
  23. package/src/exports/url.js +84 -0
  24. package/src/exports/vite/dev/index.js +28 -17
  25. package/src/exports/vite/index.js +217 -156
  26. package/src/exports/vite/preview/index.js +1 -1
  27. package/src/exports/vite/utils.js +3 -5
  28. package/src/runtime/app/paths/client.js +3 -7
  29. package/src/runtime/app/paths/server.js +1 -1
  30. package/src/runtime/app/server/remote/query.js +6 -12
  31. package/src/runtime/app/state/client.js +1 -2
  32. package/src/runtime/app/stores.js +13 -76
  33. package/src/runtime/client/client.js +26 -79
  34. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  35. package/src/runtime/client/remote-functions/form.svelte.js +5 -24
  36. package/src/runtime/client/remote-functions/prerender.svelte.js +10 -3
  37. package/src/runtime/client/remote-functions/query/instance.svelte.js +18 -9
  38. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +16 -6
  39. package/src/runtime/client/remote-functions/shared.svelte.js +1 -2
  40. package/src/runtime/client/state.svelte.js +66 -49
  41. package/src/runtime/client/types.d.ts +1 -1
  42. package/src/runtime/client/utils.js +0 -96
  43. package/src/runtime/form-utils.js +15 -2
  44. package/src/runtime/server/index.js +1 -1
  45. package/src/runtime/server/page/load_data.js +1 -1
  46. package/src/runtime/server/page/render.js +15 -24
  47. package/src/runtime/server/page/server_routing.js +13 -9
  48. package/src/runtime/server/remote.js +21 -12
  49. package/src/runtime/server/respond.js +11 -8
  50. package/src/runtime/telemetry/otel.js +1 -1
  51. package/src/types/ambient.d.ts +5 -1
  52. package/src/types/global-private.d.ts +1 -1
  53. package/src/types/internal.d.ts +9 -10
  54. package/src/utils/error.js +1 -1
  55. package/src/utils/mime.js +9 -0
  56. package/src/utils/params.js +66 -0
  57. package/src/utils/routing.js +84 -38
  58. package/src/utils/streaming.js +14 -4
  59. package/src/utils/url.js +0 -79
  60. package/src/version.js +1 -1
  61. package/types/index.d.ts +98 -87
  62. package/types/index.d.ts.map +10 -7
  63. package/src/exports/internal/server.js +0 -22
  64. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -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
  }
@@ -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
@@ -1,13 +1,10 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
- import { try_get_request_store } from '../exports/internal/event.js';
3
2
 
4
3
  /**
5
4
  * Matches a URI scheme. See https://www.rfc-editor.org/rfc/rfc3986#section-3.1
6
5
  * @type {RegExp}
7
6
  */
8
7
  export const SCHEME = /^[a-z][a-z\d+\-.]*:/i;
9
- // See https://datatracker.ietf.org/doc/html/rfc2606 - no domains under the .invalid TLD can be registered
10
- const REDIRECT_BASE = 'https://sveltekit-redirect.invalid';
11
8
 
12
9
  const internal = new URL('a://');
13
10
 
@@ -30,32 +27,6 @@ export function is_root_relative(path) {
30
27
  return path[0] === '/' && path[1] !== '/';
31
28
  }
32
29
 
33
- /**
34
- * Whether a redirect location is absolute, i.e. not a root-relative or path-relative URL,
35
- * and not pointing to the same origin as we're currently on (if determineable).
36
- * @param {string} location
37
- */
38
- export function is_external_location(location) {
39
- const origin = BROWSER ? window.location.origin : try_get_request_store()?.event.url.origin;
40
-
41
- try {
42
- return !matches_external_allowlist_entry(location, origin ?? REDIRECT_BASE);
43
- } catch {
44
- return true;
45
- }
46
- }
47
-
48
- /**
49
- * @param {string} location
50
- */
51
- function is_javascript_location(location) {
52
- try {
53
- return new URL(location, REDIRECT_BASE).protocol === 'javascript:';
54
- } catch {
55
- return false;
56
- }
57
- }
58
-
59
30
  /**
60
31
  * @param {string} location
61
32
  * @param {string} allowed
@@ -74,56 +45,6 @@ export function matches_external_allowlist_entry(location, allowed) {
74
45
  }
75
46
  }
76
47
 
77
- /**
78
- * @param {string} location
79
- * @param {{ external?: boolean | string[] }} [options]
80
- */
81
- export function validate_redirect_location(location, options) {
82
- if (!is_external_location(location)) return;
83
-
84
- const external = options?.external;
85
-
86
- if (!external) {
87
- throw new Error(
88
- DEV
89
- ? `Cannot redirect to external URL ${JSON.stringify(location)}. ` +
90
- 'To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect`'
91
- : 'Cannot redirect to external URL unless explicitly allowed'
92
- );
93
- }
94
-
95
- if (external === true) {
96
- if (is_javascript_location(location)) {
97
- throw new Error(
98
- DEV
99
- ? `Cannot redirect to ${JSON.stringify(location)} with \`{ external: true }\`. ` +
100
- 'The `:javascript` protocol must be explicitly listed in the `external` allowlist'
101
- : 'Cannot redirect to external URL unless explicitly allowed'
102
- );
103
- }
104
-
105
- return;
106
- }
107
-
108
- if (Array.isArray(external)) {
109
- if (!external.some((allowed) => matches_external_allowlist_entry(location, allowed))) {
110
- throw new Error(
111
- DEV
112
- ? `Cannot redirect to ${JSON.stringify(location)}: URL origin is not included in the \`external\` allowlist`
113
- : 'Cannot redirect to external URL unless explicitly allowed'
114
- );
115
- }
116
-
117
- return;
118
- }
119
-
120
- throw new Error(
121
- DEV
122
- ? '`redirect` options.external must be `true` or an array of allowed origins'
123
- : 'Invalid redirect options.external value'
124
- );
125
- }
126
-
127
48
  /**
128
49
  * @param {string} path
129
50
  * @param {import('types').TrailingSlash} trailing_slash
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.6';
4
+ export const VERSION = '3.0.0-next.7';