@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
@@ -1,6 +1,6 @@
1
1
  import { createReadStream } from 'node:fs';
2
2
  import { Readable } from 'node:stream';
3
- import { SvelteKitError } from '../internal/index.js';
3
+ import { SvelteKitError } from '../internal/shared.js';
4
4
  import { noop } from '../../utils/functions.js';
5
5
 
6
6
  /** @type {WeakMap<import('http').IncomingMessage, (chunk: Buffer) => void>} */
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Define [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching) for your app.
3
+ *
4
+ * @example
5
+ * ```js
6
+ * import { defineParams } from '@sveltejs/kit';
7
+ * import * as v from 'valibot';
8
+ *
9
+ * export const params = defineParams({
10
+ * locale: (param) => {
11
+ * if (param !== 'de' && param !== 'en') return;
12
+ * return param;
13
+ * },
14
+ * number: v.pipe(v.string(), v.toNumber())
15
+ * });
16
+ * ```
17
+ *
18
+ * @template {Record<string, import('./public.js').ParamDefinition>} T
19
+ * @param {T} definitions
20
+ * @returns {import('./public.js').DefinedParams<T>}
21
+ */
22
+ export function defineParams(definitions) {
23
+ /** @type {Record<string, import('./public.js').ParamMatcher>} */
24
+ const matchers = {};
25
+
26
+ for (const [key, definition] of Object.entries(definitions)) {
27
+ matchers[key] = normalize_param_definition(definition);
28
+ }
29
+
30
+ return /** @type {import('./public.js').DefinedParams<T>} */ (matchers);
31
+ }
32
+
33
+ /**
34
+ * @param {import('@sveltejs/kit').ParamDefinition} definition
35
+ * @returns {import('@sveltejs/kit').ParamMatcher}
36
+ */
37
+ export function normalize_param_definition(definition) {
38
+ if (typeof definition === 'function') {
39
+ return /** @type {import('@sveltejs/kit').ParamMatcher} */ (
40
+ /** @type {unknown} */ ({
41
+ '~standard': {
42
+ validate(/** @type {unknown} */ value) {
43
+ const result = definition(/** @type {string} */ (value));
44
+
45
+ if (result === undefined) {
46
+ return { issues: [{ message: 'Invalid param' }] };
47
+ }
48
+
49
+ if (/** @type {any} */ (result) instanceof Promise) return result; // will be validated and rejected upstream
50
+
51
+ return { value: result };
52
+ }
53
+ }
54
+ })
55
+ );
56
+ }
57
+
58
+ if (definition && typeof definition === 'object' && '~standard' in definition) {
59
+ return definition;
60
+ }
61
+
62
+ throw new Error('Invalid param definition');
63
+ }
@@ -23,6 +23,7 @@ import {
23
23
  import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types';
24
24
  import { SvelteConfig } from '@sveltejs/vite-plugin-svelte';
25
25
  import { StandardSchemaV1 } from '@standard-schema/spec';
26
+ import { Plugin } from 'vite';
26
27
  import {
27
28
  RouteId as AppRouteId,
28
29
  LayoutParams as AppLayoutParams,
@@ -70,6 +71,13 @@ export interface Adapter {
70
71
  * during dev, build and prerendering.
71
72
  */
72
73
  emulate?: () => MaybePromise<Emulator>;
74
+ vite?: {
75
+ /**
76
+ * Plugins provided by the adapter are placed before any of SvelteKit's own plugins.
77
+ * @since 3.0.0
78
+ */
79
+ plugins?: Plugin[];
80
+ };
73
81
  }
74
82
 
75
83
  export type LoadProperties<input extends Record<string, any> | void> = input extends void
@@ -490,32 +498,6 @@ export interface KitConfig {
490
498
  };
491
499
  /** Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release. */
492
500
  experimental?: {
493
- /**
494
- * Options for enabling server-side [OpenTelemetry](https://opentelemetry.io/) tracing for SvelteKit operations including the [`handle` hook](https://svelte.dev/docs/kit/hooks#Server-hooks-handle), [`load` functions](https://svelte.dev/docs/kit/load), [form actions](https://svelte.dev/docs/kit/form-actions), and [remote functions](https://svelte.dev/docs/kit/remote-functions).
495
- * @default { server: false, serverFile: false }
496
- * @since 2.31.0
497
- */
498
- tracing?: {
499
- /**
500
- * Enables server-side [OpenTelemetry](https://opentelemetry.io/) span emission for SvelteKit operations including the [`handle` hook](https://svelte.dev/docs/kit/hooks#Server-hooks-handle), [`load` functions](https://svelte.dev/docs/kit/load), [form actions](https://svelte.dev/docs/kit/form-actions), and [remote functions](https://svelte.dev/docs/kit/remote-functions).
501
- * @default false
502
- * @since 2.31.0
503
- */
504
- server?: boolean;
505
- };
506
-
507
- /**
508
- * @since 2.31.0
509
- */
510
- instrumentation?: {
511
- /**
512
- * Enables `instrumentation.server.js` for tracing and observability instrumentation.
513
- * @default false
514
- * @since 2.31.0
515
- */
516
- server?: boolean;
517
- };
518
-
519
501
  /**
520
502
  * Whether to enable the experimental remote functions feature. This feature is not yet stable and may be changed or removed at any time.
521
503
  * @default false
@@ -719,7 +701,7 @@ export interface KitConfig {
719
701
  * @default undefined
720
702
  * @since 3.0
721
703
  */
722
- origin?: `http://${string}` | `https://${string}`;
704
+ origin?: string;
723
705
  /**
724
706
  * Whether to use relative asset paths.
725
707
  *
@@ -904,6 +886,17 @@ export interface KitConfig {
904
886
  register?: false;
905
887
  }
906
888
  );
889
+ /**
890
+ * Options for enabling [OpenTelemetry](https://opentelemetry.io/) tracing for SvelteKit operations.
891
+ * @default { server: false }
892
+ */
893
+ tracing?: {
894
+ /**
895
+ * Enables server-side [OpenTelemetry](https://opentelemetry.io/) span emission for SvelteKit operations including the [`handle` hook](https://svelte.dev/docs/kit/hooks#Server-hooks-handle), [`load` functions](https://svelte.dev/docs/kit/load), [form actions](https://svelte.dev/docs/kit/form-actions), and [remote functions](https://svelte.dev/docs/kit/remote-functions). Tracing — and more significantly, observability instrumentation — can have a nontrivial overhead, so consider whether you really need it, or if it might be more appropriate to turn it on in development and preview environments only.
896
+ * @default false
897
+ */
898
+ server?: boolean;
899
+ };
907
900
  typescript?: {
908
901
  /**
909
902
  * A function that allows you to edit the generated `tsconfig.json`. You can mutate the config (recommended) or return a new one.
@@ -1078,7 +1071,7 @@ export type Transport = Record<string, Transporter>;
1078
1071
  */
1079
1072
  export interface Transporter<
1080
1073
  T = any,
1081
- U = Exclude<any, false | 0 | '' | null | undefined | typeof NaN>
1074
+ U = any /* minus falsy values, but we can't properly express that */
1082
1075
  > {
1083
1076
  encode: (value: T) => false | U;
1084
1077
  decode: (data: U) => T;
@@ -1437,7 +1430,7 @@ export type AfterNavigate = (Navigation | NavigationEnter) & {
1437
1430
  };
1438
1431
 
1439
1432
  /**
1440
- * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store.
1433
+ * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object.
1441
1434
  */
1442
1435
  export interface Page<
1443
1436
  Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>,
@@ -1485,7 +1478,64 @@ export interface Page<
1485
1478
  /**
1486
1479
  * The shape of a param matcher. See [matching](https://svelte.dev/docs/kit/advanced-routing#Matching) for more info.
1487
1480
  */
1488
- export type ParamMatcher = (param: string) => boolean;
1481
+ export type ParamMatcher<Output = any> = StandardSchemaV1<string, Output>;
1482
+
1483
+ /**
1484
+ * A value that can be parsed from a URL param and losslessly encoded with `String(...)`.
1485
+ */
1486
+ export type ParamValue = string | number | boolean | bigint;
1487
+
1488
+ /**
1489
+ * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams).
1490
+ */
1491
+ export type ParamDefinition =
1492
+ | ((param: string) => ParamValue | undefined)
1493
+ | StandardSchemaV1<string, ParamValue>;
1494
+
1495
+ /**
1496
+ * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams).
1497
+ */
1498
+ export type DefinedParams<T extends Record<string, ParamDefinition>> = {
1499
+ readonly [K in keyof T]: ParamEntry<T[K]>;
1500
+ };
1501
+
1502
+ /**
1503
+ * Normalizes a property of defineParams (schema or function) to standard schema.
1504
+ */
1505
+ type ParamEntry<M> =
1506
+ M extends StandardSchemaV1<any, any>
1507
+ ? StandardSchemaV1.InferOutput<M> extends ParamValue
1508
+ ? StandardSchemaV1<any, M>
1509
+ : StandardSchemaV1<any, never>
1510
+ : M extends (param: string) => infer R
1511
+ ? Exclude<R, undefined> extends ParamValue
1512
+ ? StandardSchemaV1<any, Exclude<R, undefined>>
1513
+ : StandardSchemaV1<any, never>
1514
+ : never;
1515
+
1516
+ /**
1517
+ * Extracts the param type from a matcher.
1518
+ */
1519
+ export type MatcherParam<M extends StandardSchemaV1<any, any>> =
1520
+ M extends StandardSchemaV1<any, infer Inner>
1521
+ ? Inner extends ParamValue
1522
+ ? Inner
1523
+ : Inner extends StandardSchemaV1<any, any>
1524
+ ? StandardSchemaV1.InferOutput<Inner> extends ParamValue
1525
+ ? StandardSchemaV1.InferOutput<Inner>
1526
+ : never
1527
+ : never
1528
+ : never;
1529
+
1530
+ /**
1531
+ * Define [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching) for your app.
1532
+ *
1533
+ * @template T
1534
+ * @param definitions
1535
+ */
1536
+ export function defineParams<T extends Record<string, ParamDefinition>>(
1537
+ definitions: T
1538
+ ): DefinedParams<T>;
1489
1539
 
1490
1540
  /**
1491
1541
  * A single entry yielded by [`requested`](https://svelte.dev/docs/kit/$app-server#requested)
@@ -2284,8 +2334,8 @@ export type RemoteQueryUpdate =
2284
2334
  | RemoteQueryOverride;
2285
2335
 
2286
2336
  export type RemoteResource<T> = Promise<T> & {
2287
- /** The error in case the query fails. Most often this is a [`HttpError`](https://svelte.dev/docs/kit/@sveltejs-kit#HttpError) but it isn't guaranteed to be. */
2288
- get error(): any;
2337
+ /** The error in case the query fails. */
2338
+ get error(): App.Error | undefined;
2289
2339
  /** `true` before the first result is available and during refreshes */
2290
2340
  get loading(): boolean;
2291
2341
  } & (
@@ -0,0 +1,84 @@
1
+ import { DEV } from 'esm-env';
2
+ // we use the export subpath to conditionally import the client/server `get_origin`
3
+ // so that `node:async_hooks` isn't pulled into the client build
4
+ import { get_origin } from '#internal';
5
+ import { matches_external_allowlist_entry } from '../utils/url.js';
6
+
7
+ // See https://datatracker.ietf.org/doc/html/rfc2606 - no domains under the .invalid TLD can be registered
8
+ const REDIRECT_BASE = 'https://sveltekit-redirect.invalid';
9
+
10
+ /**
11
+ * Whether a redirect location is absolute, i.e. not a root-relative or path-relative URL,
12
+ * and not pointing to the same origin as we're currently on (if determineable).
13
+ * @param {string} location
14
+ */
15
+ export function is_external_location(location) {
16
+ const origin = get_origin();
17
+
18
+ try {
19
+ return !matches_external_allowlist_entry(location, origin ?? REDIRECT_BASE);
20
+ } catch {
21
+ return true;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @param {string} location
27
+ */
28
+ function is_javascript_location(location) {
29
+ try {
30
+ return new URL(location, REDIRECT_BASE).protocol === 'javascript:';
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {string} location
38
+ * @param {{ external?: boolean | string[] }} [options]
39
+ */
40
+ export function validate_redirect_location(location, options) {
41
+ if (!is_external_location(location)) return;
42
+
43
+ const external = options?.external;
44
+
45
+ if (!external) {
46
+ throw new Error(
47
+ DEV
48
+ ? `Cannot redirect to external URL ${JSON.stringify(location)}. ` +
49
+ 'To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect`'
50
+ : 'Cannot redirect to external URL unless explicitly allowed'
51
+ );
52
+ }
53
+
54
+ if (external === true) {
55
+ if (is_javascript_location(location)) {
56
+ throw new Error(
57
+ DEV
58
+ ? `Cannot redirect to ${JSON.stringify(location)} with \`{ external: true }\`. ` +
59
+ 'The `:javascript` protocol must be explicitly listed in the `external` allowlist'
60
+ : 'Cannot redirect to external URL unless explicitly allowed'
61
+ );
62
+ }
63
+
64
+ return;
65
+ }
66
+
67
+ if (Array.isArray(external)) {
68
+ if (!external.some((allowed) => matches_external_allowlist_entry(location, allowed))) {
69
+ throw new Error(
70
+ DEV
71
+ ? `Cannot redirect to ${JSON.stringify(location)}: URL origin is not included in the \`external\` allowlist`
72
+ : 'Cannot redirect to external URL unless explicitly allowed'
73
+ );
74
+ }
75
+
76
+ return;
77
+ }
78
+
79
+ throw new Error(
80
+ DEV
81
+ ? '`redirect` options.external must be `true` or an array of allowed origins'
82
+ : 'Invalid redirect options.external value'
83
+ );
84
+ }
@@ -10,12 +10,14 @@ import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite';
10
10
  import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js';
11
11
  import { coalesce_to_error } from '../../../utils/error.js';
12
12
  import { resolve_entry } from '../../../utils/filesystem.js';
13
+ import { load_and_validate_params } from '../../../utils/params.js';
13
14
  import { from_fs, to_fs } from '../../../utils/vite.js';
14
15
  import { posixify } from '../../../utils/os.js';
15
16
  import { load_error_page } from '../../../core/config/index.js';
16
17
  import { SVELTE_KIT_ASSETS } from '../../../constants.js';
17
18
  import * as sync from '../../../core/sync/sync.js';
18
19
  import { get_mime_lookup, get_runtime_base } from '../../../core/utils.js';
20
+ import '../../../utils/mime.js'; // extend mrmime with additional types (affects sirv too)
19
21
  import { compact } from '../../../utils/array.js';
20
22
  import { is_chrome_devtools_request, not_found } from '../utils.js';
21
23
  import { SCHEME } from '../../../utils/url.js';
@@ -83,6 +85,10 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) {
83
85
  vite.config.logger.error(msg, { error: err });
84
86
  }
85
87
 
88
+ // TODO this is inadequate — it doesn't reliably show the overlay on every page load,
89
+ // and when it does appear it may immediately vanish. `vite.ws.send` broadcasts
90
+ // to all connected clients, even ones that are unaffected by the error.
91
+ // we need a more considered approach
86
92
  vite.ws.send({
87
93
  type: 'error',
88
94
  err: /** @type {import('vite').ErrorPayload['err']} */ ({
@@ -110,10 +116,17 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) {
110
116
  return { module, module_node, url };
111
117
  }
112
118
 
113
- function update_manifest() {
119
+ async function update_manifest() {
114
120
  try {
115
121
  ({ manifest_data } = sync.create(svelte_config, root));
116
122
 
123
+ await load_and_validate_params({
124
+ routes: manifest_data.routes,
125
+ params_path: manifest_data.params,
126
+ root,
127
+ load: (file) => loud_ssr_load_module(file)
128
+ });
129
+
117
130
  if (manifest_error) {
118
131
  manifest_error = null;
119
132
  vite.ws.send({ type: 'full-reload' });
@@ -292,22 +305,18 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) {
292
305
  })
293
306
  ),
294
307
  matchers: async () => {
295
- /** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */
296
- const matchers = {};
297
-
298
- for (const key in manifest_data.matchers) {
299
- const file = manifest_data.matchers[key];
300
- const url = path.resolve(root, file);
301
- const module = await vite.ssrLoadModule(url, { fixStacktrace: true });
302
-
303
- if (module.match) {
304
- matchers[key] = module.match;
305
- } else {
306
- throw new Error(`${file} does not export a \`match\` function`);
307
- }
308
+ if (!manifest_data.params) return {};
309
+
310
+ const url = path.resolve(root, manifest_data.params);
311
+ const module = await vite.ssrLoadModule(url, { fixStacktrace: true });
312
+
313
+ if (!module.params) {
314
+ throw new Error(
315
+ `${manifest_data.params} does not export \`params\` from \`defineParams\``
316
+ );
308
317
  }
309
318
 
310
- return matchers;
319
+ return module.params;
311
320
  }
312
321
  }
313
322
  };
@@ -324,7 +333,9 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) {
324
333
  return error.stack?.replaceAll('\0', ''); // remove null bytes from e.g. virtual module IDs, or the response will fail
325
334
  }
326
335
 
327
- update_manifest();
336
+ await update_manifest();
337
+
338
+ const params_file = resolve_entry(svelte_config.kit.files.params);
328
339
 
329
340
  /**
330
341
  * @param {string} event
@@ -334,7 +345,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) {
334
345
  vite.watcher.on(event, (file) => {
335
346
  if (
336
347
  file.startsWith(svelte_config.kit.files.routes + path.sep) ||
337
- file.startsWith(svelte_config.kit.files.params + path.sep) ||
348
+ (params_file && file === params_file) ||
338
349
  svelte_config.kit.moduleExtensions.some((ext) => file.endsWith(`.remote${ext}`)) ||
339
350
  // in contrast to server hooks, client hooks are written to the client manifest
340
351
  // and therefore need rebuilding when they are added/removed