@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.15

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 (86) hide show
  1. package/package.json +6 -3
  2. package/src/cli.js +8 -5
  3. package/src/core/adapt/builder.js +20 -10
  4. package/src/core/config/index.js +4 -5
  5. package/src/core/env.js +3 -3
  6. package/src/core/generate_manifest/index.js +6 -0
  7. package/src/core/postbuild/entities.js +8 -2
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +18 -13
  10. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  11. package/src/core/sync/create_manifest_data/index.js +33 -2
  12. package/src/core/sync/sync.js +3 -2
  13. package/src/core/sync/utils.js +2 -2
  14. package/src/core/sync/write_app_types.js +72 -27
  15. package/src/core/sync/write_tsconfig/index.js +73 -54
  16. package/src/core/sync/write_tsconfig/utils.js +0 -61
  17. package/src/core/sync/write_tsconfig/validate.js +128 -0
  18. package/src/core/sync/write_types/index.js +7 -4
  19. package/src/exports/node/index.js +2 -7
  20. package/src/exports/public.d.ts +25 -18
  21. package/src/exports/vite/build/build_server.js +2 -3
  22. package/src/exports/vite/dev/index.js +44 -36
  23. package/src/exports/vite/index.js +91 -16
  24. package/src/exports/vite/utils.js +62 -15
  25. package/src/runner.js +4 -2
  26. package/src/runtime/app/forms.js +4 -7
  27. package/src/runtime/app/internal/transport.js +53 -0
  28. package/src/runtime/app/paths/client.js +2 -2
  29. package/src/runtime/app/paths/internal/client.js +2 -2
  30. package/src/runtime/app/server/remote/prerender.js +6 -10
  31. package/src/runtime/app/server/remote/query.js +3 -9
  32. package/src/runtime/app/server/remote/requested.js +2 -2
  33. package/src/runtime/app/server/remote/shared.js +1 -16
  34. package/src/runtime/client/client.js +177 -63
  35. package/src/runtime/client/fetcher.js +2 -13
  36. package/src/runtime/client/parse.js +1 -1
  37. package/src/runtime/client/remote-functions/command.svelte.js +1 -2
  38. package/src/runtime/client/remote-functions/form.svelte.js +3 -7
  39. package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
  40. package/src/runtime/client/remote-functions/query/index.js +1 -1
  41. package/src/runtime/client/remote-functions/query/proxy.js +2 -2
  42. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  43. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  44. package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
  45. package/src/runtime/client/utils.js +1 -0
  46. package/src/runtime/form-utils.js +4 -6
  47. package/src/runtime/pathname.js +37 -0
  48. package/src/runtime/server/data/index.js +5 -8
  49. package/src/runtime/server/dev.js +22 -0
  50. package/src/runtime/server/endpoint.js +3 -4
  51. package/src/runtime/server/fetch.js +25 -30
  52. package/src/runtime/server/index.js +64 -46
  53. package/src/runtime/server/internal.js +12 -5
  54. package/src/runtime/server/page/actions.js +18 -43
  55. package/src/runtime/server/page/crypto.js +2 -2
  56. package/src/runtime/server/page/csp.js +2 -1
  57. package/src/runtime/server/page/data_serializer.js +12 -13
  58. package/src/runtime/server/page/index.js +16 -32
  59. package/src/runtime/server/page/load_data.js +28 -44
  60. package/src/runtime/server/page/render.js +28 -12
  61. package/src/runtime/server/page/respond_with_error.js +8 -20
  62. package/src/runtime/server/page/serialize_data.js +2 -13
  63. package/src/runtime/server/page/server_routing.js +58 -9
  64. package/src/runtime/server/remote-functions.js +11 -15
  65. package/src/runtime/server/respond.js +44 -56
  66. package/src/runtime/server/state.js +60 -0
  67. package/src/runtime/server/utils.js +0 -20
  68. package/src/runtime/shared.js +20 -40
  69. package/src/runtime/utils.js +3 -0
  70. package/src/types/ambient-private.d.ts +1 -1
  71. package/src/types/ambient.d.ts +58 -7
  72. package/src/types/global-private.d.ts +1 -1
  73. package/src/types/internal.d.ts +47 -53
  74. package/src/utils/filesystem.js +1 -23
  75. package/src/utils/hash.js +21 -0
  76. package/src/utils/http.js +8 -7
  77. package/src/utils/import.js +2 -1
  78. package/src/utils/params.js +1 -1
  79. package/src/utils/regex.js +9 -0
  80. package/src/utils/routing.js +52 -27
  81. package/src/utils/url.js +1 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +98 -28
  84. package/types/index.d.ts.map +1 -1
  85. package/src/exports/node/polyfills.js +0 -30
  86. package/src/runtime/server/app.js +0 -9
