Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/standardize-public-shared-diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

chore: standardize public API, `$app/*` and deprecated module diagnostics
64 changes: 64 additions & 0 deletions packages/kit/messages/server-errors/runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## server_api_unavailable

> Cannot call `%name%` on the server

This function only works in the browser, because it depends on the client-side router or on browser state. Don't call it while rendering on the server — for example at the top level of a component's `<script>` block, or in a universal `load` function that also runs on the server. Call it from an event handler or an [`$effect`](https://svelte.dev/docs/svelte/$effect) instead, or check [`browser`](https://svelte.dev/docs/kit/$app-env#browser) from `$app/env` first.

## state_read_outside_render

> Can only read `%name%` on the server during rendering (not in e.g. `load` functions), as it is bound to the current request via component context. This prevents state from leaking between users.

On the server, [`$app/state`](https://svelte.dev/docs/kit/$app-state) reads the state of the request that is currently being rendered from component context, so that one user's data can never [leak to another](https://svelte.dev/docs/kit/state-management#Avoid-shared-state-on-the-server). That context only exists while components render. In `load` functions, hooks and endpoints, use the values passed to the function instead — such as `url`, `params` and the result of `parent()` — or [`getRequestEvent`](https://svelte.dev/docs/kit/$app-server#getRequestEvent).

## read_implementation_missing

> No `read` implementation was provided. Please ensure that your adapter is up to date and supports this feature

[`read`](https://svelte.dev/docs/kit/$app-server#read) relies on the adapter to read files at runtime, and the adapter you're using didn't provide a way to do that. Update the adapter to its latest version. If it still doesn't support `read`, [prerender](https://svelte.dev/docs/kit/page-options#prerender) the routes that use it, or import the file's contents directly (for example with Vite's `?raw` suffix).

## read_asset_missing

> Asset does not exist: `%file%`

[`read`](https://svelte.dev/docs/kit/$app-server#read) can only read files that your server code imports, because those are the files SvelteKit copies into the server output. Pass it the URL you get from importing the file, rather than a path you've built yourself:

```js
import { read } from '$app/server';
import file from './data.txt';

const text = await read(file).text();
```

## url_search_unavailable_prerender

> Cannot access `url.%property%` on a page with prerendering enabled

A [prerendered](https://svelte.dev/docs/kit/page-options#prerender) page is generated once at build time and then served as a static file for every request, whatever its query string, so its `load` functions can't depend on `url.search` or `url.searchParams`. Read the query string in the browser instead — for example with [`page.url.searchParams`](https://svelte.dev/docs/kit/$app-state#page) in a component — or disable prerendering for the page.

## define_env_vars_moved

> `defineEnvVars` has moved — import it from `@sveltejs/kit/env` instead

In SvelteKit 3, [`defineEnvVars`](https://svelte.dev/docs/kit/@sveltejs-kit-env#defineEnvVars) lives in `@sveltejs/kit/env` rather than `@sveltejs/kit/hooks`. Update the import in `src/env`:

```js
/// file: src/env.js
import { defineEnvVars } from '@sveltejs/kit/env';

export const variables = defineEnvVars({
API_KEY: {}
});
```

## env_invalid

> Invalid environment variables:
> %issues%

The [environment variables](https://svelte.dev/docs/kit/environment-variables) declared in `src/env` are [validated](https://svelte.dev/docs/kit/environment-variables#Validation) when your app builds and when it starts, and some values didn't pass. Each variable is listed with its issues. Issues reported by your own validators are shown as they are. SvelteKit reports these itself:

- **Value is missing** — a variable without a `schema` is required. Set it in the environment (or a `.env` file), or add a validator that accepts `undefined`, such as Valibot's `v.optional(v.string())` or a function that returns a default.
- **The validator does not implement Standard Schema** — `schema` must be a function or a [Standard Schema](https://standardschema.dev), such as one created with Valibot, Zod or ArkType.
- **The validator is async** — environment variables are validated synchronously, so a function validator can't return a promise, and a schema can't use asynchronous validation.

To require a variable when the app starts but not while building it, use [`building`](https://svelte.dev/docs/kit/$app-env#building) from `$app/env` to choose the validator.
75 changes: 75 additions & 0 deletions packages/kit/messages/shared-errors/public.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## invalid_error_status

> HTTP error status codes must be between 400 and 599 — %status% is invalid

[`error`](https://svelte.dev/docs/kit/@sveltejs-kit#error) creates an [expected error](https://svelte.dev/docs/kit/errors#App-errors), which SvelteKit renders with its HTTP status code, so the status must be a client error (4xx) or server error (5xx) code such as `404` or `500`. Check the order of the arguments — the status comes first, then the message. To send the user somewhere else, use [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) instead.

## invalid_redirect_status

> Redirect status codes must be between 300 and 308 — %status% is invalid

[`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) takes the status code as its first argument and the location as its second, for example `redirect(303, '/login')`. Use `303` after a form submission (the browser follows the redirect with a `GET` request), `307` for a temporary redirect that keeps the request method, or `308` for a permanent one. See [Redirects](https://svelte.dev/docs/kit/load#Redirects) for more.

## redirect_external_not_allowed

> Cannot redirect to external URL `%location%`. To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect`

To protect against open redirects, [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) only accepts locations on the current origin by default. Relative locations such as `'/login'` or `'../other'` are always allowed, but some strings that look relative — such as `'//example.com'` or `'\\example.com'` — are treated as absolute URLs by browsers, and so count as external too.

If you mean to leave your app, opt in explicitly, either with `{ external: true }` (which allows any external URL except `javascript:` and `data:` URLs) or with a list of the origins you trust:

```js
import { redirect } from '@sveltejs/kit';

redirect(307, 'https://example.com/docs', { external: ['https://example.com'] });
```

If the location comes from user input, such as a `?redirectTo=` query parameter, prefer an allowlist over `true`.

## redirect_external_javascript

> Cannot redirect to `%location%` with `{ external: true }`. The `javascript:` and `data:` protocols must be explicitly listed in the `external` allowlist

Redirecting to a `javascript:` or `data:` URL can run arbitrary code in the context of your app, so `{ external: true }` doesn't allow these protocols. Such redirects are rarely intended, so check where the location comes from. If you really need one, list the protocol explicitly, for example `{ external: ['javascript:'] }`.

## redirect_external_not_in_allowlist

> Cannot redirect to `%location%`: URL origin is not included in the `external` allowlist

The redirect location's origin — its protocol, host and port — must exactly match one of the entries passed as `external` to [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect). Paths are ignored, so list origins such as `'https://example.com'`. Note that `http:` and `https:`, or `example.com` and `www.example.com`, are different origins. Add the origin to the allowlist if the redirect is intended.

## redirect_external_option_invalid

> `redirect` `options.external` must be `true` or an array of allowed origins

The third argument of [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) controls whether it may redirect to another origin. Pass `{ external: true }` to allow any external URL (except `javascript:` and `data:` URLs), or `{ external: ['https://example.com'] }` to allow specific origins. Omit the option if the location is on your own origin.

## app_stores_removed

> `$app/stores` has been removed in favour of `$app/state`

The store-based `$app/stores` module was removed in SvelteKit 3. Import `page`, `navigating` and `updated` from [`$app/state`](https://svelte.dev/docs/kit/$app-state) instead. Read `page` and `navigating` properties without the `$` prefix — for example `page.url.pathname` rather than `$page.url.pathname` — and replace `$updated` with `updated.current`. See the [migration guide](https://svelte.dev/docs/kit/migrating-to-sveltekit-3) for details.

## service_worker_module_outside_worker

> The `$app/service-worker` module can only be imported into a service worker

[`$app/service-worker`](https://svelte.dev/docs/kit/$app-service-worker) exports `self` typed as the service worker's global scope, which only exists inside a [service worker](https://svelte.dev/docs/kit/service-workers). Only import it from `src/service-worker` and the modules it imports — not from components, `load` functions or other code that runs in the page or on the server.

## match_in_service_worker

> Cannot use `match(...)` inside a service worker, as it depends on the SvelteKit client instance

[`match`](https://svelte.dev/docs/kit/$app-paths#match) resolves a URL to a route using the router of the running app, which doesn't exist inside a [service worker](https://svelte.dev/docs/kit/service-workers). The other `$app/paths` helpers, such as `asset` and `resolve`, do work there. Do the matching in the page instead, or compare URLs against the routes you care about yourself.

## resolve_params_missing

> Missing params for dynamic route ID `%id%`

When you pass a route ID containing parameters to [`resolve`](https://svelte.dev/docs/kit/$app-paths#resolve), the second argument must provide their values, for example `resolve('/blog/[slug]', { slug: 'hello-world' })`. To resolve a pathname rather than a route ID, omit the leading slash: `resolve('blog/hello-world')`.

## url_hash_unavailable

> Cannot access `event.url.hash`. Consider using `page.url.hash` inside a component instead

The hash (the part of the URL after `#`) is never sent to the server, so it isn't available to `load` functions or anything else that can run on the server. Read [`page.url.hash`](https://svelte.dev/docs/kit/$app-state#page) in a component instead, where it's available in the browser and updates when the hash changes.
42 changes: 42 additions & 0 deletions packages/kit/messages/shared-warnings/deprecations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## error_body_deprecated

> Passing an `App.Error` body as the second argument is deprecated — pass the `message` as the second argument, and any additional properties as the third

[`error`](https://svelte.dev/docs/kit/@sveltejs-kit#error) now takes the message and any additional [`App.Error`](https://svelte.dev/docs/kit/types#Error) properties as separate arguments:

```js
import { error } from '@sveltejs/kit';

// instead of error(404, { message: 'Not found', code: 'NOT_FOUND' })
error(404, 'Not found', { code: 'NOT_FOUND' });
```

## asset_leading_slash

> `asset('%path%')` should now be `asset('%fixed%')`

Paths passed to [`asset`](https://svelte.dev/docs/kit/$app-paths#asset) are relative to your `static` directory and no longer start with `/`. The leading slash is still removed for you for now, but this will stop working in SvelteKit 4, so remove it.

## app_environment_deprecated

> `$app/environment` is deprecated, use `$app/env` instead

The `$app/environment` module has been renamed to [`$app/env`](https://svelte.dev/docs/kit/$app-env), which exports the same values and can also be imported in service workers. Replace `'$app/environment'` with `'$app/env'` in your imports. `$app/environment` will be removed in SvelteKit 4.

## env_module_deprecated

> `%module%` is deprecated, use `%replacement%` instead

The `$env/*` modules are replaced by [explicit environment variables](https://svelte.dev/docs/kit/environment-variables). Declare each variable in `src/env` with [`defineEnvVars`](https://svelte.dev/docs/kit/@sveltejs-kit-env#defineEnvVars), then import it from [`$app/env/private`](https://svelte.dev/docs/kit/$app-env-private) or [`$app/env/public`](https://svelte.dev/docs/kit/$app-env-public). Declared variables can be validated, documented, and marked as [static](https://svelte.dev/docs/kit/environment-variables#Static-variables) to be inlined at build time. The `$env/*` modules will be removed in SvelteKit 4.

## handle_error_status_deprecated

> The `status` property of `handleError` is deprecated. Use `error.status` for expected and framework errors, or `500` for unexpected errors.

The [`handleError`](https://svelte.dev/docs/kit/hooks#handleError) hook now receives a `kind` that says where the error came from. For `'app'` and `'framework'` errors the status is `error.status`, and `'unknown'` errors always have status `500`. Check `kind` instead of reading the top-level `status`.

## handle_error_message_deprecated

> The `message` property of `handleError` is deprecated. Use `error.message` for expected and framework errors, or `'Internal Error'` for unexpected errors.

The [`handleError`](https://svelte.dev/docs/kit/hooks#handleError) hook now receives a `kind` that says where the error came from. For `'app'` and `'framework'` errors the safe message is `error.message`. For `'unknown'` errors the message shown to users defaults to `'Internal Error'`, because the thrown error's own message may contain sensitive information. Check `kind` instead of reading the top-level `message`.
12 changes: 12 additions & 0 deletions packages/kit/scripts/process-messages/templates/server-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @import { ServerThrowOptions } from './internal/server.js' */
import { throw_error } from './internal/server.js';

/**
* DESCRIPTION
* @param {VALUES} _values
* @param {ServerThrowOptions} [options]
* @returns {never}
*/
export function CODE(_values, options) {
throw_error('CODE', MESSAGE(_values), options, CODE);
}
18 changes: 18 additions & 0 deletions packages/kit/scripts/process-messages/templates/shared-warnings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DEV } from 'esm-env';
import { bold, normal } from './internal/shared.js';

/**
* DESCRIPTION
* @param {VALUES} _values
*/
export function CODE(_values) {
if (DEV) {
console.warn(
`%c[sveltekit] ${'CODE'}\n%c${MESSAGE(_values)}\nhttps://next.svelte.dev/e/@sveltejs/kit/${'CODE'}`,
bold,
normal
);
} else {
console.warn('https://next.svelte.dev/e/@sveltejs/kit/CODE');
}
}
4 changes: 3 additions & 1 deletion packages/kit/src/exports/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as e from '../../messages/server-errors.js';

export { sequence } from './sequence.js';

/**
* @internal
*/
export function defineEnvVars() {
throw new Error(`\`defineEnvVars\` has moved — import it from \`@sveltejs/kit/env\` instead`);
e.define_env_vars_moved();
}
10 changes: 5 additions & 5 deletions packages/kit/src/exports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
strip_resolution_suffix
} from '../pathname.js';
import { validate_redirect_location } from './url.js';
import * as e from '../messages/shared-errors.js';
import * as w from '../messages/shared-warnings.js';

const text_encoder = new TextEncoder();

Expand Down Expand Up @@ -80,14 +82,12 @@ export { VERSION } from '../version.js';
*/
export function error(status, message, properties) {
if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) {
throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`);
e.invalid_error_status({ status: String(status) });
}

if (message !== undefined && typeof message !== 'string') {
if (DEV) {
console.warn(
'Passing an `App.Error` body as the second argument is deprecated — pass the `message` as the second argument, and any additional properties as the third'
);
w.error_body_deprecated();
}

({ message, ...properties } = message);
Expand Down Expand Up @@ -128,7 +128,7 @@ export function isHttpError(e, status) {
*/
export function redirect(status, location, options) {
if ((!BROWSER || DEV) && (isNaN(status) || status < 300 || status > 308)) {
throw new Error('Invalid status code');
e.invalid_redirect_status({ status: String(status) });
}

const href = location.toString();
Expand Down
Loading
Loading