@@ -158,7 +158,7 @@ export interface ServerHooks {
158
158
  handleError: HandleServerError;
159
159
  handleValidationError: HandleValidationError;
160
160
  reroute: Reroute;
161
- transport: Transport;
161
+ transport?: Transport;
162
162
  init?: ServerInit;
163
163
  }
164
164
 
@@ -174,24 +174,23 @@ export interface Env {
174
174
  public: Record<string, string>;
175
175
  }
176
176
 
177
+ export interface InternalRequestOptions extends RequestOptions {
178
+ prerendering?: PrerenderOptions;
179
+ /** @internal for saving dependencies during prerendering and generating fallback pages */
180
+ read: (file: string) => Buffer<ArrayBuffer>;
181
+ /** @internal used during development to check feature availability depending on the current route */
182
+ before_handle?: (
183
+ event: RequestEvent,
184
+ config: any,
185
+ prerender: PrerenderOption,
186
+ handle: () => Promise<Response>
187
+ ) => Promise<Response>;
188
+ emulator?: Emulator;
189
+ }
190
+
177
191
  export class InternalServer extends Server {
178
192
  init(options: ServerInitOptions): Promise<void>;
179
- respond(
180
- request: Request,
181
- options: RequestOptions & {
182
- prerendering?: PrerenderOptions;
183
- /** @internal for saving dependencies during prerendering and generating fallback pages */
184
- read: (file: string) => Buffer<ArrayBuffer>;
185
- /** @internal used during development to check feature availability depending on the current route */
186
- before_handle?: (
187
- event: RequestEvent,
188
- config: any,
189
- prerender: PrerenderOption,
190
- handle: () => Promise<Response>
191
- ) => Promise<Response>;
192
- emulator?: Emulator;
193
- }
194
- ): Promise<Response>;
193
+ respond(request: Request, options: InternalRequestOptions): Promise<Response>;
195
194
  }
196
195
 
197
196
  export interface ManifestData {
@@ -239,6 +238,8 @@ export interface PrerenderOptions {
239
238
  dependencies: Map<string, PrerenderDependency>;
240
239
  /** Results of remote `prerender` functions, shared across the whole prerender run so that each only executes once */
241
240
  remote_responses: Map<string, Promise<any>>;
241
+ /** Route IDs whose resolution module has been emitted, shared across the whole prerender run so that each only generates once */
242
+ resolved_route_ids: Set<string>;
242
243
  /** True for the duration of a call to the `reroute` hook */
243
244
  inside_reroute?: boolean;
244
245
  }
@@ -535,39 +536,6 @@ export interface SSRClientRoute {
535
536
  leaf: [has_server_load: boolean, node_id: number];
536
537
  }
537
538
 
538
- export interface SSRState {
539
- fallback?: string;
540
- getClientAddress(): string;
541
- /**
542
- * True if we're currently attempting to render an error page.
543
- */
544
- error: boolean;
545
- /**
546
- * Allows us to prevent `event.fetch` from making infinitely looping internal requests.
547
- */
548
- depth: number;
549
- platform?: any;
550
- prerendering?: PrerenderOptions;
551
- /**
552
- * When fetching data from a +server.js endpoint in `load`, the page's
553
- * prerender option is inherited by the endpoint, unless overridden.
554
- */
555
- prerender_default?: PrerenderOption;
556
- /** @internal reads from the filesystem when user code tries to fetch a static asset */
557
- read?: (file: string) => Buffer<ArrayBuffer>;
558
- /**
559
- * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
560
- * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.
561
- */
562
- before_handle?: (
563
- event: RequestEvent,
564
- config: Record<string, any>,
565
- prerender: PrerenderOption,
566
- handle: () => Promise<Response>
567
- ) => Promise<Response>;
568
- emulator?: Emulator;
569
- }
570
-
571
539
  export type StrictBody = string | ArrayBufferView;
572
540
 
573
541
  export interface Uses {
@@ -681,8 +649,35 @@ export type RecordSpan = <T>(options: {
681
649
  * used for tracking things like remote function calls
682
650
  */
683
651
  export interface RequestState {
684
- readonly prerendering: PrerenderOptions | undefined;
685
- readonly transport: ServerHooks['transport'];
652
+ readonly getClientAddress: () => string;
653
+ readonly platform?: any;
654
+ /** @internal reads from the filesystem when user code tries to fetch a static asset */
655
+ readonly read?: (file: string) => Buffer<ArrayBuffer>;
656
+ /**
657
+ * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
658
+ * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.
659
+ */
660
+ readonly before_handle?: (
661
+ event: RequestEvent,
662
+ config: Record<string, any>,
663
+ prerender: PrerenderOption,
664
+ handle: () => Promise<Response>
665
+ ) => Promise<Response>;
666
+ readonly emulator?: Emulator;
667
+ readonly prerendering?: PrerenderOptions;
668
+ /**
669
+ * When fetching data from a +server.js endpoint in `load`, the page's
670
+ * prerender option is inherited by the endpoint, unless overridden.
671
+ */
672
+ prerender_default?: PrerenderOption;
673
+ /**
674
+ * True if we're currently attempting to render an error page.
675
+ */
676
+ error: boolean;
677
+ /**
678
+ * Allows us to prevent `event.fetch` from making infinitely looping internal requests.
679
+ */
680
+ readonly depth: number;
686
681
  readonly handleValidationError: ServerHooks['handleValidationError'];
687
682
  readonly tracing: {
688
683
  record_span: RecordSpan;
@@ -737,7 +732,6 @@ export interface RequestState {
737
732
  readonly is_in_remote_query: boolean;
738
733
  readonly is_in_remote_prerender: boolean;
739
734
  readonly is_in_render: boolean;
740
- readonly is_in_universal_load: boolean;
741
735
  /**
742
736
  * The event before `derive_remote_function_event` hid or stubbed properties.
743
737
  * Hooks like `handleValidationError` receive this so `url` etc. stay accessible
@@ -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/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,5 +1,6 @@
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
6
  * Resolves a peer dependency relative to the current working directory.
@@ -51,7 +52,7 @@ function resolve_peer(dependency, root) {
51
52
  */
52
53
  export async function import_peer(dependency, root) {
53
54
  try {
54
- return await import(/* @vite-ignore */ resolve_peer(dependency, root));
55
+ return await import(/* @vite-ignore */ pathToFileURL(resolve_peer(dependency, root)).href);
55
56
  } catch {
56
57
  return await import(/* @vite-ignore */ dependency);
57
58
  }
@@ -25,7 +25,7 @@ export function collect_matcher_names(routes) {
25
25
  */
26
26
  export function validate_param_matchers(params, names, file) {
27
27
  for (const name of names) {
28
- if (!(name in params)) {
28
+ if (!Object.hasOwn(params, name)) {
29
29
  throw new Error(`No matcher found for parameter '${name}'${file ? ` in ${file}` : ''}`);
30
30
  }
31
31
  }
@@ -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
+ }
@@ -1,9 +1,32 @@
1
1
  import { BROWSER } from 'esm-env';
2
+ import { escape_for_regexp } from './regex.js';
2
3
 
3
4
  const param_pattern = /^(\[)?(\.\.\.)?([\w-]+)(?:=([\w-]+))?(\])?$/;
4
5
 
5
6
  const root_group_pattern = /^\/\((?:[^)]+)\)$/;
6
7
 
8
+ const escape_sequence_pattern = /\[([ux])\+([^\]]+)\]/;
9
+
10
+ /**
11
+ * Decodes the codepoints of an `[x+nn]` or `[u+nnnn]` escape sequence
12
+ * @param {string} code the sequence without its `[x+`/`[u+` prefix or `]` suffix
13
+ */
14
+ export function decode_escape_sequence(code) {
15
+ return String.fromCodePoint(...code.split('-').map((codepoint) => parseInt(codepoint, 16)));
16
+ }
17
+
18
+ /**
19
+ * Encodes the characters that `decode_pathname` leaves untouched, so that a decoded
20
+ * escape sequence still matches the pattern `parse_route_id` builds for it
21
+ * @param {string} str
22
+ */
23
+ export function encode_pathname_chars(str) {
24
+ return str.replace(
25
+ /[%/?#]/g,
26
+ (char) => '%' + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')
27
+ );
28
+ }
29
+
7
30
  /**
8
31
  * Creates the regex pattern, extracts parameter names, and generates types for a route
9
32
  * @param {string} id
@@ -51,19 +74,8 @@ export function parse_route_id(id) {
51
74
  const result = parts
52
75
  .map((content, i) => {
53
76
  if (i % 2) {
54
- if (content.startsWith('x+')) {
55
- return escape(String.fromCharCode(parseInt(content.slice(2), 16)));
56
- }
57
-
58
- if (content.startsWith('u+')) {
59
- return escape(
60
- String.fromCharCode(
61
- ...content
62
- .slice(2)
63
- .split('-')
64
- .map((code) => parseInt(code, 16))
65
- )
66
- );
77
+ if (content.startsWith('x+') || content.startsWith('u+')) {
78
+ return escape(decode_escape_sequence(content.slice(2)));
67
79
  }
68
80
 
69
81
  // We know the match cannot be null in the browser because manifest generation
@@ -243,25 +255,36 @@ export function exec(match, params, matchers) {
243
255
  return result;
244
256
  }
245
257
 
258
+ /**
259
+ * `decode_pathname` leaves these characters untouched, so routes have to match their encoded forms
260
+ * @type {Record<string, string>}
261
+ */
262
+ const encoded = {
263
+ '%': '%25',
264
+ '/': '%2[Ff]',
265
+ '?': '%3[Ff]',
266
+ '#': '%23'
267
+ };
268
+
246
269
  /** @param {string} str */
247
270
  function escape(str) {
248
- return (
249
- str
250
- .normalize()
251
- // escape [ and ] before escaping other characters, since they are used in the replacements
252
- .replace(/[[\]]/g, '\\$&')
253
- // replace %, /, ? and # with their encoded versions because decode_pathname leaves them untouched
254
- .replace(/%/g, '%25')
255
- .replace(/\//g, '%2[Ff]')
256
- .replace(/\?/g, '%3[Ff]')
257
- .replace(/#/g, '%23')
258
- // escape characters that have special meaning in regex
259
- .replace(/[.*+?^${}()|\\]/g, '\\$&')
260
- );
271
+ // the replacements in `encoded` are regex source themselves, so they must not be escaped again
272
+ return str
273
+ .normalize()
274
+ .split(/([%/?#])/)
275
+ .map((part, i) => (i % 2 ? encoded[part] : escape_for_regexp(part)))
276
+ .join('');
261
277
  }
262
278
 
263
279
  const basic_param_pattern = /\[(\[)?(\.\.\.)?([\w-]+?)(?:=([\w-]+))?\]\]?/g;
264
280
 
281
+ // escape sequences are expanded in the same pass as the params, so that a param
282
+ // value containing `[x+2f]` is not itself expanded
283
+ export const segment_pattern = new RegExp(
284
+ `${escape_sequence_pattern.source}|${basic_param_pattern.source}`,
285
+ 'g'
286
+ );
287
+
265
288
  /**
266
289
  * Populate a route ID with params to resolve a pathname.
267
290
  * @example
@@ -286,7 +309,9 @@ export function resolve_route(id, params) {
286
309
  '/' +
287
310
  segments
288
311
  .map((segment) =>
289
- segment.replace(basic_param_pattern, (_, optional, rest, name) => {
312
+ segment.replace(segment_pattern, (_, escape_type, escape_code, optional, rest, name) => {
313
+ if (escape_type) return encode_pathname_chars(decode_escape_sequence(escape_code));
314
+
290
315
  const value = params[name];
291
316
 
292
317
  if (value === undefined || value === '') {
package/src/utils/url.js CHANGED
@@ -46,6 +46,7 @@ export function relative_pathname(from, to) {
46
46
  export function matches_external_allowlist_entry(location, allowed) {
47
47
  if (location === allowed) return true;
48
48
 
49
+ // TODO replace the try/catch with `URL.parse` when browser support allows (Chrome 126, Firefox 126, Safari 18)
49
50
  try {
50
51
  const allow = new URL(allowed);
51
52
  const loc = new URL(location, allow);
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.13';
4
+ export const VERSION = '3.0.0-next.15';