Socket
Socket
Sign inDemoInstall

@sveltejs/kit

Package Overview
Dependencies
Maintainers
4
Versions
784
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sveltejs/kit - npm Package Compare versions

Comparing version 1.3.2 to 2.0.6

src/core/sync/write_non_ambient.js

74

package.json
{
"name": "@sveltejs/kit",
"version": "1.3.2",
"version": "2.0.6",
"description": "The fastest way to build Svelte apps",
"repository": {

@@ -13,35 +14,33 @@ "type": "git",

"dependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.0",
"@types/cookie": "^0.5.1",
"cookie": "^0.5.0",
"devalue": "^4.2.2",
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"devalue": "^4.3.2",
"esm-env": "^1.0.0",
"kleur": "^4.1.5",
"magic-string": "^0.27.0",
"mime": "^3.0.0",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.5.1",
"sirv": "^2.0.2",
"tiny-glob": "^0.2.9",
"undici": "5.16.0"
"set-cookie-parser": "^2.6.0",
"sirv": "^2.0.4",
"tiny-glob": "^0.2.9"
},
"devDependencies": {
"@playwright/test": "^1.29.2",
"@types/connect": "^3.4.35",
"@types/marked": "^4.0.7",
"@types/mime": "^3.0.1",
"@types/node": "^16.18.6",
"@types/sade": "^1.7.4",
"@types/set-cookie-parser": "^2.4.2",
"marked": "^4.2.3",
"rollup": "^3.7.0",
"svelte": "^3.55.1",
"svelte-preprocess": "^5.0.0",
"typescript": "^4.9.4",
"uvu": "^0.5.6",
"vite": "^4.0.4"
"@playwright/test": "1.30.0",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@types/connect": "^3.4.38",
"@types/node": "^18.19.3",
"@types/sade": "^1.7.8",
"@types/set-cookie-parser": "^2.4.7",
"dts-buddy": "^0.4.3",
"rollup": "^4.8.0",
"svelte": "^4.2.8",
"svelte-preprocess": "^5.1.2",
"typescript": "^5.3.3",
"vite": "^5.0.8",
"vitest": "^1.0.4"
},
"peerDependencies": {
"svelte": "^3.54.0",
"vite": "^4.0.0"
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
"vite": "^5.0.3"
},

@@ -67,11 +66,15 @@ "bin": {

"./node": {
"types": "./types/index.d.ts",
"import": "./src/exports/node/index.js"
},
"./node/polyfills": {
"types": "./types/index.d.ts",
"import": "./src/exports/node/polyfills.js"
},
"./hooks": {
"types": "./types/index.d.ts",
"import": "./src/exports/hooks/index.js"
},
"./vite": {
"types": "./types/index.d.ts",
"import": "./src/exports/vite/index.js"

@@ -82,15 +85,18 @@ }

"engines": {
"node": "^16.14 || >=18"
"node": ">=18.13"
},
"scripts": {
"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
"check": "tsc",
"lint": "prettier --config ../../.prettierrc --check .",
"check": "tsc && cd ./test/types && tsc",
"check:all": "tsc && pnpm -r --filter=\"./**\" check",
"format": "pnpm lint --write",
"format": "prettier --config ../../.prettierrc --write .",
"test": "pnpm test:unit && pnpm test:integration",
"test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test",
"test:cross-platform": "pnpm test:unit && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform",
"test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\"",
"postinstall": "node postinstall.js"
"test:cross-platform:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:dev",
"test:cross-platform:build": "pnpm test:unit && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:build",
"test:unit": "vitest --config kit.vitest.config.js run",
"postinstall": "node postinstall.js",
"generate:version": "node scripts/generate-version.js",
"generate:types": "node scripts/generate-dts.js"
}
}

@@ -1,6 +0,5 @@

import fs from 'fs';
import path from 'path';
import glob from 'tiny-glob/sync.js';
import { load_config } from './src/core/config/index.js';
import * as sync from './src/core/sync/sync.js';
import glob from 'tiny-glob/sync.js';
import fs from 'node:fs';

@@ -14,16 +13,22 @@ try {

const directories = [];
const workspaces = [];
if (pkg.workspaces) {
// we have to do this because of https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
const packages = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
// Find all npm and Yarn workspace glob patterns
// https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
// https://docs.npmjs.com/cli/v9/configuring-npm/package-json#workspaces
const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
for (const directory of packages) {
directories.push(...glob(directory, { cwd }).map((dir) => path.resolve(cwd, dir)));
for (const pattern of patterns) {
workspaces.push(
...glob(pattern, { cwd, absolute: true }).filter((path) =>
fs.statSync(path).isDirectory()
)
);
}
} else {
directories.push(cwd);
workspaces.push(cwd);
}
for (const cwd of directories) {
for (const cwd of workspaces) {
process.chdir(cwd);

@@ -41,4 +46,4 @@

} catch (error) {
console.log('Error while trying to sync SvelteKit config');
console.log(error.stack);
console.error('Error while trying to sync SvelteKit config');
console.error(error);
}

@@ -48,3 +53,3 @@ }

} catch (error) {
console.error(error.stack);
console.error(error);
}

@@ -5,3 +5,3 @@ # The fastest way to build Svelte apps

The quickest way to get started is via the [create-svelte](https://github.com/sveltejs/kit/tree/master/packages/create-svelte) package:
The quickest way to get started is via the [create-svelte](https://github.com/sveltejs/kit/tree/main/packages/create-svelte) package:

@@ -19,2 +19,2 @@ ```bash

[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md).
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md).

@@ -1,3 +0,3 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import colors from 'kleur';

@@ -4,0 +4,0 @@ import sade from 'sade';

@@ -1,5 +0,11 @@

// in `vite dev` and `vite preview`, we use a fake asset path so that we can
// serve local assets while verifying that requests are correctly prefixed
/**
* A fake asset path used in `vite dev` and `vite preview`, so that we can
* serve local assets while verifying that requests are correctly prefixed
*/
export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';
export const GENERATED_COMMENT = '// this file is generated — do not edit it\n';
export const ENDPOINT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'];
export const PAGE_METHODS = ['GET', 'POST', 'HEAD'];
import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
import { extname, resolve } from 'node:path';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob';
import zlib from 'zlib';
import zlib from 'node:zlib';
import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';

@@ -12,4 +10,8 @@ import { generate_manifest } from '../generate_manifest/index.js';

import { get_env } from '../../exports/vite/utils.js';
import generate_fallback from '../postbuild/fallback.js';
import { write } from '../sync/utils.js';
import { list_files } from '../utils.js';
const pipe = promisify(pipeline);
const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm'];

@@ -22,9 +24,53 @@ /**

* server_metadata: import('types').ServerMetadata;
* routes: import('types').RouteData[];
* route_data: import('types').RouteData[];
* prerendered: import('types').Prerendered;
* prerender_map: import('types').PrerenderMap;
* log: import('types').Logger;
* vite_config: import('vite').ResolvedConfig;
* }} opts
* @returns {import('types').Builder}
* @returns {import('@sveltejs/kit').Builder}
*/
export function create_builder({ config, build_data, server_metadata, routes, prerendered, log }) {
export function create_builder({
config,
build_data,
server_metadata,
route_data,
prerendered,
prerender_map,
log,
vite_config
}) {
/** @type {Map<import('@sveltejs/kit').RouteDefinition, import('types').RouteData>} */
const lookup = new Map();
/**
* Rather than exposing the internal `RouteData` type, which is subject to change,
* we expose a stable type that adapters can use to group/filter routes
*/
const routes = route_data.map((route) => {
const { config, methods, page, api } = /** @type {import('types').ServerMetadataRoute} */ (
server_metadata.routes.get(route.id)
);
/** @type {import('@sveltejs/kit').RouteDefinition} */
const facade = {
id: route.id,
api,
page,
segments: get_route_segments(route.id).map((segment) => ({
dynamic: segment.includes('['),
rest: segment.includes('[...'),
content: segment
})),
pattern: route.pattern,
prerender: prerender_map.get(route.id) ?? false,
methods,
config
};
lookup.set(facade, route);
return facade;
});
return {

@@ -38,2 +84,3 @@ log,

prerendered,
routes,

@@ -45,11 +92,8 @@ async compress(directory) {

const files = await glob('**/*.{html,js,json,css,svg,xml,wasm}', {
cwd: directory,
dot: true,
absolute: true,
filesOnly: true
});
const files = list_files(directory, (file) => extensions.includes(extname(file))).map(
(file) => resolve(directory, file)
);
await Promise.all(
files.map((file) => Promise.all([compress_file(file, 'gz'), compress_file(file, 'br')]))
files.flatMap((file) => [compress_file(file, 'gz'), compress_file(file, 'br')])
);

@@ -59,25 +103,8 @@ },

async createEntries(fn) {
/** @type {import('types').RouteDefinition[]} */
const facades = routes.map((route) => {
const methods =
/** @type {import('types').HttpMethod[]} */
(server_metadata.routes.get(route.id)?.methods);
return {
id: route.id,
segments: get_route_segments(route.id).map((segment) => ({
dynamic: segment.includes('['),
rest: segment.includes('[...'),
content: segment
})),
pattern: route.pattern,
methods
};
});
const seen = new Set();
for (let i = 0; i < routes.length; i += 1) {
const route = routes[i];
const { id, filter, complete } = fn(facades[i]);
for (let i = 0; i < route_data.length; i += 1) {
const route = route_data[i];
if (prerender_map.get(route.id) === true) continue;
const { id, filter, complete } = fn(routes[i]);

@@ -90,5 +117,6 @@ if (seen.has(id)) continue;

// figure out which lower priority routes should be considered fallbacks
for (let j = i + 1; j < routes.length; j += 1) {
if (filter(facades[j])) {
group.push(routes[j]);
for (let j = i + 1; j < route_data.length; j += 1) {
if (prerender_map.get(routes[j].id) === true) continue;
if (filter(routes[j])) {
group.push(route_data[j]);
}

@@ -104,3 +132,3 @@ }

if (route.page) {
const endpoint = routes.find((candidate) => candidate.id === route.id + '.json');
const endpoint = route_data.find((candidate) => candidate.id === route.id + '.json');

@@ -126,34 +154,28 @@ if (endpoint) {

generateFallback(dest) {
// do prerendering in a subprocess so any dangling stuff gets killed upon completion
const script = fileURLToPath(new URL('../postbuild/fallback.js', import.meta.url));
async generateFallback(dest) {
const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;
const env = get_env(config.kit.env, vite_config.mode);
const env = get_env(config.kit.env, 'production');
const fallback = await generate_fallback({
manifest_path,
env: { ...env.private, ...env.public }
});
return new Promise((fulfil, reject) => {
const child = fork(
script,
[dest, manifest_path, JSON.stringify({ ...env.private, ...env.public })],
{
stdio: 'inherit'
}
);
write(dest, fallback);
},
child.on('exit', (code) => {
if (code) {
reject(new Error(`Could not create a fallback page — failed with code ${code}`));
} else {
fulfil(undefined);
}
});
});
generateEnvModule() {
const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}/env.js`;
const env = get_env(config.kit.env, vite_config.mode);
write(dest, `export const env=${JSON.stringify(env.public)}`);
},
generateManifest: ({ relativePath }) => {
generateManifest({ relativePath, routes: subset }) {
return generate_manifest({
build_data,
relative_path: relativePath,
routes
routes: subset
? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
: route_data.filter((route) => prerender_map.get(route.id) !== true)
});

@@ -179,14 +201,9 @@ },

writeClient(dest) {
return [...copy(`${config.kit.outDir}/output/client`, dest)];
return copy(`${config.kit.outDir}/output/client`, dest, {
// avoid making vite build artefacts public
filter: (basename) => basename !== '.vite'
});
},
// @ts-expect-error
writePrerendered(dest, opts) {
// TODO remove for 1.0
if (opts?.fallback) {
throw new Error(
'The fallback option no longer exists — use builder.generateFallback(fallback) instead'
);
}
writePrerendered(dest) {
const source = `${config.kit.outDir}/output/prerendered`;

@@ -215,3 +232,3 @@ return [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];

}
})
})
: zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION });

@@ -218,0 +235,0 @@

@@ -11,4 +11,13 @@ import colors from 'kleur';

* @param {import('types').Logger} log
* @param {import('vite').ResolvedConfig} vite_config
*/
export async function adapt(config, build_data, server_metadata, prerendered, prerender_map, log) {
export async function adapt(
config,
build_data,
server_metadata,
prerendered,
prerender_map,
log,
vite_config
) {
const { name, adapt } = config.kit.adapter;

@@ -22,11 +31,9 @@

server_metadata,
routes: build_data.manifest_data.routes.filter((route) => {
if (!route.page && !route.endpoint) return false;
const prerender = prerender_map.get(route.id);
return prerender === false || prerender === undefined || prerender === 'auto';
}),
route_data: build_data.manifest_data.routes.filter((route) => route.page || route.endpoint),
prerendered,
log
prerender_map,
log,
vite_config
});
await adapt(builder);

@@ -33,0 +40,0 @@

@@ -1,4 +0,4 @@

import fs from 'fs';
import path from 'path';
import * as url from 'url';
import fs from 'node:fs';
import path from 'node:path';
import * as url from 'node:url';
import options from './options.js';

@@ -72,7 +72,15 @@

return process_config(config.default, { cwd });
try {
return process_config(config.default, { cwd });
} catch (e) {
const error = /** @type {Error} */ (e);
// redact the stack trace — it's not helpful to users
error.stack = `Could not load svelte.config.js: ${error.message}\n`;
throw error;
}
}
/**
* @param {import('types').Config} config
* @param {import('@sveltejs/kit').Config} config
* @returns {import('types').ValidatedConfig}

@@ -99,3 +107,3 @@ */

/**
* @param {import('types').Config} config
* @param {import('@sveltejs/kit').Config} config
* @returns {import('types').ValidatedConfig}

@@ -102,0 +110,0 @@ */

@@ -1,4 +0,4 @@

import { join } from 'path';
import { join } from 'node:path';
/** @typedef {import('./types').Validator} Validator */
/** @typedef {import('./types.js').Validator} Validator */

@@ -118,3 +118,4 @@ const directives = object({

dir: string(process.cwd()),
publicPrefix: string('PUBLIC_')
publicPrefix: string('PUBLIC_'),
privatePrefix: string('')
}),

@@ -142,2 +143,6 @@

output: object({
preloadStrategy: list(['modulepreload', 'preload-js', 'preload-mjs'], 'modulepreload')
}),
paths: object({

@@ -173,3 +178,4 @@ base: validate('', (input, keypath) => {

return input;
})
}),
relative: boolean(true)
}),

@@ -196,14 +202,44 @@

handleHttpError: validate('fail', (input, keypath) => {
if (typeof input === 'function') return input;
if (['fail', 'warn', 'ignore'].includes(input)) return input;
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
}),
handleHttpError: validate(
(/** @type {any} */ { message }) => {
throw new Error(
message +
'\nTo suppress or handle this error, implement `handleHttpError` in https://kit.svelte.dev/docs/configuration#prerender'
);
},
(input, keypath) => {
if (typeof input === 'function') return input;
if (['fail', 'warn', 'ignore'].includes(input)) return input;
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
}
),
handleMissingId: validate('fail', (input, keypath) => {
if (typeof input === 'function') return input;
if (['fail', 'warn', 'ignore'].includes(input)) return input;
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
}),
handleMissingId: validate(
(/** @type {any} */ { message }) => {
throw new Error(
message +
'\nTo suppress or handle this error, implement `handleMissingId` in https://kit.svelte.dev/docs/configuration#prerender'
);
},
(input, keypath) => {
if (typeof input === 'function') return input;
if (['fail', 'warn', 'ignore'].includes(input)) return input;
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
}
),
handleEntryGeneratorMismatch: validate(
(/** @type {any} */ { message }) => {
throw new Error(
message +
'\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https://kit.svelte.dev/docs/configuration#prerender'
);
},
(input, keypath) => {
if (typeof input === 'function') return input;
if (['fail', 'warn', 'ignore'].includes(input)) return input;
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
}
),
origin: validate('http://sveltekit-prerender', (input, keypath) => {

@@ -210,0 +246,0 @@ assert_string(input, keypath);

import { GENERATED_COMMENT } from '../constants.js';
import { dedent } from './sync/utils.js';
import { runtime_base } from './utils.js';

@@ -6,7 +7,2 @@

* @typedef {'public' | 'private'} EnvType
* @typedef {{
* public: Record<string, string>;
* private: Record<string, string>;
* prefix: string;
* }} EnvData
*/

@@ -48,3 +44,3 @@

}
return `export { ${type}_env as env } from '${runtime_base}/shared.js';`;
return `export { ${type}_env as env } from '${runtime_base}/shared-server.js';`;
}

@@ -54,3 +50,3 @@

* @param {EnvType} id
* @param {EnvData} env
* @param {import('types').Env} env
* @returns {string}

@@ -61,6 +57,9 @@ */

.filter((k) => valid_identifier.test(k))
.map((k) => `\texport const ${k}: string;`)
.join('\n');
.map((k) => `export const ${k}: string;`);
return `declare module '$env/static/${id}' {\n${declarations}\n}`;
return dedent`
declare module '$env/static/${id}' {
${declarations.join('\n')}
}
`;
}

@@ -70,21 +69,36 @@

* @param {EnvType} id
* @param {EnvData} env
* @param {import('types').Env} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @returns {string}
*/
export function create_dynamic_types(id, env) {
export function create_dynamic_types(id, env, { public_prefix, private_prefix }) {
const properties = Object.keys(env[id])
.filter((k) => valid_identifier.test(k))
.map((k) => `\t\t${k}: string;`);
.map((k) => `${k}: string;`);
const prefixed = `[key: \`${env.prefix}\${string}\`]`;
const public_prefixed = `[key: \`${public_prefix}\${string}\`]`;
const private_prefixed = `[key: \`${private_prefix}\${string}\`]`;
if (id === 'private') {
properties.push(`\t\t${prefixed}: undefined;`);
properties.push(`\t\t[key: string]: string | undefined;`);
if (public_prefix) {
properties.push(`${public_prefixed}: undefined;`);
}
properties.push(`${private_prefixed}: string | undefined;`);
} else {
properties.push(`\t\t${prefixed}: string | undefined;`);
if (private_prefix) {
properties.push(`${private_prefixed}: undefined;`);
}
properties.push(`${public_prefixed}: string | undefined;`);
}
const declaration = `export const env: {\n${properties.join('\n')}\n\t}`;
return `declare module '$env/dynamic/${id}' {\n\t${declaration}\n}`;
return dedent`
declare module '$env/dynamic/${id}' {
export const env: {
${properties.join('\n')}
}
}
`;
}

@@ -91,0 +105,0 @@

@@ -6,2 +6,3 @@ import { s } from '../../utils/misc.js';

import { join_relative } from '../../utils/filesystem.js';
import { dedent } from '../sync/utils.js';

@@ -46,15 +47,4 @@ /**

/** @typedef {{ index: number, path: string }} LookupEntry */
/** @type {Map<import('types').PageNode, LookupEntry>} */
const bundled_nodes = new Map();
build_data.manifest_data.nodes.forEach((node, i) => {
bundled_nodes.set(node, {
path: join_relative(relative_path, `/nodes/${i}.js`),
index: i
});
});
/** @type {(path: string) => string} */
const loader = (path) => `() => import('${path}')`;
const loader = (path) => `__memo(() => import('${path}'))`;

@@ -70,12 +60,8 @@ const assets = build_data.manifest_data.assets.map((asset) => asset.file);

function get_nodes(indexes) {
let string = indexes.map((n) => reindexed.get(n) ?? '').join(',');
const string = indexes.map((n) => reindexed.get(n) ?? '').join(',');
if (indexes.at(-1) === undefined) {
// since JavaScript ignores trailing commas, we need to insert a dummy
// comma so that the array has the correct length if the last item
// is undefined
string += ',';
}
return `[${string}]`;
// since JavaScript ignores trailing commas, we need to insert a dummy
// comma so that the array has the correct length if the last item
// is undefined
return `[${string},]`;
}

@@ -85,36 +71,56 @@

// String representation of
/** @type {import('types').SSRManifest} */
return `{
appDir: ${s(build_data.app_dir)},
appPath: ${s(build_data.app_path)},
assets: new Set(${s(assets)}),
mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))},
_: {
entry: ${s(build_data.client_entry)},
nodes: [
${(node_paths).map(loader).join(',\n\t\t\t\t')}
],
routes: [
${routes.map(route => {
route.params.forEach(param => {
if (param.matcher) matchers.add(param.matcher);
});
/** @template {import('@sveltejs/kit').SSRManifest} T */
const manifest_expr = dedent`
{
appDir: ${s(build_data.app_dir)},
appPath: ${s(build_data.app_path)},
assets: new Set(${s(assets)}),
mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))},
_: {
client: ${s(build_data.client)},
nodes: [
${(node_paths).map(loader).join(',\n')}
],
routes: [
${routes.map(route => {
if (!route.page && !route.endpoint) return;
route.params.forEach(param => {
if (param.matcher) matchers.add(param.matcher);
});
if (!route.page && !route.endpoint) return;
return `{
id: ${s(route.id)},
pattern: ${route.pattern},
params: ${s(route.params)},
page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'},
endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file).chunk.file)) : 'null'}
}`;
}).filter(Boolean).join(',\n\t\t\t\t')}
],
matchers: async () => {
${Array.from(matchers).map(type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`).join('\n\t\t\t\t')}
return { ${Array.from(matchers).join(', ')} };
return dedent`
{
id: ${s(route.id)},
pattern: ${route.pattern},
params: ${s(route.params)},
page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'},
endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file).chunk.file)) : 'null'}
}
`;
}).filter(Boolean).join(',\n')}
],
matchers: async () => {
${Array.from(
matchers,
type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`
).join('\n')}
return { ${Array.from(matchers).join(', ')} };
}
}
}
}`.replace(/^\t/gm, '');
`;
// Memoize the loaders to prevent Node from doing unnecessary work
// on every dynamic import call
return dedent`
(() => {
function __memo(fn) {
let value;
return () => value ??= (value = fn());
}
return ${manifest_expr}
})()
`;
}

@@ -1,6 +0,8 @@

import { join } from 'path';
import { pathToFileURL } from 'url';
import { get_option } from '../../runtime/server/utils.js';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { get_option } from '../../utils/options.js';
import {
validate_common_exports,
validate_layout_exports,
validate_layout_server_exports,
validate_page_exports,
validate_page_server_exports,

@@ -11,4 +13,6 @@ validate_server_exports

import { forked } from '../../utils/fork.js';
import { should_polyfill } from '../../utils/platform.js';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { ENDPOINT_METHODS } from '../../constants.js';
import { filter_private_env, filter_public_env } from '../../utils/env.js';
import { resolve_route } from '../../utils/routing.js';

@@ -24,3 +28,3 @@ export default forked(import.meta.url, analyse);

async function analyse({ manifest_path, env }) {
/** @type {import('types').SSRManifest} */
/** @type {import('@sveltejs/kit').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

@@ -36,15 +40,15 @@

if (should_polyfill) {
installPolyfills();
}
installPolyfills();
// configure `import { building } from '$app/environment'` —
// essential we do this before analysing the code
internal.set_building(true);
internal.set_building();
// set env, in case it's used in initialisation
const entries = Object.entries(env);
const prefix = config.env.publicPrefix;
internal.set_private_env(Object.fromEntries(entries.filter(([k]) => !k.startsWith(prefix))));
internal.set_public_env(Object.fromEntries(entries.filter(([k]) => k.startsWith(prefix))));
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
const private_env = filter_private_env(env, { public_prefix, private_prefix });
const public_env = filter_public_env(env, { public_prefix, private_prefix });
internal.set_private_env(private_env);
internal.set_public_env(public_env);
internal.set_safe_public_env(public_env);

@@ -57,9 +61,9 @@ /** @type {import('types').ServerMetadata} */

const nodes = await Promise.all(manifest._.nodes.map((loader) => loader()));
// analyse nodes
for (const loader of manifest._.nodes) {
const node = await loader();
metadata.nodes.push({
has_server_load: node.server?.load !== undefined
});
for (const node of nodes) {
metadata.nodes[node.index] = {
has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined
};
}

@@ -69,72 +73,125 @@

for (const route of manifest._.routes) {
/** @type {Set<import('types').HttpMethod>} */
const methods = new Set();
const page =
route.page &&
analyse_page(
route.page.layouts.map((n) => (n === undefined ? n : nodes[n])),
nodes[route.page.leaf]
);
/** @type {import('types').PrerenderOption | undefined} */
let prerender = undefined;
const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint());
if (route.endpoint) {
const mod = await route.endpoint();
if (mod.prerender !== undefined) {
validate_server_exports(mod, route.id);
if (page?.prerender && endpoint?.prerender) {
throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`);
}
if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
if (page?.config && endpoint?.config) {
for (const key in { ...page.config, ...endpoint.config }) {
if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) {
throw new Error(
`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
`Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any`
);
}
prerender = mod.prerender;
}
if (mod.GET) methods.add('GET');
if (mod.POST) methods.add('POST');
if (mod.PUT) methods.add('PUT');
if (mod.PATCH) methods.add('PATCH');
if (mod.DELETE) methods.add('DELETE');
}
if (route.page) {
const nodes = await Promise.all(
[...route.page.layouts, route.page.leaf].map((n) => {
if (n !== undefined) return manifest._.nodes[n]();
})
);
const page_methods = page?.methods ?? [];
const api_methods = endpoint?.methods ?? [];
const entries = page?.entries ?? endpoint?.entries;
const layouts = nodes.slice(0, -1);
const page = nodes.at(-1);
metadata.routes.set(route.id, {
config: page?.config ?? endpoint?.config,
methods: Array.from(new Set([...page_methods, ...api_methods])),
page: {
methods: page_methods
},
api: {
methods: api_methods
},
prerender: page?.prerender ?? endpoint?.prerender,
entries:
entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object))
});
}
for (const layout of layouts) {
if (layout) {
validate_common_exports(layout.server, route.id);
validate_common_exports(layout.universal, route.id);
}
}
return metadata;
}
if (page) {
methods.add('GET');
if (page.server?.actions) methods.add('POST');
/**
* @param {import('types').SSRRoute} route
* @param {import('types').SSREndpoint} mod
*/
function analyse_endpoint(route, mod) {
validate_server_exports(mod, route.id);
validate_page_server_exports(page.server, route.id);
validate_common_exports(page.universal, route.id);
}
if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
throw new Error(
`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
);
}
const should_prerender = get_option(nodes, 'prerender');
prerender =
should_prerender === true ||
// Try prerendering if ssr is false and no server needed. Set it to 'auto' so that
// the route is not removed from the manifest, there could be a server load function.
// People can opt out of this behavior by explicitly setting prerender to false
(should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions
? 'auto'
: should_prerender ?? false);
/** @type {Array<import('types').HttpMethod | '*'>} */
const methods = [];
for (const method of /** @type {import('types').HttpMethod[]} */ (ENDPOINT_METHODS)) {
if (mod[method]) methods.push(method);
}
if (mod.fallback) {
methods.push('*');
}
return {
config: mod.config,
entries: mod.entries,
methods,
prerender: mod.prerender ?? false
};
}
/**
* @param {Array<import('types').SSRNode | undefined>} layouts
* @param {import('types').SSRNode} leaf
*/
function analyse_page(layouts, leaf) {
for (const layout of layouts) {
if (layout) {
validate_layout_server_exports(layout.server, layout.server_id);
validate_layout_exports(layout.universal, layout.universal_id);
}
}
metadata.routes.set(route.id, {
prerender,
methods: Array.from(methods)
});
/** @type {Array<'GET' | 'POST'>} */
const methods = ['GET'];
if (leaf.server?.actions) methods.push('POST');
validate_page_server_exports(leaf.server, leaf.server_id);
validate_page_exports(leaf.universal, leaf.universal_id);
return {
config: get_config([...layouts, leaf]),
entries: leaf.universal?.entries ?? leaf.server?.entries,
methods,
prerender: get_option([...layouts, leaf], 'prerender') ?? false
};
}
/**
* Do a shallow merge (first level) of the config object
* @param {Array<import('types').SSRNode | undefined>} nodes
*/
function get_config(nodes) {
/** @type {any} */
let current = {};
for (const node of nodes) {
if (!node?.universal?.config && !node?.server?.config) continue;
current = {
...current,
...node?.universal?.config,
...node?.server?.config
};
}
return metadata;
return Object.keys(current).length ? current : undefined;
}

@@ -0,1 +1,2 @@

import { resolve } from '../../utils/url.js';
import { decode } from './entities.js';

@@ -15,4 +16,21 @@

/** @param {string} html */
export function crawl(html) {
const CRAWLABLE_META_NAME_ATTRS = new Set([
'og:url',
'og:image',
'og:image:url',
'og:image:secure_url',
'og:video',
'og:video:url',
'og:video:secure_url',
'og:audio',
'og:audio:url',
'og:audio:secure_url',
'twitter:image'
]);
/**
* @param {string} html
* @param {string} base
*/
export function crawl(html, base) {
/** @type {string[]} */

@@ -81,2 +99,5 @@ const ids = [];

/** @type {Record<string, string>} */
const attributes = {};
if (tag === 'SCRIPT' || tag === 'STYLE') {

@@ -96,5 +117,2 @@ while (i < html.length) {

let href = '';
let rel = '';
while (i < html.length) {

@@ -161,33 +179,3 @@ const start = i;

value = decode(value);
if (name === 'href') {
href = value;
} else if (name === 'id') {
ids.push(value);
} else if (name === 'name') {
if (tag === 'A') ids.push(value);
} else if (name === 'rel') {
rel = value;
} else if (name === 'src') {
hrefs.push(value);
} else if (name === 'srcset') {
const candidates = [];
let insideURL = true;
value = value.trim();
for (let i = 0; i < value.length; i++) {
if (value[i] === ',' && (!insideURL || (insideURL && value[i + 1] === ' '))) {
candidates.push(value.slice(0, i));
value = value.substring(i + 1).trim();
i = 0;
insideURL = true;
} else if (value[i] === ' ') {
insideURL = false;
}
}
candidates.push(value);
for (const candidate of candidates) {
const src = candidate.split(WHITESPACE)[0];
hrefs.push(src);
}
}
attributes[name] = value;
} else {

@@ -201,5 +189,53 @@ i -= 1;

if (href && !/\bexternal\b/i.test(rel)) {
hrefs.push(href);
const { href, id, name, property, rel, src, srcset, content } = attributes;
if (href) {
if (tag === 'BASE') {
base = resolve(base, href);
} else if (!rel || !/\bexternal\b/i.test(rel)) {
hrefs.push(resolve(base, href));
}
}
if (id) {
ids.push(id);
}
if (name && tag === 'A') {
ids.push(name);
}
if (src) {
hrefs.push(resolve(base, src));
}
if (srcset) {
let value = srcset;
const candidates = [];
let insideURL = true;
value = value.trim();
for (let i = 0; i < value.length; i++) {
if (value[i] === ',' && (!insideURL || (insideURL && WHITESPACE.test(value[i + 1])))) {
candidates.push(value.slice(0, i));
value = value.substring(i + 1).trim();
i = 0;
insideURL = true;
} else if (WHITESPACE.test(value[i])) {
insideURL = false;
}
}
candidates.push(value);
for (const candidate of candidates) {
const src = candidate.split(WHITESPACE)[0];
if (src) hrefs.push(resolve(base, src));
}
}
if (tag === 'META' && content) {
const attr = name ?? property;
if (attr && CRAWLABLE_META_NAME_ATTRS.has(attr)) {
hrefs.push(resolve(base, content));
}
}
}

@@ -206,0 +242,0 @@ }

@@ -1,46 +0,54 @@

import { readFileSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';
import { pathToFileURL } from 'url';
import { mkdirp } from '../../utils/filesystem.js';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { load_config } from '../config/index.js';
import { forked } from '../../utils/fork.js';
const [, , dest, manifest_path, env] = process.argv;
export default forked(import.meta.url, generate_fallback);
/** @type {import('types').ValidatedKitConfig} */
const config = (await load_config()).kit;
/**
* @param {{
* manifest_path: string;
* env: Record<string, string>
* }} opts
*/
async function generate_fallback({ manifest_path, env }) {
/** @type {import('types').ValidatedKitConfig} */
const config = (await load_config()).kit;
installPolyfills();
installPolyfills();
const server_root = join(config.outDir, 'output');
const server_root = join(config.outDir, 'output');
/** @type {import('types').ServerInternalModule} */
const { set_building, set_paths } = await import(
pathToFileURL(`${server_root}/server/internal.js`).href
);
/** @type {import('types').ServerInternalModule} */
const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
/** @type {import('types').ServerModule} */
const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
/** @type {import('types').ServerModule} */
const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
/** @type {import('types').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
/** @type {import('@sveltejs/kit').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
set_building(true);
set_paths(config.paths);
set_building();
const server = new Server(manifest);
await server.init({ env: JSON.parse(env) });
const server = new Server(manifest);
await server.init({ env });
const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
getClientAddress: () => {
throw new Error('Cannot read clientAddress during prerendering');
},
prerendering: {
fallback: true,
dependencies: new Map()
},
read: (file) => readFileSync(join(config.files.assets, file))
});
const response = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
getClientAddress: () => {
throw new Error('Cannot read clientAddress during prerendering');
},
prerendering: {
fallback: true,
dependencies: new Map()
},
read: (file) => readFileSync(join(config.files.assets, file))
});
mkdirp(dirname(dest));
writeFileSync(dest, await rendered.text());
if (response.ok) {
return await response.text();
}
throw new Error(`Could not create a fallback page — failed with status ${response.status}`);
}

@@ -1,2 +0,2 @@

import { readFileSync, writeFileSync } from 'node:fs';
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';

@@ -6,4 +6,3 @@ import { pathToFileURL } from 'node:url';

import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
import { should_polyfill } from '../../utils/platform.js';
import { is_root_relative, resolve } from '../../utils/url.js';
import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
import { escape_html_attr } from '../../utils/escape.js';

@@ -16,2 +15,3 @@ import { logger } from '../utils.js';

import { forked } from '../../utils/fork.js';
import * as devalue from 'devalue';

@@ -30,3 +30,3 @@ export default forked(import.meta.url, prerender);

async function prerender({ out, manifest_path, metadata, verbose, env }) {
/** @type {import('types').SSRManifest} */
/** @type {import('@sveltejs/kit').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

@@ -42,3 +42,4 @@

// essential we do this before analysing the code
internal.set_building(true);
internal.set_building();
internal.set_prerendering();

@@ -100,5 +101,3 @@ /**

if (should_polyfill) {
installPolyfills();
}
installPolyfills();

@@ -108,7 +107,2 @@ /** @type {Map<string, string>} */

internal.set_paths(config.paths);
const server = new Server(manifest);
await server.init({ env });
const handle_http_error = normalise_error_handler(

@@ -138,2 +132,10 @@ log,

const handle_entry_generator_mismatch = normalise_error_handler(
log,
config.prerender.handleEntryGeneratorMismatch,
({ generatedFromId, entry, matchedId }) => {
return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://kit.svelte.dev/docs/configuration#prerender for more info.`;
}
);
const q = queue(config.prerender.concurrency);

@@ -156,2 +158,10 @@

const files = new Set(walk(`${out}/client`).map(posixify));
files.add(`${config.appDir}/env.js`);
const immutable = `${config.appDir}/immutable`;
if (existsSync(`${out}/server/${immutable}`)) {
for (const file of walk(`${out}/server/${immutable}`)) {
files.add(posixify(`${config.appDir}/immutable/${file}`));
}
}
const seen = new Set();

@@ -170,4 +180,5 @@ const written = new Set();

* @param {string} [encoded]
* @param {string} [generated_from_id]
*/
function enqueue(referrer, decoded, encoded) {
function enqueue(referrer, decoded, encoded, generated_from_id) {
if (seen.has(decoded)) return;

@@ -179,3 +190,3 @@ seen.add(decoded);

return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer));
return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer, generated_from_id));
}

@@ -187,4 +198,5 @@

* @param {string?} referrer
* @param {string} [generated_from_id]
*/
async function visit(decoded, encoded, referrer) {
async function visit(decoded, encoded, referrer, generated_from_id) {
if (!decoded.startsWith(config.paths.base)) {

@@ -215,2 +227,16 @@ handle_http_error({ status: 404, path: decoded, referrer, referenceType: 'linked' });

const encoded_id = response.headers.get('x-sveltekit-routeid');
const decoded_id = encoded_id && decode_uri(encoded_id);
if (
decoded_id !== null &&
generated_from_id !== undefined &&
decoded_id !== generated_from_id
) {
handle_entry_generator_mismatch({
generatedFromId: generated_from_id,
entry: decoded,
matchedId: decoded_id
});
}
const body = Buffer.from(await response.arrayBuffer());

@@ -224,3 +250,3 @@

const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
const decoded_dependency_path = decodeURI(encoded_dependency_path);
const decoded_dependency_path = decode_uri(encoded_dependency_path);

@@ -233,3 +259,3 @@ const headers = Object.fromEntries(result.response.headers);

if (encoded_route_id != null) {
const route_id = decodeURI(encoded_route_id);
const route_id = decode_uri(encoded_route_id);
const existing_value = prerender_map.get(route_id);

@@ -259,3 +285,3 @@ if (existing_value !== 'auto') {

if (config.prerender.crawl && headers['content-type'] === 'text/html') {
const { ids, hrefs } = crawl(body.toString());
const { ids, hrefs } = crawl(body.toString(), decoded);

@@ -265,9 +291,6 @@ actual_hashlinks.set(decoded, ids);

for (const href of hrefs) {
if (href.startsWith('data:')) continue;
if (!is_root_relative(href)) continue;
const resolved = resolve(encoded, href);
if (!is_root_relative(resolved)) continue;
const { pathname, search, hash } = new URL(href, 'http://localhost');
const { pathname, search, hash } = new URL(resolved, 'http://localhost');
if (search) {

@@ -278,3 +301,3 @@ // TODO warn that query strings have no effect on statically-exported pages

if (hash) {
const key = decodeURI(pathname + hash);
const key = decode_uri(pathname + hash);

@@ -288,3 +311,3 @@ if (!expected_hashlinks.has(key)) {

enqueue(decoded, decodeURI(pathname), pathname);
enqueue(decoded, decode_uri(pathname), pathname);
}

@@ -316,3 +339,3 @@ }

const encoded_route_id = response.headers.get('x-sveltekit-routeid');
const route_id = encoded_route_id != null ? decodeURI(encoded_route_id) : null;
const route_id = encoded_route_id != null ? decode_uri(encoded_route_id) : null;
if (route_id !== null) prerendered_routes.add(route_id);

@@ -326,3 +349,3 @@

if (is_root_relative(resolved)) {
enqueue(decoded, decodeURI(resolved), resolved);
enqueue(decoded, decode_uri(resolved), resolved);
}

@@ -337,3 +360,7 @@

dest,
`<meta http-equiv="refresh" content=${escape_html_attr(`0;url=${location}`)}>`
`<script>location.href=${devalue.uneval(
location
)};</script><meta http-equiv="refresh" content=${escape_html_attr(
`0;url=${location}`
)}>`
);

@@ -360,4 +387,19 @@

if (response.status === 200) {
mkdirp(dirname(dest));
if (existsSync(dest) && statSync(dest).isDirectory()) {
throw new Error(
`Cannot save ${decoded} as it is already a directory. See https://kit.svelte.dev/docs/page-options#prerender-route-conflicts for more information`
);
}
const dir = dirname(dest);
if (existsSync(dir) && !statSync(dir).isDirectory()) {
const parent = decoded.split('/').slice(0, -1).join('/');
throw new Error(
`Cannot save ${decoded} as ${parent} is already a file. See https://kit.svelte.dev/docs/page-options#prerender-route-conflicts for more information`
);
}
mkdirp(dir);
log.info(`${response.status} ${decoded}`);

@@ -386,2 +428,31 @@ writeFileSync(dest, body);

/** @type {Array<{ id: string, entries: Array<string>}>} */
const route_level_entries = [];
for (const [id, { entries }] of metadata.routes.entries()) {
if (entries) {
route_level_entries.push({ id, entries });
}
}
let has_prerenderable_routes = false;
for (const value of prerender_map.values()) {
if (value) {
has_prerenderable_routes = true;
break;
}
}
if (
(config.prerender.entries.length === 0 && route_level_entries.length === 0) ||
!has_prerenderable_routes
) {
return { prerendered, prerender_map };
}
log.info('Prerendering');
const server = new Server(manifest);
await server.init({ env });
for (const entry of config.prerender.entries) {

@@ -391,4 +462,8 @@ if (entry === '*') {

if (prerender) {
if (id.includes('[')) continue;
const path = `/${get_route_segments(id).join('/')}`;
// remove optional parameters from the route
const segments = get_route_segments(id).filter((segment) => !segment.startsWith('[['));
const processed_id = '/' + segments.join('/');
if (processed_id.includes('[')) continue;
const path = `/${get_route_segments(processed_id).join('/')}`;
enqueue(null, config.paths.base + path);

@@ -402,2 +477,8 @@ }

for (const { id, entries } of route_level_entries) {
for (const entry of entries) {
enqueue(null, config.paths.base + entry, undefined, id);
}
}
await q.done();

@@ -430,6 +511,6 @@

if (not_prerendered.length > 0) {
const list = not_prerendered.map((id) => ` - ${id}`).join('\n');
throw new Error(
`The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${not_prerendered.map(
(id) => ` - ${id}`
)}\n\nSee https://kit.svelte.dev/docs/page-options#prerender-troubleshooting for info on how to solve this`
`The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}\n\nSee https://kit.svelte.dev/docs/page-options#prerender-troubleshooting for info on how to solve this`
);

@@ -436,0 +517,0 @@ }

@@ -1,5 +0,6 @@

import fs from 'fs';
import path from 'path';
import mime from 'mime';
import { runtime_directory } from '../../utils.js';
import fs from 'node:fs';
import path from 'node:path';
import colors from 'kleur';
import { lookup } from 'mrmime';
import { list_files, runtime_directory } from '../../utils.js';
import { posixify } from '../../../utils/filesystem.js';

@@ -50,3 +51,3 @@ import { parse_route_id } from '../../../utils/routing.js';

size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
type: mime.getType(file)
type: lookup(file) || null
}));

@@ -205,5 +206,28 @@ }

if (file.is_dir) continue;
if (!file.name.startsWith('+')) continue;
if (!valid_extensions.find((ext) => file.name.endsWith(ext))) continue;
const ext = valid_extensions.find((ext) => file.name.endsWith(ext));
if (!ext) continue;
if (!file.name.startsWith('+')) {
const name = file.name.slice(0, -ext.length);
// check if it is a valid route filename but missing the + prefix
const typo =
/^(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/.test(name) ||
/^(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/.test(
name
);
if (typo) {
console.log(
colors
.bold()
.yellow(
`Missing route file prefix. Did you mean +${file.name}?` +
` at ${path.join(dir, file.name)}`
)
);
}
continue;
}
if (file.name.endsWith('.d.ts')) {

@@ -232,2 +256,14 @@ let name = file.name.slice(0, -5);

/**
* @param {string} type
* @param {string} existing_file
*/
function duplicate_files_error(type, existing_file) {
return new Error(
`Multiple ${type} files found in ${routes_base}${route.id} : ${path.basename(
existing_file
)} and ${file.name}`
);
}
if (item.kind === 'component') {

@@ -240,7 +276,17 @@ if (item.is_error) {

} else if (item.is_layout) {
if (!route.layout) route.layout = { depth, child_pages: [] };
if (!route.layout) {
route.layout = { depth, child_pages: [] };
} else if (route.layout.component) {
throw duplicate_files_error('layout component', route.layout.component);
}
route.layout.component = project_relative;
if (item.uses_layout !== undefined) route.layout.parent_id = item.uses_layout;
} else {
if (!route.leaf) route.leaf = { depth };
if (!route.leaf) {
route.leaf = { depth };
} else if (route.leaf.component) {
throw duplicate_files_error('page component', route.leaf.component);
}
route.leaf.component = project_relative;

@@ -250,8 +296,28 @@ if (item.uses_layout !== undefined) route.leaf.parent_id = item.uses_layout;

} else if (item.is_layout) {
if (!route.layout) route.layout = { depth, child_pages: [] };
if (!route.layout) {
route.layout = { depth, child_pages: [] };
} else if (route.layout[item.kind]) {
throw duplicate_files_error(
item.kind + ' layout module',
/** @type {string} */ (route.layout[item.kind])
);
}
route.layout[item.kind] = project_relative;
} else if (item.is_page) {
if (!route.leaf) route.leaf = { depth };
if (!route.leaf) {
route.leaf = { depth };
} else if (route.leaf[item.kind]) {
throw duplicate_files_error(
item.kind + ' page module',
/** @type {string} */ (route.leaf[item.kind])
);
}
route.leaf[item.kind] = project_relative;
} else {
if (route.endpoint) {
throw duplicate_files_error('endpoint', route.endpoint.file);
}
route.endpoint = {

@@ -386,3 +452,3 @@ file: project_relative

* @param {string[]} module_extensions
* @returns {import('./types').RouteFile}
* @returns {import('./types.js').RouteFile}
*/

@@ -423,3 +489,3 @@ function analyze(project_relative, file, component_extensions, module_extensions) {

const kind = !!(match[1] || match[4] || match[7]) ? 'server' : 'universal';
const kind = match[1] || match[4] || match[7] ? 'server' : 'universal';

@@ -436,24 +502,2 @@ return {

/** @param {string} dir */
function list_files(dir) {
/** @type {string[]} */
const files = [];
/** @param {string} current */
function walk(current) {
for (const file of fs.readdirSync(path.resolve(dir, current))) {
const child = path.posix.join(current, file);
if (fs.statSync(path.resolve(dir, child)).isDirectory()) {
walk(child);
} else {
files.push(child);
}
}
}
if (fs.existsSync(dir)) walk('');
return files;
}
/**

@@ -482,3 +526,3 @@ * @param {string} needle

// find all permutations created by optional parameters
const split = normalized.split(/<\?(.+?)\>/g);
const split = normalized.split(/<\?(.+?)>/g);

@@ -485,0 +529,0 @@ let permutations = [/** @type {string} */ (split[0])];

@@ -1,2 +0,2 @@

import path from 'path';
import path from 'node:path';
import create_manifest_data from './create_manifest_data/index.js';

@@ -8,2 +8,3 @@ import { write_client_manifest } from './write_client_manifest.js';

import { write_ambient } from './write_ambient.js';
import { write_non_ambient } from './write_non_ambient.js';
import { write_server } from './write_server.js';

@@ -19,2 +20,3 @@

write_ambient(config.kit, mode);
write_non_ambient(config.kit);
}

@@ -21,0 +23,0 @@

@@ -1,3 +0,4 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { VERSION } from 'svelte/compiler';
import { mkdirp } from '../../utils/filesystem.js';

@@ -28,7 +29,48 @@

/** @param {string} str */
export function trim(str) {
const indentation = /** @type {RegExpExecArray} */ (/\n?([ \t]*)/.exec(str))[1];
const pattern = new RegExp(`^${indentation}`, 'gm');
return str.replace(pattern, '').trim();
/** @type {WeakMap<TemplateStringsArray, { strings: string[], indents: string[] }>} */
const dedent_map = new WeakMap();
/**
* Allows indenting template strings without the extra indentation ending up in the result.
* Still allows indentation of lines relative to one another in the template string.
* @param {TemplateStringsArray} strings
* @param {any[]} values
*/
export function dedent(strings, ...values) {
let dedented = dedent_map.get(strings);
if (!dedented) {
const indentation = /** @type {RegExpExecArray} */ (/\n?([ \t]*)/.exec(strings[0]))[1];
const pattern = new RegExp(`^${indentation}`, 'gm');
dedented = {
strings: strings.map((str) => str.replace(pattern, '')),
indents: []
};
let current = '\n';
for (let i = 0; i < values.length; i += 1) {
const string = dedented.strings[i];
const match = /\n([ \t]*)$/.exec(string);
if (match) current = match[0];
dedented.indents[i] = current;
}
dedent_map.set(strings, dedented);
}
let str = dedented.strings[0];
for (let i = 0; i < values.length; i += 1) {
str += String(values[i]).replace(/\n/g, dedented.indents[i]) + dedented.strings[i + 1];
}
str = str.trim();
return str;
}
export function isSvelte5Plus() {
return Number(VERSION[0]) >= 5;
}

@@ -1,3 +0,4 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { get_env } from '../../exports/vite/utils.js';

@@ -7,7 +8,6 @@ import { GENERATED_COMMENT } from '../../constants.js';

import { write_if_changed } from './utils.js';
import { fileURLToPath } from 'url';
// TODO these types should be described in a neutral place, rather than
// inside either `packages/kit` or `kit.svelte.dev`
const descriptions_dir = fileURLToPath(new URL('../../../types/synthetic', import.meta.url));
const descriptions_dir = fileURLToPath(new URL('../../../src/types/synthetic', import.meta.url));

@@ -25,5 +25,9 @@ /** @param {string} filename */

/**
* @param {import('../env.js').EnvData} env
* @param {import('types').Env} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
*/
const template = (env) => `
const template = (env, prefixes) => `
${GENERATED_COMMENT}

@@ -40,6 +44,6 @@

${read_description('$env+dynamic+private.md')}
${create_dynamic_types('private', env)}
${create_dynamic_types('private', env, prefixes)}
${read_description('$env+dynamic+public.md')}
${create_dynamic_types('public', env)}
${create_dynamic_types('public', env, prefixes)}
`;

@@ -56,7 +60,8 @@

const env = get_env(config.env, mode);
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
write_if_changed(
path.join(config.outDir, 'ambient.d.ts'),
template({ ...env, prefix: config.env.publicPrefix })
template(env, { public_prefix, private_prefix })
);
}

@@ -0,4 +1,6 @@

import path from 'node:path';
import { relative_path, resolve_entry } from '../../utils/filesystem.js';
import { s } from '../../utils/misc.js';
import { trim, write_if_changed } from './utils.js';
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';
import colors from 'kleur';

@@ -24,3 +26,3 @@ /**

`import * as universal from ${s(relative_path(`${output}/nodes`, node.universal))};`,
`export { universal };`
'export { universal };'
);

@@ -42,3 +44,2 @@ }

const indices = new Map();
const nodes = manifest_data.nodes

@@ -51,66 +52,80 @@ .map((node, i) => {

})
.join(',\n\t');
.join(',\n');
const layouts_with_server_load = new Set();
const dictionary = `{
${manifest_data.routes
.map((route) => {
if (route.page) {
const errors = route.page.errors.slice(1).map((n) => n ?? '');
const layouts = route.page.layouts.slice(1).map((n) => n ?? '');
const dictionary = dedent`
{
${manifest_data.routes
.map((route) => {
if (route.page) {
const errors = route.page.errors.slice(1).map((n) => n ?? '');
const layouts = route.page.layouts.slice(1).map((n) => n ?? '');
while (layouts.at(-1) === '') layouts.pop();
while (errors.at(-1) === '') errors.pop();
while (layouts.at(-1) === '') layouts.pop();
while (errors.at(-1) === '') errors.pop();
let leaf_has_server_load = false;
if (route.leaf) {
if (metadata) {
const i = /** @type {number} */ (indices.get(route.leaf));
leaf_has_server_load = metadata[i].has_server_load;
} else if (route.leaf.server) {
leaf_has_server_load = true;
let leaf_has_server_load = false;
if (route.leaf) {
if (metadata) {
const i = /** @type {number} */ (indices.get(route.leaf));
leaf_has_server_load = metadata[i].has_server_load;
} else if (route.leaf.server) {
leaf_has_server_load = true;
}
}
}
// Encode whether or not the route uses server data
// using the ones' complement, to save space
const array = [`${leaf_has_server_load ? '~' : ''}${route.page.leaf}`];
// Encode whether or not the route uses server data
// using the ones' complement, to save space
const array = [`${leaf_has_server_load ? '~' : ''}${route.page.leaf}`];
// Encode whether or not the layout uses server data.
// It's a different method compared to pages because layouts
// are reused across pages, so we save space by doing it this way.
route.page.layouts.forEach((layout) => {
if (layout == undefined) return;
// Encode whether or not the layout uses server data.
// It's a different method compared to pages because layouts
// are reused across pages, so we save space by doing it this way.
route.page.layouts.forEach((layout) => {
if (layout == undefined) return;
let layout_has_server_load = false;
let layout_has_server_load = false;
if (metadata) {
layout_has_server_load = metadata[layout].has_server_load;
} else if (manifest_data.nodes[layout].server) {
layout_has_server_load = true;
}
if (metadata) {
layout_has_server_load = metadata[layout].has_server_load;
} else if (manifest_data.nodes[layout].server) {
layout_has_server_load = true;
}
if (layout_has_server_load) {
layouts_with_server_load.add(layout);
}
});
if (layout_has_server_load) {
layouts_with_server_load.add(layout);
}
});
// only include non-root layout/error nodes if they exist
if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`);
if (errors.length > 0) array.push(`[${errors.join(',')}]`);
// only include non-root layout/error nodes if they exist
if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`);
if (errors.length > 0) array.push(`[${errors.join(',')}]`);
return `${s(route.id)}: [${array.join(',')}]`;
}
})
.filter(Boolean)
.join(',\n\t\t')}
}`.replace(/^\t/gm, '');
return `${s(route.id)}: [${array.join(',')}]`;
}
})
.filter(Boolean)
.join(',\n')}
}
`;
const hooks_file = resolve_entry(kit.files.hooks.client);
// String representation of __CLIENT__/manifest.js
const typo = resolve_entry('src/+hooks.client');
if (typo) {
console.log(
colors
.bold()
.yellow(
`Unexpected + prefix. Did you mean ${typo.split('/').at(-1)?.slice(1)}?` +
` at ${path.resolve(typo)}`
)
);
}
write_if_changed(
`${output}/manifest.js`,
trim(`
`${output}/app.js`,
dedent`
${hooks_file ? `import * as client_hooks from '${relative_path(output, hooks_file)}';` : ''}

@@ -120,3 +135,5 @@

export const nodes = [${nodes}];
export const nodes = [
${nodes}
];

@@ -132,3 +149,5 @@ export const server_loads = [${[...layouts_with_server_load].join(',')}];

};
`)
export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
`
);

@@ -135,0 +154,0 @@

@@ -1,2 +0,2 @@

import { trim, write_if_changed } from './utils.js';
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';

@@ -24,16 +24,14 @@ /**

let pyramid = `<svelte:component this={components[${l}]} data={data_${l}} {form} />`;
let pyramid = `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />`;
while (l--) {
pyramid = `
{#if components[${l + 1}]}
<svelte:component this={components[${l}]} data={data_${l}}>
${pyramid.replace(/\n/g, '\n\t\t\t\t\t')}
pyramid = dedent`
{#if constructors[${l + 1}]}
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}}>
${pyramid}
</svelte:component>
{:else}
<svelte:component this={components[${l}]} data={data_${l}} {form} />
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />
{/if}
`
.replace(/^\t\t\t/gm, '')
.trim();
`;
}

@@ -43,15 +41,27 @@

`${output}/root.svelte`,
trim(`
dedent`
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
${isSvelte5Plus() ? '<svelte:options runes={true} />' : ''}
<script>
import { setContext, afterUpdate, onMount } from 'svelte';
import { setContext, ${isSvelte5Plus() ? '' : 'afterUpdate, '}onMount, tick } from 'svelte';
import { browser } from '$app/environment';
// stores
export let stores;
export let page;
${
isSvelte5Plus()
? dedent`
let { stores, page, constructors, components = [], form, ${levels
.map((l) => `data_${l} = null`)
.join(', ')} } = $props();
`
: dedent`
export let stores;
export let page;
export let components;
export let form;
${levels.map((l) => `export let data_${l} = null;`).join('\n\t\t\t\t')}
export let constructors;
export let components = [];
export let form;
${levels.map((l) => `export let data_${l} = null;`).join('\n')}
`
}

@@ -62,8 +72,27 @@ if (!browser) {

$: stores.page.set(page);
afterUpdate(stores.page.notify);
${
isSvelte5Plus()
? dedent`
if (browser) {
$effect.pre(() => stores.page.set(page));
} else {
stores.page.set(page);
}
`
: '$: stores.page.set(page);'
}
${
isSvelte5Plus()
? dedent`
$effect(() => {
stores;page;constructors;components;form;${levels.map((l) => `data_${l}`).join(';')};
stores.page.notify();
});
`
: 'afterUpdate(stores.page.notify);'
}
let mounted = false;
let navigated = false;
let title = null;
let mounted = ${isSvelte5Plus() ? '$state(false)' : 'false'};
let navigated = ${isSvelte5Plus() ? '$state(false)' : 'false'};
let title = ${isSvelte5Plus() ? '$state(null)' : 'null'};

@@ -74,3 +103,5 @@ onMount(() => {

navigated = true;
title = document.title || 'untitled page';
tick().then(() => {
title = document.title || 'untitled page';
});
}

@@ -84,3 +115,3 @@ });

${pyramid.replace(/\n/g, '\n\t\t\t')}
${pyramid}

@@ -94,4 +125,15 @@ {#if mounted}

{/if}
`)
`
);
if (isSvelte5Plus()) {
write_if_changed(
`${output}/root.js`,
dedent`
import { asClassComponent } from 'svelte/legacy';
import Root from './root.svelte';
export default asClassComponent(Root);
`
);
}
}

@@ -1,3 +0,3 @@

import fs from 'node:fs';
import path from 'node:path';
import { hash } from '../../runtime/hash.js';
import { posixify, resolve_entry } from '../../utils/filesystem.js';

@@ -7,3 +7,4 @@ import { s } from '../../utils/misc.js';

import { runtime_directory } from '../utils.js';
import { write_if_changed } from './utils.js';
import { isSvelte5Plus, write_if_changed } from './utils.js';
import colors from 'kleur';

@@ -28,9 +29,10 @@ /**

}) => `
import root from '../root.svelte';
import { set_building, set_paths, set_private_env, set_public_env, set_version } from '${runtime_directory}/shared.js';
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
import { set_building, set_prerendering } from '__sveltekit/environment';
import { set_assets } from '__sveltekit/paths';
import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js';
set_paths(${s(config.kit.paths)});
set_version(${s(config.kit.version.name)});
export const options = {
app_dir: ${s(config.kit.appDir)},
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
csp: ${s(config.kit.csp)},

@@ -40,3 +42,5 @@ csrf_check_origin: ${s(config.kit.csrf.checkOrigin)},

env_public_prefix: '${config.kit.env.publicPrefix}',
env_private_prefix: '${config.kit.env.privatePrefix}',
hooks: null, // added lazily, via \`get_hooks\`
preload_strategy: ${s(config.kit.output.preloadStrategy)},
root,

@@ -57,3 +61,4 @@ service_worker: ${has_service_worker},

.replace(/%sveltekit\.error\.message%/g, '" + message + "')}
}
},
version_hash: ${s(hash(config.kit.version.name))}
};

@@ -65,3 +70,3 @@

export { set_building, set_paths, set_private_env, set_public_env };
export { set_assets, set_building, set_prerendering, set_private_env, set_public_env, set_safe_public_env };
`;

@@ -79,5 +84,16 @@

export function write_server(config, output) {
// TODO the casting shouldn't be necessary — investigate
const hooks_file = /** @type {string} */ (resolve_entry(config.kit.files.hooks.server));
const hooks_file = resolve_entry(config.kit.files.hooks.server);
const typo = resolve_entry('src/+hooks.server');
if (typo) {
console.log(
colors
.bold()
.yellow(
`Unexpected + prefix. Did you mean ${typo.split('/').at(-1)?.slice(1)}?` +
` at ${path.resolve(typo)}`
)
);
}
/** @param {string} file */

@@ -92,3 +108,3 @@ function relative(file) {

config,
hooks: fs.existsSync(hooks_file) ? relative(hooks_file) : null,
hooks: hooks_file ? relative(hooks_file) : null,
has_service_worker:

@@ -95,0 +111,0 @@ config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker),

@@ -1,7 +0,6 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import colors from 'kleur';
import { posixify } from '../../utils/filesystem.js';
import { write_if_changed } from './utils.js';
import { ts } from './ts.js';

@@ -45,34 +44,5 @@ /**

const user_config = load_user_tsconfig(cwd);
if (user_config) validate_user_config(kit, cwd, out, user_config);
if (user_config) validate_user_config(cwd, out, user_config);
// only specify baseUrl if a) the user doesn't specify their own baseUrl
// and b) they have non-relative paths. this causes problems with auto-imports,
// so we print a suggestion that they use relative paths instead
// TODO(v2): never include base URL, and skip the check below
let include_base_url = false;
if (user_config && !user_config.options.compilerOptions?.baseUrl) {
const non_relative_paths = new Set();
for (const paths of Object.values(user_config?.options.compilerOptions?.paths || {})) {
for (const path of paths) {
if (!path.startsWith('.')) non_relative_paths.add(path);
}
}
if (non_relative_paths.size) {
include_base_url = true;
console.log(colors.bold().yellow('Please replace non-relative compilerOptions.paths:\n'));
for (const path of non_relative_paths) {
console.log(` - "${path}" -> "./${path}"`);
}
console.log(
'\nDoing so allows us to omit "baseUrl" — which causes problems with imports — from the generated tsconfig.json. See https://github.com/sveltejs/kit/pull/8437 for more information.'
);
}
}
write_if_changed(out, JSON.stringify(get_tsconfig(kit, include_base_url), null, '\t'));
write_if_changed(out, JSON.stringify(get_tsconfig(kit), null, '\t'));
}

@@ -83,23 +53,34 @@

* @param {import('types').ValidatedKitConfig} kit
* @param {boolean} include_base_url
*/
export function get_tsconfig(kit, include_base_url) {
export function get_tsconfig(kit) {
/** @param {string} file */
const config_relative = (file) => posixify(path.relative(kit.outDir, file));
const include = ['ambient.d.ts', './types/**/$types.d.ts', config_relative('vite.config.ts')];
for (const dir of [kit.files.routes, kit.files.lib]) {
const relative = project_relative(path.dirname(dir));
include.push(config_relative(`${relative}/**/*.js`));
include.push(config_relative(`${relative}/**/*.ts`));
include.push(config_relative(`${relative}/**/*.svelte`));
const include = new Set([
'ambient.d.ts',
'non-ambient.d.ts',
'./types/**/$types.d.ts',
config_relative('vite.config.js'),
config_relative('vite.config.ts')
]);
// TODO(v2): find a better way to include all src files. We can't just use routes/lib only because
// people might have other folders/files in src that they want included.
const src_includes = [kit.files.routes, kit.files.lib, path.resolve('src')].filter((dir) => {
const relative = path.relative(path.resolve('src'), dir);
return !relative || relative.startsWith('..');
});
for (const dir of src_includes) {
include.add(config_relative(`${dir}/**/*.js`));
include.add(config_relative(`${dir}/**/*.ts`));
include.add(config_relative(`${dir}/**/*.svelte`));
}
// Test folder is a special case - we advocate putting tests in a top-level test folder
// and it's not configurable (should we make it?)
const test_folder = project_relative('tests');
include.push(config_relative(`${test_folder}/**/*.js`));
include.push(config_relative(`${test_folder}/**/*.ts`));
include.push(config_relative(`${test_folder}/**/*.svelte`));
include.add(config_relative(`${test_folder}/**/*.js`));
include.add(config_relative(`${test_folder}/**/*.ts`));
include.add(config_relative(`${test_folder}/**/*.svelte`));
const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**'];
const exclude = [config_relative('node_modules/**')];
if (path.extname(kit.files.serviceWorker)) {

@@ -116,4 +97,3 @@ exclude.push(config_relative(kit.files.serviceWorker));

// generated options
baseUrl: include_base_url ? config_relative('.') : undefined,
paths: get_tsconfig_paths(kit, include_base_url),
paths: get_tsconfig_paths(kit),
rootDirs: [config_relative('.'), './types'],

@@ -124,8 +104,7 @@

// to enforce using \`import type\` instead of \`import\` for Types.
importsNotUsedAsValues: 'error',
// Also, TypeScript doesn't know about import usages in the template because it only sees the
// script of a Svelte file. Therefore preserve all value imports.
verbatimModuleSyntax: true,
// Vite compiles modules one at a time
isolatedModules: true,
// TypeScript doesn't know about import usages in the template because it only sees the
// script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
preserveValueImports: true,

@@ -135,10 +114,8 @@ // This is required for svelte-package to work as expected

lib: ['esnext', 'DOM', 'DOM.Iterable'],
moduleResolution: 'node',
moduleResolution: 'bundler',
module: 'esnext',
target: 'esnext',
// TODO(v2): use the new flag verbatimModuleSyntax instead (requires support by Vite/Esbuild)
ignoreDeprecations: ts && Number(ts.version.split('.')[0]) >= 5 ? '5.0' : undefined
noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this
target: 'esnext'
},
include,
include: [...include],
exclude

@@ -166,3 +143,2 @@ };

/**
* @param {import('types').ValidatedKitConfig} kit
* @param {string} cwd

@@ -172,6 +148,11 @@ * @param {string} out

*/
function validate_user_config(kit, cwd, out, config) {
function validate_user_config(cwd, out, config) {
// we need to check that the user's tsconfig extends the framework config
const extend = config.options.extends;
const extends_framework_config = extend && path.resolve(cwd, extend) === out;
const extends_framework_config =
typeof extend === 'string'
? path.resolve(cwd, extend) === out
: Array.isArray(extend)
? extend.some((e) => path.resolve(cwd, e) === out)
: false;

@@ -181,25 +162,13 @@ const options = config.options.compilerOptions || {};

if (extends_framework_config) {
const { paths: user_paths } = options;
const { paths, baseUrl } = options;
if (user_paths && fs.existsSync(kit.files.lib)) {
/** @type {string[]} */
const lib = user_paths['$lib'] || [];
/** @type {string[]} */
const lib_ = user_paths['$lib/*'] || [];
// TODO(v2): check needs to be adjusted when we remove the base path
const missing_lib_paths =
!lib.some((relative) => path.resolve(cwd, relative) === kit.files.lib) ||
!lib_.some((relative) => path.resolve(cwd, relative) === path.join(kit.files.lib, '/*'));
if (missing_lib_paths) {
console.warn(
colors
.bold()
.yellow(`Your compilerOptions.paths in ${config.kind} should include the following:`)
);
let relative = posixify(path.relative('.', kit.files.lib));
if (!relative.startsWith('.')) relative = `./${relative}`;
console.warn(`{\n "$lib":["${relative}"],\n "$lib/*":["${relative}/*"]\n}`);
}
if (baseUrl || paths) {
console.warn(
colors
.bold()
.yellow(
`You have specified a baseUrl and/or paths in your ${config.kind} which interferes with SvelteKit's auto-generated tsconfig.json. ` +
'Remove it to avoid problems with intellisense. For path aliases, use `kit.alias` instead: https://kit.svelte.dev/docs/configuration#alias'
)
);
}

@@ -229,5 +198,4 @@ } else {

* @param {import('types').ValidatedKitConfig} config
* @param {boolean} include_base_url
*/
function get_tsconfig_paths(config, include_base_url) {
function get_tsconfig_paths(config) {
/** @param {string} file */

@@ -251,5 +219,3 @@ const config_relative = (file) => posixify(path.relative(config.outDir, file));

const rel_path = (include_base_url ? project_relative : config_relative)(
remove_trailing_slashstar(value)
);
const rel_path = config_relative(remove_trailing_slashstar(value));
const slashstar = key_match[2];

@@ -256,0 +222,0 @@

@@ -1,3 +0,3 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import MagicString from 'magic-string';

@@ -55,3 +55,3 @@ import { posixify, rimraf, walk } from '../../../utils/filesystem.js';

const has_meta_data = fs.existsSync(meta_data_file);
let meta_data = has_meta_data
const meta_data = has_meta_data
? /** @type {Record<string, string[]>} */ (JSON.parse(fs.readFileSync(meta_data_file, 'utf-8')))

@@ -181,3 +181,3 @@ : {};

// now generate new types
const imports = [`import type * as Kit from '@sveltejs/kit';`];
const imports = ["import type * as Kit from '@sveltejs/kit';"];

@@ -193,8 +193,22 @@ /** @type {string[]} */

declarations.push('type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;');
// returns the predicate of a matcher's type guard - or string if there is no type guard
declarations.push(
`type RouteParams = { ${route.params
.map((param) => `${param.name}${param.optional ? '?' : ''}: string`)
.join('; ')} }`
// TS complains on infer U, which seems weird, therefore ts-ignore it
[
'// @ts-ignore',
'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
].join('\n')
);
declarations.push(
'type RouteParams = ' + generate_params_type(route.params, outdir, config) + ';'
);
if (route.params.length > 0) {
exports.push(
'export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;'
);
}
declarations.push(`type RouteId = '${route.id}';`);

@@ -204,20 +218,23 @@

if (route.layout || route.leaf) {
// If T extends the empty object, void is also allowed as a return type
declarations.push(`type MaybeWithVoid<T> = {} extends T ? T | void : T;`);
// Returns the key of the object whose values are required.
declarations.push(
`export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];`
// If T extends the empty object, void is also allowed as a return type
'type MaybeWithVoid<T> = {} extends T ? T | void : T;',
// Returns the key of the object whose values are required.
'export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];',
// Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
// If none, void is also allowed as a return type.
'type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>',
// null & {} == null, we need to prevent that in some situations
'type EnsureDefined<T> = T extends null | undefined ? {} : T;',
// Takes a union type and returns a union type where each type also has all properties
// of all possible types (typed as undefined), making accessing them more ergonomic
'type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;',
// Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer <T> from the return type of `snapshot.capture`
'export type Snapshot<T = any> = Kit.Snapshot<T>;'
);
// Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
// If none, void is also allowed as a return type.
declarations.push(
`type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`
);
// null & {} == null, we need to prevent that in some situations
declarations.push(`type EnsureDefined<T> = T extends null | undefined ? {} : T;`);
// Takes a union type and returns a union type where each type also has all properties
// of all possible types (typed as undefined), making accessing them more ergonomic
declarations.push(
`type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;`
);
}

@@ -253,6 +270,6 @@

exports.push(
`export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>`
'export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>'
);
exports.push(
`export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>`
'export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>'
);

@@ -264,3 +281,4 @@ }

let all_pages_have_load = true;
const layout_params = new Set();
/** @type {import('types').RouteParam[]} */
const layout_params = [];
const ids = ['RouteId'];

@@ -274,3 +292,5 @@

for (const param of leaf.route.params) {
layout_params.add(param.name);
// skip if already added
if (layout_params.some((p) => p.name === param.name)) continue;
layout_params.push({ ...param, optional: true });
}

@@ -302,5 +322,3 @@

declarations.push(
`type LayoutParams = RouteParams & { ${Array.from(layout_params).map(
(param) => `${param}?: string`
)} }`
'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config)
);

@@ -328,7 +346,7 @@

if (route.endpoint) {
exports.push(`export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;`);
exports.push('export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;');
}
if (route.leaf?.server || route.layout?.server || route.endpoint) {
exports.push(`export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;`);
exports.push('export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;');
}

@@ -389,3 +407,3 @@

node.universal || (!is_page && all_pages_have_load)
? `Partial<App.PageData> & Record<string, any> | void`
? 'Partial<App.PageData> & Record<string, any> | void'
: `OutputDataShape<${parent_type}>`;

@@ -400,12 +418,19 @@ exports.push(

let type = 'unknown';
if (proxy) {
if (proxy.exports.includes('actions')) {
// If the file wasn't tweaked, we can use the return type of the original file.
// The advantage is that type updates are reflected without saving.
const from = proxy.modified
? `./proxy${replace_ext_with_js(basename)}`
: path_to_original(outdir, node.server);
if (proxy && proxy.exports.includes('actions')) {
// If the file wasn't tweaked, we can use the return type of the original file.
// The advantage is that type updates are reflected without saving.
const from = proxy.modified
? `./proxy${replace_ext_with_js(basename)}`
: path_to_original(outdir, node.server);
type = `Expand<Kit.AwaitedActions<typeof import('${from}').actions>> | null`;
}
exports.push(
'type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;',
'type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];',
'type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;',
'type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];',
`type ActionsExport = typeof import('${from}').actions`,
'export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>'
);
type = 'Expand<Kit.AwaitedActions<ActionsExport>> | null';
}

@@ -438,3 +463,3 @@ exports.push(`export type ActionData = ${type};`);

!is_page && all_pages_have_load
? `Partial<App.PageData> & Record<string, any> | void`
? 'Partial<App.PageData> & Record<string, any> | void'
: `OutputDataShape<${parent_type}>`;

@@ -470,3 +495,3 @@ exports.push(

: path_to_original(outdir, file_path);
const type = `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').load>>>`;
const type = `Kit.LoadProperties<Awaited<ReturnType<typeof import('${from}').load>>>`;
return expand ? `Expand<OptionalUnion<EnsureDefined<${type}>>>` : type;

@@ -568,2 +593,24 @@ } else {

/**
* @param {import('types').RouteParam[]} params
* @param {string} outdir
* @param {import('types').ValidatedConfig} config
*/
function generate_params_type(params, outdir, config) {
/** @param {string} matcher */
const path_to_matcher = (matcher) =>
posixify(path.relative(outdir, path.join(config.kit.files.params, matcher)));
return `{ ${params
.map(
(param) =>
`${param.name}${param.optional ? '?' : ''}: ${
param.matcher
? `MatcherParam<typeof import('${path_to_matcher(param.matcher)}').match>`
: 'string'
}`
)
.join('; ')} }`;
}
/**
* @param {string} content

@@ -606,3 +653,6 @@ * @param {boolean} is_server

if (node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
if (
ts.canHaveModifiers(node) &&
ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
) {
if (ts.isFunctionDeclaration(node) && node.name?.text && names.has(node.name?.text)) {

@@ -687,3 +737,3 @@ exports.set(node.name.text, node.name.text);

let a = declaration.type.pos;
let b = declaration.type.end;
const b = declaration.type.end;
while (/\s/.test(content[a])) a += 1;

@@ -760,3 +810,3 @@

let a = declaration.type.pos;
let b = declaration.type.end;
const b = declaration.type.end;
while (/\s/.test(content[a])) a += 1;

@@ -789,3 +839,3 @@

arg.name.end,
`: import('./$types').RequestEvent` + (add_parens ? ')' : '')
": import('./$types').RequestEvent" + (add_parens ? ')' : '')
);

@@ -792,0 +842,0 @@ }

@@ -1,4 +0,5 @@

import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import colors from 'kleur';
import { fileURLToPath } from 'url';
import { posixify, to_fs } from '../utils/filesystem.js';

@@ -59,1 +60,28 @@

}
/**
* @param {string} dir
* @param {(file: string) => boolean} [filter]
*/
export function list_files(dir, filter) {
/** @type {string[]} */
const files = [];
/** @param {string} current */
function walk(current) {
for (const file of fs.readdirSync(path.resolve(dir, current))) {
const child = path.posix.join(current, file);
if (fs.statSync(path.resolve(dir, child)).isDirectory()) {
walk(child);
} else {
if (!filter || filter(child)) {
files.push(child);
}
}
}
}
if (fs.existsSync(dir)) walk('');
return files;
}
/**
* @param {...import('types').Handle} handlers
* @returns {import('types').Handle}
* A helper function for sequencing multiple `handle` calls in a middleware-like manner.
* The behavior for the `handle` options is as follows:
* - `transformPageChunk` is applied in reverse order and merged
* - `preload` is applied in forward order, the first option "wins" and no `preload` options after it are called
* - `filterSerializedResponseHeaders` behaves the same as `preload`
*
* ```js
* /// file: src/hooks.server.js
* import { sequence } from '@sveltejs/kit/hooks';
*
* /// type: import('@sveltejs/kit').Handle
* async function first({ event, resolve }) {
* console.log('first pre-processing');
* const result = await resolve(event, {
* transformPageChunk: ({ html }) => {
* // transforms are applied in reverse order
* console.log('first transform');
* return html;
* },
* preload: () => {
* // this one wins as it's the first defined in the chain
* console.log('first preload');
* }
* });
* console.log('first post-processing');
* return result;
* }
*
* /// type: import('@sveltejs/kit').Handle
* async function second({ event, resolve }) {
* console.log('second pre-processing');
* const result = await resolve(event, {
* transformPageChunk: ({ html }) => {
* console.log('second transform');
* return html;
* },
* preload: () => {
* console.log('second preload');
* },
* filterSerializedResponseHeaders: () => {
* // this one wins as it's the first defined in the chain
* console.log('second filterSerializedResponseHeaders');
* }
* });
* console.log('second post-processing');
* return result;
* }
*
* export const handle = sequence(first, second);
* ```
*
* The example above would print:
*
* ```
* first pre-processing
* first preload
* second pre-processing
* second filterSerializedResponseHeaders
* second transform
* first transform
* second post-processing
* first post-processing
* ```
*
* @param {...import('@sveltejs/kit').Handle} handlers The chain of `handle` functions
* @returns {import('@sveltejs/kit').Handle}
*/

@@ -14,4 +78,4 @@ export function sequence(...handlers) {

* @param {number} i
* @param {import('types').RequestEvent} event
* @param {import('types').ResolveOptions | undefined} parent_options
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('@sveltejs/kit').ResolveOptions | undefined} parent_options
* @returns {import('types').MaybePromise<Response>}

@@ -25,3 +89,3 @@ */

resolve: (event, options) => {
/** @param {{ html: string, done: boolean }} opts */
/** @type {import('@sveltejs/kit').ResolveOptions['transformPageChunk']} */
const transformPageChunk = async ({ html, done }) => {

@@ -39,5 +103,17 @@ if (options?.transformPageChunk) {

/** @type {import('@sveltejs/kit').ResolveOptions['filterSerializedResponseHeaders']} */
const filterSerializedResponseHeaders =
parent_options?.filterSerializedResponseHeaders ??
options?.filterSerializedResponseHeaders;
/** @type {import('@sveltejs/kit').ResolveOptions['preload']} */
const preload = parent_options?.preload ?? options?.preload;
return i < length - 1
? apply_handle(i + 1, event, { transformPageChunk })
: resolve(event, { transformPageChunk });
? apply_handle(i + 1, event, {
transformPageChunk,
filterSerializedResponseHeaders,
preload
})
: resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload });
}

@@ -44,0 +120,0 @@ });

import { HttpError, Redirect, ActionFailure } from '../runtime/control.js';
import { BROWSER, DEV } from 'esm-env';
// For some reason we need to type the params as well here,
// JSdoc doesn't seem to like @type with function overloads
export { VERSION } from '../version.js';
/**
* @type {import('@sveltejs/kit').error}
* @param {number} status
* @param {any} message
* @template {number} TNumber
* @template {any[]} [TArray=[]]
* @typedef {TNumber extends TArray['length'] ? TArray[number] : LessThan<TNumber, [...TArray, TArray['length']]>} LessThan
*/
export function error(status, message) {
/**
* @template {number} TStart
* @template {number} TEnd
* @typedef {Exclude<TEnd | LessThan<TEnd>, LessThan<TStart>>} NumericRange
*/
// we have to repeat the JSDoc because the display for function overloads is broken
// see https://github.com/microsoft/TypeScript/issues/55056
/**
* Throws an error with a HTTP status code and an optional message.
* When called during request handling, this will cause SvelteKit to
* return an error response without invoking `handleError`.
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
* @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {App.Error} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
* @overload
* @param {NumericRange<400, 599>} status
* @param {App.Error} body
* @return {never}
* @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
* @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
/**
* Throws an error with a HTTP status code and an optional message.
* When called during request handling, this will cause SvelteKit to
* return an error response without invoking `handleError`.
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
* @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body] An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
* @overload
* @param {NumericRange<400, 599>} status
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body]
* @return {never}
* @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
* @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
/**
* Throws an error with a HTTP status code and an optional message.
* When called during request handling, this will cause SvelteKit to
* return an error response without invoking `handleError`.
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
* @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
* @return {never}
* @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
* @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
export function error(status, body) {
if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) {

@@ -16,6 +65,26 @@ throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`);

return new HttpError(status, message);
throw new HttpError(status, body);
}
/** @type {import('@sveltejs/kit').redirect} */
/**
* Checks whether this is an error thrown by {@link error}.
* @template {number} T
* @param {unknown} e
* @param {T} [status] The status to filter for.
* @return {e is (HttpError & { status: T extends undefined ? never : T })}
*/
export function isHttpError(e, status) {
if (!(e instanceof HttpError)) return false;
return !status || e.status === status;
}
/**
* Redirect a request. When called during request handling, SvelteKit will return a redirect response.
* Make sure you're not catching the thrown redirect, which would prevent SvelteKit from handling it.
* @param {NumericRange<300, 308>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). Must be in the range 300-308.
* @param {string | URL} location The location to redirect to.
* @throws {Redirect} This error instructs SvelteKit to redirect to the specified location.
* @throws {Error} If the provided status is invalid.
* @return {never}
*/
export function redirect(status, location) {

@@ -26,6 +95,19 @@ if ((!BROWSER || DEV) && (isNaN(status) || status < 300 || status > 308)) {

return new Redirect(status, location);
throw new Redirect(status, location.toString());
}
/** @type {import('@sveltejs/kit').json} */
/**
* Checks whether this is a redirect thrown by {@link redirect}.
* @param {unknown} e The object to check.
* @return {e is Redirect}
*/
export function isRedirect(e) {
return e instanceof Redirect;
}
/**
* Create a JSON `Response` object from the supplied data.
* @param {any} data The value that will be serialized as JSON.
* @param {ResponseInit} [init] Options such as `status` and `headers` that will be added to the response. `Content-Type: application/json` and `Content-Length` headers will be added automatically.
*/
export function json(data, init) {

@@ -56,7 +138,16 @@ // TODO deprecate this in favour of `Response.json` when it's

/** @type {import('@sveltejs/kit').text} */
/**
* Create a `Response` object from the supplied body.
* @param {string} body The value that will be used as-is.
* @param {ResponseInit} [init] Options such as `status` and `headers` that will be added to the response. A `Content-Length` header will be added automatically.
*/
export function text(body, init) {
const headers = new Headers(init?.headers);
if (!headers.has('content-length')) {
headers.set('content-length', encoder.encode(body).byteLength.toString());
const encoded = encoder.encode(body);
headers.set('content-length', encoded.byteLength.toString());
return new Response(encoded, {
...init,
headers
});
}

@@ -71,8 +162,27 @@

/**
* Generates an `ActionFailure` object.
* Create an `ActionFailure` object.
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @overload
* @param {number} status
* @param {Record<string, any> | undefined} [data]
* @returns {import('./public.js').ActionFailure<undefined>}
*/
/**
* Create an `ActionFailure` object.
* @template {Record<string, unknown> | undefined} [T=undefined]
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {T} data Data associated with the failure (e.g. validation errors)
* @overload
* @param {number} status
* @param {T} data
* @returns {import('./public.js').ActionFailure<T>}
*/
/**
* Create an `ActionFailure` object.
* @param {number} status
* @param {any} [data]
* @returns {import('./public.js').ActionFailure<any>}
*/
export function fail(status, data) {
// @ts-expect-error unique symbol missing
return new ActionFailure(status, data);
}
import * as set_cookie_parser from 'set-cookie-parser';
import { error } from '../index.js';
import { SvelteKitError } from '../../runtime/control.js';

@@ -25,15 +25,2 @@ /**

let length = content_length;
if (body_size_limit) {
if (!length) {
length = body_size_limit;
} else if (length > body_size_limit) {
throw error(
413,
`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`
);
}
}
if (req.destroyed) {

@@ -50,2 +37,13 @@ const readable = new ReadableStream();

start(controller) {
if (body_size_limit !== undefined && content_length > body_size_limit) {
const error = new SvelteKitError(
413,
'Payload Too Large',
`Content-length of ${content_length} exceeds limit of ${body_size_limit} bytes.`
);
controller.error(error);
return;
}
req.on('error', (error) => {

@@ -65,12 +63,11 @@ cancelled = true;

size += chunk.length;
if (size > length) {
if (size > content_length) {
cancelled = true;
controller.error(
error(
413,
`request body size exceeded ${
content_length ? "'content-length'" : 'BODY_SIZE_LIMIT'
} of ${length}`
)
);
const constraint = content_length ? 'content-length' : 'BODY_SIZE_LIMIT';
const message = `request body size exceeded ${constraint} of ${content_length}`;
const error = new SvelteKitError(413, 'Payload Too Large', message);
controller.error(error);
return;

@@ -98,3 +95,10 @@ }

/** @type {import('@sveltejs/kit/node').getRequest} */
/**
* @param {{
* request: import('http').IncomingMessage;
* base: string;
* bodySizeLimit?: number;
* }} options
* @returns {Promise<Request>}
*/
export async function getRequest({ request, base, bodySizeLimit }) {

@@ -110,15 +114,27 @@ return new Request(base + request.url, {

/** @type {import('@sveltejs/kit/node').setResponse} */
/**
* @param {import('http').ServerResponse} res
* @param {Response} response
* @returns {Promise<void>}
*/
export async function setResponse(res, response) {
const headers = Object.fromEntries(response.headers);
if (response.headers.has('set-cookie')) {
const header = /** @type {string} */ (response.headers.get('set-cookie'));
const split = set_cookie_parser.splitCookiesString(header);
// @ts-expect-error
headers['set-cookie'] = split;
for (const [key, value] of response.headers) {
try {
res.setHeader(
key,
key === 'set-cookie'
? set_cookie_parser.splitCookiesString(
// This is absurd but necessary, TODO: investigate why
/** @type {string}*/ (response.headers.get(key))
)
: value
);
} catch (error) {
res.getHeaderNames().forEach((name) => res.removeHeader(name));
res.writeHead(500).end(String(error));
return;
}
}
res.writeHead(response.status, headers);
res.writeHead(response.status);

@@ -131,7 +147,6 @@ if (!response.body) {

if (response.body.locked) {
res.write(
res.end(
'Fatal error: Response body is locked. ' +
`This can happen when the response was already read (for example through 'response.json()' or 'response.text()').`
"This can happen when the response was already read (for example through 'response.json()' or 'response.text()')."
);
res.end();
return;

@@ -138,0 +153,0 @@ }

@@ -1,22 +0,23 @@

import { fetch, Response, Request, Headers, FormData } from 'undici';
import { ReadableStream, TransformStream, WritableStream } from 'stream/web';
import { webcrypto as crypto } from 'crypto';
import buffer from 'node:buffer';
import { webcrypto as crypto } from 'node:crypto';
// `buffer.File` was added in Node 18.13.0 while the `File` global was added in Node 20.0.0
const File = /** @type {import('node:buffer') & { File?: File}} */ (buffer).File;
/** @type {Record<string, any>} */
const globals = {
crypto,
fetch,
Response,
Request,
Headers,
ReadableStream,
TransformStream,
WritableStream,
FormData
File
};
// exported for dev/preview and node environments
// TODO: remove this once we only support Node 18.11+ (the version multipart/form-data was added)
/**
* Make various web APIs available as globals:
* - `crypto`
* - `File`
*/
export function installPolyfills() {
for (const name in globals) {
if (name in globalThis) continue;
Object.defineProperty(globalThis, name, {

@@ -23,0 +24,0 @@ enumerable: true,

@@ -5,2 +5,3 @@ import fs from 'node:fs';

import { s } from '../../../utils/misc.js';
import { normalizePath } from 'vite';

@@ -13,3 +14,3 @@ /**

* @param {import('vite').Manifest | null} client_manifest
* @param {import('rollup').OutputAsset[] | null} css
* @param {import('vite').Rollup.OutputAsset[] | null} css
*/

@@ -53,13 +54,7 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css) {

if (node.component && client_manifest) {
const entry = find_deps(client_manifest, node.component, true);
imported.push(...entry.imports);
stylesheets.push(...entry.stylesheets);
fonts.push(...entry.fonts);
exports.push(
`export const component = async () => (await import('../${
'let component_cache;',
`export const component = async () => component_cache ??= (await import('../${
resolve_symlinks(server_manifest, node.component).chunk.file
}')).default;`,
`export const file = '${entry.file}';` // TODO what is this?
}')).default;`
);

@@ -69,12 +64,5 @@ }

if (node.universal) {
if (client_manifest) {
const entry = find_deps(client_manifest, node.universal, true);
imported.push(...entry.imports);
stylesheets.push(...entry.stylesheets);
fonts.push(...entry.fonts);
}
imports.push(`import * as universal from '../${server_manifest[node.universal].file}';`);
exports.push(`export { universal };`);
exports.push('export { universal };');
exports.push(`export const universal_id = ${s(node.universal)};`);
}

@@ -84,5 +72,18 @@

imports.push(`import * as server from '../${server_manifest[node.server].file}';`);
exports.push(`export { server };`);
exports.push('export { server };');
exports.push(`export const server_id = ${s(node.server)};`);
}
if (client_manifest && (node.universal || node.component)) {
const entry = find_deps(
client_manifest,
`${normalizePath(kit.outDir)}/generated/client-optimized/nodes/${i}.js`,
true
);
imported.push(...entry.imports);
stylesheets.push(...entry.stylesheets);
fonts.push(...entry.fonts);
}
exports.push(

@@ -89,0 +90,0 @@ `export const imports = ${s(imported)};`,

@@ -1,6 +0,6 @@

import fs from 'fs';
import fs from 'node:fs';
import * as vite from 'vite';
import { dedent } from '../../../core/sync/utils.js';
import { s } from '../../../utils/misc.js';
import { get_config_aliases } from '../utils.js';
import { assets_base } from './utils.js';

@@ -35,9 +35,15 @@ /**

// in a service worker, `location` is the location of the service worker itself,
// which is guaranteed to be `<base>/service-worker.js`
const base = "location.pathname.split('/').slice(0, -1).join('/')";
fs.writeFileSync(
service_worker,
`
dedent`
export const base = /*@__PURE__*/ ${base};
export const build = [
${Array.from(build)
.map((file) => `${s(`${kit.paths.base}/${file}`)}`)
.join(',\n\t\t\t\t')}
.map((file) => `base + ${s(`/${file}`)}`)
.join(',\n')}
];

@@ -48,8 +54,8 @@

.filter((asset) => kit.serviceWorker.files(asset.file))
.map((asset) => `${s(`${kit.paths.base}/${asset.file}`)}`)
.join(',\n\t\t\t\t')}
.map((asset) => `base + ${s(`/${asset.file}`)}`)
.join(',\n')}
];
export const prerendered = [
${prerendered.paths.map((path) => s(path)).join(',\n\t\t\t\t')}
${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
];

@@ -59,17 +65,16 @@

`
.replace(/^\t{3}/gm, '')
.trim()
);
await vite.build({
base: assets_base(kit),
build: {
lib: {
entry: /** @type {string} */ (service_worker_entry_file),
name: 'app',
formats: ['es']
},
modulePreload: false,
rollupOptions: {
input: {
'service-worker': service_worker_entry_file
},
output: {
entryFileNames: 'service-worker.js'
// .mjs so that esbuild doesn't incorrectly inject `export` https://github.com/vitejs/vite/issues/15379
entryFileNames: 'service-worker.mjs',
assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
inlineDynamicImports: true
}

@@ -80,8 +85,19 @@ },

},
configFile: false,
define: vite_config.define,
configFile: false,
publicDir: false,
resolve: {
alias: [...get_config_aliases(kit), { find: '$service-worker', replacement: service_worker }]
},
experimental: {
renderBuiltUrl(filename) {
return {
runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname`
};
}
}
});
// rename .mjs to .js to avoid incorrect MIME types with ancient webservers
fs.renameSync(`${out}/client/service-worker.mjs`, `${out}/client/service-worker.js`);
}
import fs from 'node:fs';
import path from 'node:path';
import { normalizePath } from 'vite';

@@ -9,2 +10,3 @@ /**

* @param {boolean} add_dynamic_css
* @returns {import('types').AssetDependencies}
*/

@@ -75,3 +77,5 @@ export function find_deps(manifest, entry, add_dynamic_css) {

while (!manifest[file]) {
file = path.relative('.', fs.realpathSync(file));
const next = normalizePath(path.relative('.', fs.realpathSync(file)));
if (next === file) throw new Error(`Could not find file "${file}" in Vite manifest`);
file = next;
}

@@ -84,12 +88,4 @@

/**
* @param {import('types').ValidatedKitConfig} config
* @returns {string}
*/
export function assets_base(config) {
return (config.paths.assets || config.paths.base || '.') + '/';
}
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']);
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']);
// If we'd written this in TypeScript, it could be easy...

@@ -103,1 +99,9 @@ /**

}
/**
* @param {import('types').ValidatedKitConfig} config
* @returns {string}
*/
export function assets_base(config) {
return (config.paths.assets || config.paths.base || '.') + '/';
}

@@ -1,7 +0,7 @@

import fs from 'fs';
import fs from 'node:fs';
import path from 'node:path';
import { URL } from 'node:url';
import colors from 'kleur';
import path from 'path';
import sirv from 'sirv';
import { URL } from 'url';
import { isCSSRequest, loadEnv } from 'vite';
import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite';
import { getRequest, setResponse } from '../../../exports/node/index.js';

@@ -11,3 +11,2 @@ import { installPolyfills } from '../../../exports/node/polyfills.js';

import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js';
import { should_polyfill } from '../../../utils/platform.js';
import { load_error_page } from '../../../core/config/index.js';

@@ -19,2 +18,3 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js';

import { not_found } from '../utils.js';
import { SCHEME } from '../../../utils/url.js';

@@ -30,9 +30,7 @@ const cwd = process.cwd();

export async function dev(vite, vite_config, svelte_config) {
if (should_polyfill) {
installPolyfills();
}
installPolyfills();
const fetch = globalThis.fetch;
globalThis.fetch = (info, init) => {
if (typeof info === 'string' && !/^\w+:\/\//.test(info)) {
if (typeof info === 'string' && !SCHEME.test(info)) {
throw new Error(

@@ -50,3 +48,3 @@ `Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://kit.svelte.dev/docs/web-standards#fetch-apis`

let manifest_data;
/** @type {import('types').SSRManifest} */
/** @type {import('@sveltejs/kit').SSRManifest} */
let manifest;

@@ -57,2 +55,28 @@

/** @param {string} url */
async function loud_ssr_load_module(url) {
try {
return await vite.ssrLoadModule(url, { fixStacktrace: true });
} catch (/** @type {any} */ err) {
const msg = buildErrorMessage(err, [colors.red(`Internal server error: ${err.message}`)]);
if (!vite.config.logger.hasErrorLogged(err)) {
vite.config.logger.error(msg, { error: err });
}
vite.ws.send({
type: 'error',
err: {
...err,
// these properties are non-enumerable and will
// not be serialized unless we explicitly include them
message: err.message,
stack: err.stack
}
});
throw err;
}
}
/** @param {string} id */

@@ -62,3 +86,3 @@ async function resolve(id) {

const module = await vite.ssrLoadModule(url);
const module = await loud_ssr_load_module(url);

@@ -100,7 +124,9 @@ const module_node = await vite.moduleGraph.getModuleByUrl(url);

_: {
entry: {
file: `${runtime_base}/client/start.js`,
client: {
start: `${runtime_base}/client/start.js`,
app: `${to_fs(svelte_config.kit.outDir)}/generated/client/app.js`,
imports: [],
stylesheets: [],
fonts: []
fonts: [],
uses_env_dynamic_public: true
},

@@ -124,3 +150,3 @@ nodes: manifest_data.nodes.map((node, index) => {

result.component = async () => {
const { module_node, module, url } = await resolve(
const { module_node, module } = await resolve(
/** @type {string} */ (node.component)

@@ -130,3 +156,2 @@ );

module_nodes.push(module_node);
result.file = url.endsWith('.svelte') ? url : url + '?import'; // TODO what is this for?

@@ -165,11 +190,15 @@ return module.default;

for (const dep of deps) {
const parsed = new URL(dep.url, 'http://localhost/');
const query = parsed.searchParams;
const url = new URL(dep.url, 'dummy:/');
const query = url.searchParams;
if (
isCSSRequest(dep.file) ||
(query.has('svelte') && query.get('type') === 'style')
(isCSSRequest(dep.file) ||
(query.has('svelte') && query.get('type') === 'style')) &&
!(query.has('raw') || query.has('url') || query.has('inline'))
) {
try {
const mod = await vite.ssrLoadModule(dep.url);
query.set('inline', '');
const mod = await vite.ssrLoadModule(
`${decodeURI(url.pathname)}${url.search}${url.hash}`
);
styles[dep.url] = mod.default;

@@ -204,4 +233,4 @@ } catch {

const url = path.resolve(cwd, endpoint.file);
return await vite.ssrLoadModule(url);
}
return await loud_ssr_load_module(url);
}
: null,

@@ -213,3 +242,3 @@ endpoint_id: endpoint?.file

matchers: async () => {
/** @type {Record<string, import('types').ParamMatcher>} */
/** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */
const matchers = {};

@@ -220,3 +249,3 @@

const url = path.resolve(cwd, file);
const module = await vite.ssrLoadModule(url);
const module = await vite.ssrLoadModule(url, { fixStacktrace: true });

@@ -236,5 +265,6 @@ if (module.match) {

/** @param {string} stack */
function fix_stack_trace(stack) {
return stack ? vite.ssrRewriteStacktrace(stack) : stack;
/** @param {Error} error */
function fix_stack_trace(error) {
vite.ssrFixStacktrace(error);
return error.stack;
}

@@ -336,3 +366,3 @@

// causing `instanceof` checks to fail
const control_module_node = await import(`../../../runtime/control.js`);
const control_module_node = await import('../../../runtime/control.js');
const control_module_vite = await vite.ssrLoadModule(`${runtime_base}/control.js`);

@@ -343,3 +373,4 @@

HttpError: control_module_vite.HttpError,
Redirect: control_module_vite.Redirect
Redirect: control_module_vite.Redirect,
SvelteKitError: control_module_vite.SvelteKitError
});

@@ -384,3 +415,3 @@ }

res.statusCode = 500;
res.end(fix_stack_trace(/** @type {string} */ (error.stack)));
res.end(fix_stack_trace(error));
}

@@ -444,21 +475,15 @@ });

// we have to import `Server` before calling `set_paths`
// we have to import `Server` before calling `set_assets`
const { Server } = /** @type {import('types').ServerModule} */ (
await vite.ssrLoadModule(`${runtime_base}/server/index.js`)
await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true })
);
const { set_paths, set_version, set_fix_stack_trace } =
/** @type {import('types').ServerInternalModule} */ (
await vite.ssrLoadModule(`${runtime_base}/shared.js`)
);
const { set_fix_stack_trace } = await vite.ssrLoadModule(
`${runtime_base}/shared-server.js`
);
set_fix_stack_trace(fix_stack_trace);
set_paths({
base: svelte_config.kit.paths.base,
assets
});
const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths');
set_assets(assets);
set_version(svelte_config.kit.version.name);
set_fix_stack_trace(fix_stack_trace);
const server = new Server(manifest);

@@ -468,14 +493,7 @@

let request;
const request = await getRequest({
base,
request: req
});
try {
request = await getRequest({
base,
request: req
});
} catch (/** @type {any} */ err) {
res.statusCode = err.status || 400;
return res.end('Invalid request body');
}
if (manifest_error) {

@@ -523,3 +541,3 @@ console.error(colors.bold().red(manifest_error.message));

res.statusCode = 500;
res.end(fix_stack_trace(/** @type {string} */ (error.stack)));
res.end(fix_stack_trace(error));
}

@@ -534,3 +552,3 @@ });

function remove_static_middlewares(server) {
const static_middlewares = ['viteServeStaticMiddleware'];
const static_middlewares = ['viteServeStaticMiddleware', 'viteServePublicMiddleware'];
for (let i = server.stack.length - 1; i > 0; i--) {

@@ -537,0 +555,0 @@ // @ts-expect-error using internals

@@ -1,5 +0,7 @@

import path from 'path';
import path from 'node:path';
import { posixify } from '../../../utils/filesystem.js';
import { strip_virtual_prefix } from '../utils.js';
import { env_dynamic_private, env_static_private } from '../module_ids.js';
const ILLEGAL_IMPORTS = new Set(['\0$env/dynamic/private', '\0$env/static/private']);
const ILLEGAL_IMPORTS = new Set([env_dynamic_private, env_static_private]);
const ILLEGAL_MODULE_NAME_PATTERN = /.*\.server\..+/;

@@ -24,3 +26,3 @@

* Creates a guard that checks that no id imports a module that is not allowed to be imported into client-side code.
* @param {import('rollup').PluginContext} context
* @param {import('vite').Rollup.PluginContext} context
* @param {{ cwd: string; lib: string }} paths

@@ -55,6 +57,10 @@ */

return `${' '.repeat(i * 2)}- ${id} ${dynamic ? 'dynamically imports' : 'imports'}\n`;
}) + `${' '.repeat(chain.length)}- ${id}`;
return `${' '.repeat(i * 2)}- ${strip_virtual_prefix(id)} ${
dynamic ? 'dynamically imports' : 'imports'
}\n`;
}) + `${' '.repeat(chain.length)}- ${strip_virtual_prefix(id)}`;
const message = `Cannot import ${id} into client-side code:\n${pyramid}`;
const message = `Cannot import ${strip_virtual_prefix(
id
)} into client-side code:\n${pyramid}`;

@@ -61,0 +67,0 @@ throw new Error(message);

@@ -8,3 +8,3 @@ import fs from 'node:fs';

import { mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js';
import { copy, mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js';
import { create_static_module, create_dynamic_module } from '../../core/env.js';

@@ -22,12 +22,22 @@ import * as sync from '../../core/sync/sync.js';

import { preview } from './preview/index.js';
import { get_config_aliases, get_env } from './utils.js';
import { get_config_aliases, get_env, strip_virtual_prefix } from './utils.js';
import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
import prerender from '../../core/postbuild/prerender.js';
import analyse from '../../core/postbuild/analyse.js';
import { s } from '../../utils/misc.js';
import { hash } from '../../runtime/hash.js';
import { dedent, isSvelte5Plus } from '../../core/sync/utils.js';
import {
env_dynamic_private,
env_dynamic_public,
env_static_private,
env_static_public,
service_worker,
sveltekit_environment,
sveltekit_paths
} from './module_ids.js';
export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const cwd = process.cwd();
/** @type {import('./types').EnforcedConfig} */
/** @type {import('./types.js').EnforcedConfig} */
const enforced_config = {

@@ -74,3 +84,3 @@ appType: true,

/** @type {import('@sveltejs/vite-plugin-svelte').PreprocessorGroup} */
/** @type {import('svelte/compiler').PreprocessorGroup} */
const warning_preprocessor = {

@@ -101,6 +111,10 @@ script: ({ content, filename }) => {

const basename = path.basename(filename);
if (basename.startsWith('+layout.') && !content.includes('<slot')) {
const has_children =
content.includes('<slot') || (isSvelte5Plus() && content.includes('{@render'));
if (basename.startsWith('+layout.') && !has_children) {
const message =
`\n${colors.bold().red(path.relative('.', filename))}\n` +
`\`<slot />\` missing — inner content will not be rendered`;
`\`<slot />\`${isSvelte5Plus() ? ' or `{@render ...}` tag' : ''}` +
' missing — inner content will not be rendered';

@@ -115,3 +129,6 @@ if (!warned.has(message)) {

/** @return {Promise<import('vite').Plugin[]>} */
/**
* Returns the SvelteKit Vite plugins.
* @returns {Promise<import('vite').Plugin[]>}
*/
export async function sveltekit() {

@@ -138,3 +155,3 @@ const svelte_config = await load_config();

// @ts-expect-error SvelteKit requires hydratable true by default
hydratable: true,
hydratable: isSvelte5Plus() ? undefined : true,
...svelte_config.compilerOptions

@@ -148,7 +165,9 @@ },

/**
* If `true`, the server build has been completed and we're creating the client build
*/
let secondary_build = false;
// These variables live outside the `kit()` function because it is re-invoked by each Vite build
let secondary_build_started = false;
/** @type {import('types').ManifestData} */
let manifest_data;
/**

@@ -171,2 +190,4 @@ * Returns the SvelteKit Vite plugin. Vite executes Rollup hooks as well as some of its own.

const version_hash = hash(kit.version.name);
/** @type {import('vite').ResolvedConfig} */

@@ -178,5 +199,2 @@ let vite_config;

/** @type {import('types').ManifestData} */
let manifest_data;
/** @type {boolean} */

@@ -188,7 +206,13 @@ let is_build;

/** @type {(() => Promise<void>) | null} */
let finalise = null;
/** @type {() => Promise<void>} */
let finalise;
/** @type {import('vite').UserConfig} */
let initial_config;
const service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) =>
relative_path.includes('node_modules') || relative_path.includes(kit.outDir);
/** @type {import('vite').Plugin} */

@@ -203,2 +227,3 @@ const plugin_setup = {

async config(config, config_env) {
initial_config = config;
vite_config_env = config_env;

@@ -230,8 +255,3 @@ is_build = config_env.command === 'build';

alias: [
{
find: '__CLIENT__',
replacement: `${generated}/${is_build ? 'client-optimized' : 'client'}`
},
{ find: '__SERVER__', replacement: `${generated}/server` },
{ find: '__GENERATED__', replacement: generated },
{ find: '$app', replacement: `${runtime_directory}/app` },

@@ -243,5 +263,7 @@ ...get_config_aliases(kit)

server: {
cors: { preflightContinue: true },
fs: {
allow: [...allow]
},
sourcemapIgnoreList,
watch: {

@@ -254,2 +276,5 @@ ignored: [

},
preview: {
cors: { preflightContinue: true }
},
optimizeDeps: {

@@ -263,2 +288,21 @@ exclude: [

]
},
ssr: {
noExternal: [
// This ensures that esm-env is inlined into the server output with the
// export conditions resolved correctly through Vite. This prevents adapters
// that bundle later on from resolving the export conditions incorrectly
// and for example include browser-only code in the server output
// because they for example use esbuild.build with `platform: 'browser'`
'esm-env',
// We need this for two reasons:
// 1. Without this, `@sveltejs/kit` imports are kept as-is in the server output,
// and that causes modules and therefore classes like `Redirect` to be imported twice
// under different IDs, which breaks a bunch of stuff because of failing instanceof checks.
// 2. Vitest bypasses Vite when loading external modules, so we bundle
// when it is detected to keep our virtual modules working.
// See https://github.com/sveltejs/kit/pull/9172
// and https://vitest.dev/config/#deps-registernodeloader
'@sveltejs/kit'
]
}

@@ -269,8 +313,8 @@ };

if (!new_config.build) new_config.build = {};
new_config.build.ssr = !secondary_build;
new_config.build.ssr = !secondary_build_started;
new_config.define = {
__SVELTEKIT_ADAPTER_NAME__: JSON.stringify(kit.adapter?.name),
__SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${kit.appDir}/version.json`),
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(kit.version.pollInterval),
__SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name),
__SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval),
__SVELTEKIT_DEV__: 'false',

@@ -280,14 +324,5 @@ __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'

new_config.ssr = {
noExternal: [
// TODO document why this is necessary
'@sveltejs/kit',
// This ensures that esm-env is inlined into the server output with the
// export conditions resolved correctly through Vite. This prevents adapters
// that bundle later on to resolve the export conditions incorrectly
// and for example include browser-only code in the server output
// because they for example use esbuild.build with `platform: 'browser'`
'esm-env'
]
};
if (!secondary_build_started) {
manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
}
} else {

@@ -300,9 +335,8 @@ new_config.define = {

new_config.ssr = {
// Without this, Vite will treat `@sveltejs/kit` as noExternal if it's
// a linked dependency, and that causes modules to be imported twice
// under different IDs, which breaks a bunch of stuff
// https://github.com/vitejs/vite/pull/9296
external: ['@sveltejs/kit', 'cookie', 'set-cookie-parser']
};
// These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
// Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`.
/** @type {NonNullable<import('vite').UserConfig['ssr']>} */ (new_config.ssr).external = [
'cookie',
'set-cookie-parser'
];
}

@@ -320,6 +354,2 @@

vite_config = config;
// This is a hack to prevent Vite from nuking useful logs,
// pending https://github.com/vitejs/vite/issues/9378
config.logger.warn('');
}

@@ -334,6 +364,14 @@ };

// treat $env/static/[public|private] as virtual
if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`;
if (id.startsWith('$env/') || id.startsWith('__sveltekit/') || id === '$service-worker') {
return `\0virtual:${id}`;
}
},
async load(id, options) {
const browser = !options?.ssr;
const global = is_build
? `globalThis.__sveltekit_${version_hash}`
: 'globalThis.__sveltekit_dev';
if (options?.ssr === false && process.env.TEST !== 'true') {

@@ -350,3 +388,3 @@ const normalized_cwd = vite.normalizePath(cwd);

const relative = normalize_id(id, normalized_lib, normalized_cwd);
throw new Error(`Cannot import ${relative} into client-side code`);
throw new Error(`Cannot import ${strip_virtual_prefix(relative)} into client-side code`);
}

@@ -356,7 +394,9 @@ }

switch (id) {
case '\0$env/static/private':
case env_static_private:
return create_static_module('$env/static/private', env.private);
case '\0$env/static/public':
case env_static_public:
return create_static_module('$env/static/public', env.public);
case '\0$env/dynamic/private':
case env_dynamic_private:
return create_dynamic_module(

@@ -366,3 +406,9 @@ 'private',

);
case '\0$env/dynamic/public':
case env_dynamic_public:
// populate `$env/dynamic/public` from `window`
if (browser) {
return `export const env = ${global}.env ?? (await import(/* @vite-ignore */ ${global}.base + '/' + '${kit.appDir}/env.js')).env;`;
}
return create_dynamic_module(

@@ -372,4 +418,63 @@ 'public',

);
case '\0$service-worker':
case service_worker:
return create_service_worker_module(svelte_config);
// for internal use only. it's published as $app/paths externally
// we use this alias so that we won't collide with user aliases
case sveltekit_paths: {
const { assets, base } = svelte_config.kit.paths;
// use the values defined in `global`, but fall back to hard-coded values
// for the sake of things like Vitest which may import this module
// outside the context of a page
if (browser) {
return dedent`
export const base = ${global}?.base ?? ${s(base)};
export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};
`;
}
return dedent`
export let base = ${s(base)};
export let assets = ${assets ? s(assets) : 'base'};
export const relative = ${svelte_config.kit.paths.relative};
const initial = { base, assets };
export function override(paths) {
base = paths.base;
assets = paths.assets;
}
export function reset() {
base = initial.base;
assets = initial.assets;
}
/** @param {string} path */
export function set_assets(path) {
assets = initial.assets = path;
}
`;
}
case sveltekit_environment: {
const { version } = svelte_config.kit;
return dedent`
export const version = ${s(version.name)};
export let building = false;
export let prerendering = false;
export function set_building() {
building = true;
}
export function set_prerendering() {
prerendering = true;
}
`;
}
}

@@ -390,3 +495,3 @@ }

async handler(_options) {
if (!secondary_build) return;
if (vite_config.build.ssr) return;

@@ -417,3 +522,3 @@ const guard = module_guard(this, {

*/
async config(config, config_env) {
async config(config) {
/** @type {import('vite').UserConfig} */

@@ -423,4 +528,2 @@ let new_config;

if (is_build) {
manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
const ssr = /** @type {boolean} */ (config.build?.ssr);

@@ -467,32 +570,30 @@ const prefix = `${kit.appDir}/immutable`;

} else {
/** @type {Record<string, string>} */
input.start = `${runtime_directory}/client/start.js`;
input['entry/start'] = `${runtime_directory}/client/start.js`;
input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`;
manifest_data.nodes.forEach((node) => {
if (node.component) {
const resolved = path.resolve(node.component);
const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
const name = relative.startsWith('..')
? path.basename(node.component)
: posixify(path.join('pages', relative));
input[`components/${name}`] = resolved;
manifest_data.nodes.forEach((node, i) => {
if (node.component || node.universal) {
input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`;
}
if (node.universal) {
const resolved = path.resolve(node.universal);
const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
const name = relative.startsWith('..')
? path.basename(node.universal)
: posixify(path.join('pages', relative));
input[`modules/${name}`] = resolved;
}
});
}
// see the kit.output.preloadStrategy option for details on why we have multiple options here
const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : 'js';
// We could always use a relative asset base path here, but it's better for performance not to.
// E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
// That's larger and takes longer to run and also causes an HTML diff between SSR and client
// causing us to do a more expensive hydration check.
const client_base =
kit.paths.relative !== false || kit.paths.assets ? './' : kit.paths.base || '/';
new_config = {
base: ssr ? assets_base(kit) : './',
base: ssr ? assets_base(kit) : client_base,
build: {
copyPublicDir: !ssr,
cssCodeSplit: true,
cssMinify: initial_config.build?.minify == null ? true : !!initial_config.build.minify,
// don't use the default name to avoid collisions with 'static/manifest.json'
manifest: '.vite/manifest.json', // TODO: remove this after bumping peer dep to vite 5
outDir: `${out}/${ssr ? 'server' : 'client'}`,

@@ -503,14 +604,14 @@ rollupOptions: {

format: 'esm',
entryFileNames: ssr ? '[name].js' : `${prefix}/[name]-[hash].js`,
chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name]-[hash].js`,
assetFileNames: `${prefix}/assets/[name]-[hash][extname]`,
hoistTransitiveImports: false
entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
hoistTransitiveImports: false,
sourcemapIgnoreList
},
preserveEntrySignatures: 'strict'
},
target: ssr ? 'node16.14' : undefined,
// don't use the default name to avoid collisions with 'static/manifest.json'
manifest: 'vite-manifest.json'
ssrEmitAssets: true,
target: ssr ? 'node18.13' : 'es2022'
},
publicDir: ssr ? false : kit.files.assets,
publicDir: kit.files.assets,
worker: {

@@ -567,7 +668,4 @@ rollupOptions: {

buildStart() {
if (secondary_build) return;
if (secondary_build_started) return;
// reset (here, not in `config`, because `build --watch` skips `config`)
finalise = null;
if (is_build) {

@@ -582,3 +680,3 @@ if (!vite_config.build.watch) {

generateBundle() {
if (!secondary_build) return;
if (vite_config.build.ssr) return;

@@ -588,3 +686,3 @@ this.emitFile({

fileName: `${kit.appDir}/version.json`,
source: JSON.stringify({ version: kit.version.name })
source: s({ version: kit.version.name })
});

@@ -595,4 +693,4 @@ },

* Vite builds a single bundle. We need three bundles: client, server, and service worker.
* The user's package.json scripts will invoke the Vite CLI to execute the client build. We
* then use this hook to kick off builds for the server and service worker.
* The user's package.json scripts will invoke the Vite CLI to execute the server build. We
* then use this hook to kick off builds for the client and service worker.
*/

@@ -602,4 +700,3 @@ writeBundle: {

async handler(_options) {
if (secondary_build) return; // only run this once
secondary_build = true;
if (secondary_build_started) return; // only run this once

@@ -617,4 +714,4 @@ const verbose = vite_config.logLevel === 'info';

manifest_data,
service_worker: !!service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
client_entry: null,
service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
client: null,
server_manifest

@@ -634,2 +731,4 @@ };

// first, build server nodes without the client manifest so we can analyse it
log.info('Analysing routes');
build_server_nodes(out, kit, manifest_data, server_manifest, null, null);

@@ -642,2 +741,4 @@

log.info('Building app');
// create client build

@@ -651,3 +752,5 @@ write_client_manifest(

const { output } = /** @type {import('rollup').RollupOutput} */ (
secondary_build_started = true;
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ (
await vite.build({

@@ -658,17 +761,40 @@ configFile: vite_config.configFile,

logLevel: vite_config.logLevel,
clearScreen: vite_config.clearScreen
clearScreen: vite_config.clearScreen,
build: {
minify: initial_config.build?.minify,
assetsInlineLimit: vite_config.build.assetsInlineLimit,
sourcemap: vite_config.build.sourcemap
},
optimizeDeps: {
force: vite_config.optimizeDeps.force
}
})
);
copy(
`${out}/server/${kit.appDir}/immutable/assets`,
`${out}/client/${kit.appDir}/immutable/assets`
);
/** @type {import('vite').Manifest} */
const client_manifest = JSON.parse(read(`${out}/client/${vite_config.build.manifest}`));
build_data.client_entry = find_deps(
client_manifest,
posixify(path.relative('.', `${runtime_directory}/client/start.js`)),
false
);
const deps_of = /** @param {string} f */ (f) =>
find_deps(client_manifest, posixify(path.relative('.', f)), false);
const start = deps_of(`${runtime_directory}/client/start.js`);
const app = deps_of(`${kit.outDir}/generated/client-optimized/app.js`);
build_data.client = {
start: start.file,
app: app.file,
imports: [...start.imports, ...app.imports],
stylesheets: [...start.stylesheets, ...app.stylesheets],
fonts: [...start.fonts, ...app.fonts],
uses_env_dynamic_public: output.some(
(chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
)
};
const css = output.filter(
/** @type {(value: any) => value is import('rollup').OutputAsset} */
/** @type {(value: any) => value is import('vite').Rollup.OutputAsset} */
(value) => value.type === 'asset' && value.fileName.endsWith('.css')

@@ -691,4 +817,2 @@ );

// ...and prerender
log.info('Prerendering');
const { prerendered, prerender_map } = await prerender({

@@ -741,3 +865,11 @@ out,

const { adapt } = await import('../../core/adapt/index.js');
await adapt(svelte_config, build_data, metadata, prerendered, prerender_map, log);
await adapt(
svelte_config,
build_data,
metadata,
prerendered,
prerender_map,
log,
vite_config
);
} else {

@@ -752,5 +884,3 @@ console.log(colors.bold().yellow('\nNo adapter specified'));

// avoid making the manifest available to users
fs.unlinkSync(`${out}/client/${vite_config.build.manifest}`);
fs.unlinkSync(`${out}/server/${vite_config.build.manifest}`);
secondary_build_started = false;
};

@@ -766,3 +896,4 @@ }

async handler() {
finalise?.();
if (!vite_config.build.ssr) return;
await finalise?.();
}

@@ -793,3 +924,3 @@ }

* @param {Record<string, any>} resolved_config
* @param {import('./types').EnforcedConfig} enforced_config
* @param {import('./types.js').EnforcedConfig} enforced_config
* @param {string} path

@@ -822,16 +953,16 @@ * @param {string[]} out used locally to compute the return value

*/
const create_service_worker_module = (config) => `
if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
throw new Error('This module can only be imported inside a service worker');
}
const create_service_worker_module = (config) => dedent`
if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
throw new Error('This module can only be imported inside a service worker');
}
export const build = [];
export const files = [
${create_assets(config)
.filter((asset) => config.kit.serviceWorker.files(asset.file))
.map((asset) => `${JSON.stringify(`${config.kit.paths.base}/${asset.file}`)}`)
.join(',\n\t\t\t\t')}
];
export const prerendered = [];
export const version = ${JSON.stringify(config.kit.version.name)};
export const build = [];
export const files = [
${create_assets(config)
.filter((asset) => config.kit.serviceWorker.files(asset.file))
.map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
.join(',\n')}
];
export const prerendered = [];
export const version = ${s(config.kit.version.name)};
`;

@@ -1,5 +0,6 @@

import fs from 'fs';
import { join } from 'path';
import fs from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { lookup } from 'mrmime';
import sirv from 'sirv';
import { pathToFileURL } from 'url';
import { loadEnv, normalizePath } from 'vite';

@@ -9,3 +10,2 @@ import { getRequest, setResponse } from '../../../exports/node/index.js';

import { SVELTE_KIT_ASSETS } from '../../../constants.js';
import { should_polyfill } from '../../../utils/platform.js';
import { not_found } from '../utils.js';

@@ -18,6 +18,3 @@

/**
* @param {{
* middlewares: import('connect').Server;
* httpServer: import('http').Server;
* }} vite
* @param {{ middlewares: import('connect').Server }} vite
* @param {import('vite').ResolvedConfig} vite_config

@@ -27,5 +24,3 @@ * @param {import('types').ValidatedConfig} svelte_config

export async function preview(vite, vite_config, svelte_config) {
if (should_polyfill) {
installPolyfills();
}
installPolyfills();

@@ -42,4 +37,8 @@ const { paths } = svelte_config.kit;

if (!fs.existsSync(dir)) {
throw new Error(`Server files not found at ${dir}, did you run \`build\` first?`);
}
/** @type {import('types').ServerInternalModule} */
const { set_paths } = await import(pathToFileURL(join(dir, 'internal.js')).href);
const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href);

@@ -51,3 +50,3 @@ /** @type {import('types').ServerModule} */

set_paths({ base, assets });
set_assets(assets);

@@ -60,2 +59,14 @@ const server = new Server(manifest);

return () => {
// Remove the base middleware. It screws with the URL.
// It also only lets through requests beginning with the base path, so that requests beginning
// with the assets URL never reach us. We could serve assets separately before the base
// middleware, but we'd need that to occur after the compression and cors middlewares, so would
// need to insert it manually into the stack, which would be at least as bad as doing this.
for (let i = vite.middlewares.stack.length - 1; i > 0; i--) {
// @ts-expect-error using internals
if (vite.middlewares.stack[i].handle.name === 'viteBaseMiddleware') {
vite.middlewares.stack.splice(i, 1);
}
}
// generated client assets and the contents of `static`

@@ -78,4 +89,16 @@ vite.middlewares.use(

const original_url = /** @type {string} */ (req.url);
const { pathname } = new URL(original_url, 'http://dummy');
const { pathname, search } = new URL(original_url, 'http://dummy');
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
// regardless of the `trailingSlash` route option
if (base.length > 1 && pathname === base) {
let location = base + '/';
if (search) location += search;
res.writeHead(307, {
location
});
res.end();
return;
}
if (pathname.startsWith(base)) {

@@ -110,3 +133,3 @@ next();

const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy');
const { pathname, search } = new URL(/** @type {string} */ (req.url), 'http://dummy');

@@ -119,4 +142,29 @@ let filename = normalizePath(

if (!prerendered) {
filename += filename.endsWith('/') ? 'index.html' : '.html';
prerendered = is_file(filename);
const has_trailing_slash = pathname.endsWith('/');
const html_filename = `${filename}${has_trailing_slash ? 'index.html' : '.html'}`;
/** @type {string | undefined} */
let redirect;
if (is_file(html_filename)) {
filename = html_filename;
prerendered = true;
} else if (has_trailing_slash) {
if (is_file(filename.slice(0, -1) + '.html')) {
redirect = pathname.slice(0, -1);
}
} else if (is_file(filename + '/index.html')) {
redirect = pathname + '/';
}
if (redirect) {
if (search) redirect += search;
res.writeHead(307, {
location: redirect
});
res.end();
return;
}
}

@@ -126,3 +174,3 @@

res.writeHead(200, {
'content-type': 'text/html',
'content-type': lookup(pathname) || 'text/html',
etag

@@ -142,14 +190,7 @@ });

let request;
const request = await getRequest({
base: `${protocol}://${host}`,
request: req
});
try {
request = await getRequest({
base: `${protocol}://${host}`,
request: req
});
} catch (/** @type {any} */ err) {
res.statusCode = err.status || 400;
return res.end('Invalid request body');
}
setResponse(

@@ -179,3 +220,3 @@ res,

maxAge: 0
})
})
: (_req, _res, next) => next();

@@ -182,0 +223,0 @@

@@ -1,5 +0,6 @@

import path from 'path';
import path from 'node:path';
import { loadEnv } from 'vite';
import { posixify } from '../../utils/filesystem.js';
import { negotiate } from '../../utils/http.js';
import { filter_private_env, filter_public_env } from '../../utils/env.js';

@@ -59,7 +60,8 @@ /**

export function get_env(env_config, mode) {
const entries = Object.entries(loadEnv(mode, env_config.dir, ''));
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = env_config;
const env = loadEnv(mode, env_config.dir, '');
return {
public: Object.fromEntries(entries.filter(([k]) => k.startsWith(env_config.publicPrefix))),
private: Object.fromEntries(entries.filter(([k]) => !k.startsWith(env_config.publicPrefix)))
public: filter_public_env(env, { public_prefix, private_prefix }),
private: filter_private_env(env, { public_prefix, private_prefix })
};

@@ -91,9 +93,11 @@ }

res.end(
`The server is configured with a public base URL of /path-base - did you mean to visit <a href="${prefixed}">${prefixed}</a> instead?`
`The server is configured with a public base URL of ${base} - did you mean to visit <a href="${prefixed}">${prefixed}</a> instead?`
);
} else {
res.end(
`The server is configured with a public base URL of /path-base - did you mean to visit ${prefixed} instead?`
`The server is configured with a public base URL of ${base} - did you mean to visit ${prefixed} instead?`
);
}
}
export const strip_virtual_prefix = /** @param {string} id */ (id) => id.replace('\0virtual:', '');
import { BROWSER, DEV } from 'esm-env';
export { building, version } from '__sveltekit/environment';
/**
* @type {import('$app/environment').browser}
* `true` if the app is running in the browser.
*/

@@ -9,6 +10,4 @@ export const browser = BROWSER;

/**
* @type {import('$app/environment').dev}
* Whether the dev server is running. This is not guaranteed to correspond to `NODE_ENV` or `MODE`.
*/
export const dev = DEV;
export { building, version } from '../shared.js';
import * as devalue from 'devalue';
import { BROWSER, DEV } from 'esm-env';
import { client } from '../client/singletons.js';
import { invalidateAll } from './navigation.js';
import { BROWSER, DEV } from 'esm-env';
/**
* @param {string} name
* This action updates the `form` property of the current page with the given data and updates `$page.status`.
* In case of an error, it redirects to the nearest error page.
* @template {Record<string, unknown> | undefined} Success
* @template {Record<string, unknown> | undefined} Failure
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result
* @returns {Promise<void>}
*/
function guard(name) {
return () => {
throw new Error(`Cannot call ${name}(...) on the server`);
};
export function applyAction(result) {
if (BROWSER) {
return client.apply_action(result);
} else {
throw new Error('Cannot call applyAction(...) on the server');
}
}
/** @type {import('$app/forms').applyAction} */
export const applyAction = BROWSER ? client.apply_action : guard('applyAction');
/** @type {import('$app/forms').deserialize} */
/**
* Use this function to deserialize the response from a form submission.
* Usage:
*
* ```js
* import { deserialize } from '$app/forms';
*
* async function handleSubmit(event) {
* const response = await fetch('/form?/action', {
* method: 'POST',
* body: new FormData(event.target)
* });
*
* const result = deserialize(await response.text());
* // ...
* }
* ```
* @template {Record<string, unknown> | undefined} Success
* @template {Record<string, unknown> | undefined} Failure
* @param {string} result
* @returns {import('@sveltejs/kit').ActionResult<Success, Failure>}
*/
export function deserialize(result) {

@@ -27,9 +52,37 @@ const parsed = JSON.parse(result);

/** @type {import('$app/forms').enhance} */
export function enhance(form, submit = () => {}) {
if (
DEV &&
/** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form)).method !==
'post'
) {
/**
* Shallow clone an element, so that we can access e.g. `form.action` without worrying
* that someone has added an `<input name="action">` (https://github.com/sveltejs/kit/issues/7593)
* @template {HTMLElement} T
* @param {T} element
* @returns {T}
*/
function clone(element) {
return /** @type {T} */ (HTMLElement.prototype.cloneNode.call(element));
}
/**
* This action enhances a `<form>` element that otherwise would work without JavaScript.
*
* The `submit` function is called upon submission with the given FormData and the `action` that should be triggered.
* If `cancel` is called, the form will not be submitted.
* You can use the abort `controller` to cancel the submission in case another one starts.
* If a function is returned, that function is called with the response from the server.
* If nothing is returned, the fallback will be used.
*
* If this function or its return value isn't set, it
* - falls back to updating the `form` prop with the returned data if the action is one same page as the form
* - updates `$page.status`
* - resets the `<form>` element and invalidates all data in case of successful submission with no redirect response
* - redirects in case of a redirect response
* - redirects to the nearest error page in case of an unexpected error
*
* If you provide a custom function with a callback and want to use the default behavior, invoke `update` in your callback.
* @template {Record<string, unknown> | undefined} Success
* @template {Record<string, unknown> | undefined} Failure
* @param {HTMLFormElement} form_element The form element
* @param {import('@sveltejs/kit').SubmitFunction<Success, Failure>} submit Submit callback
*/
export function enhance(form_element, submit = () => {}) {
if (DEV && clone(form_element).method !== 'post') {
throw new Error('use:enhance can only be used on <form> fields with method="POST"');

@@ -41,13 +94,21 @@ }

* action: URL;
* result: import('types').ActionResult;
* invalidateAll?: boolean;
* result: import('@sveltejs/kit').ActionResult;
* reset?: boolean
* }} opts
*/
const fallback_callback = async ({ action, result, reset }) => {
const fallback_callback = async ({
action,
result,
reset = true,
invalidateAll: shouldInvalidateAll = true
}) => {
if (result.type === 'success') {
if (reset !== false) {
if (reset) {
// We call reset from the prototype to avoid DOM clobbering
HTMLFormElement.prototype.reset.call(form);
HTMLFormElement.prototype.reset.call(form_element);
}
await invalidateAll();
if (shouldInvalidateAll) {
await invalidateAll();
}
}

@@ -68,2 +129,7 @@

async function handle_submit(event) {
const method = event.submitter?.hasAttribute('formmethod')
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formMethod
: clone(form_element).method;
if (method !== 'post') return;
event.preventDefault();

@@ -73,13 +139,22 @@

// We can't do submitter.formAction directly because that property is always set
// We do cloneNode for avoid DOM clobbering - https://github.com/sveltejs/kit/issues/7593
event.submitter?.hasAttribute('formaction')
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
: /** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form)).action
: clone(form_element).action
);
const data = new FormData(form);
const form_data = new FormData(form_element);
if (DEV && clone(form_element).enctype !== 'multipart/form-data') {
for (const value of form_data.values()) {
if (value instanceof File) {
throw new Error(
'Your form contains <input type="file"> fields, but is missing the necessary `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819.'
);
}
}
}
const submitter_name = event.submitter?.getAttribute('name');
if (submitter_name) {
data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
}

@@ -97,8 +172,9 @@

controller,
data,
form
formData: form_data,
formElement: form_element,
submitter: event.submitter
})) ?? fallback_callback;
if (cancelled) return;
/** @type {import('types').ActionResult} */
/** @type {import('@sveltejs/kit').ActionResult} */
let result;

@@ -114,3 +190,3 @@

cache: 'no-store',
body: data,
body: form_data,
signal: controller.signal

@@ -128,5 +204,11 @@ });

action,
data,
form,
update: (opts) => fallback_callback({ action, result, reset: opts?.reset }),
formData: form_data,
formElement: form_element,
update: (opts) =>
fallback_callback({
action,
result,
reset: opts?.reset,
invalidateAll: opts?.invalidateAll
}),
// @ts-expect-error generic constraints stuff we don't care about

@@ -138,3 +220,3 @@ result

// @ts-expect-error
HTMLFormElement.prototype.addEventListener.call(form, 'submit', handle_submit);
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit);

@@ -144,5 +226,5 @@ return {

// @ts-expect-error
HTMLFormElement.prototype.removeEventListener.call(form, 'submit', handle_submit);
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit);
}
};
}

@@ -1,22 +0,142 @@

import { BROWSER } from 'esm-env';
import { client } from '../client/singletons.js';
import { client_method } from '../client/singletons.js';
/**
* @param {string} name
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
* This is generally discouraged, since it breaks user expectations.
* @returns {void}
*/
function guard(name) {
return () => {
throw new Error(`Cannot call ${name}(...) on the server`);
};
}
export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scroll_handling');
export const disableScrollHandling = BROWSER
? client.disable_scroll_handling
: guard('disableScrollHandling');
export const goto = BROWSER ? client.goto : guard('goto');
export const invalidate = BROWSER ? client.invalidate : guard('invalidate');
export const invalidateAll = BROWSER ? client.invalidateAll : guard('invalidateAll');
export const preloadData = BROWSER ? client.preload_data : guard('preloadData');
export const preloadCode = BROWSER ? client.preload_code : guard('preloadCode');
export const beforeNavigate = BROWSER ? client.before_navigate : () => {};
export const afterNavigate = BROWSER ? client.after_navigate : () => {};
/**
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
* For external URLs, use `window.location = url` instead of calling `goto(url)`.
*
* @type {(url: string | URL, opts?: { replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; state?: App.PageState }) => Promise<void>}
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
* @param {Object} [opts] Options related to the navigation
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
* @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
* @param {App.PageState} [opts.state] An optional object that will be available on the `$page.state` store
* @returns {Promise<void>}
*/
export const goto = /* @__PURE__ */ client_method('goto');
/**
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
*
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
*
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
*
* ```ts
* // Example: Match '/path' regardless of the query parameters
* import { invalidate } from '$app/navigation';
*
* invalidate((url) => url.pathname === '/path');
* ```
* @type {(url: string | URL | ((url: URL) => boolean)) => Promise<void>}
* @param {string | URL | ((url: URL) => boolean)} url The invalidated URL
* @returns {Promise<void>}
*/
export const invalidate = /* @__PURE__ */ client_method('invalidate');
/**
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
* @type {() => Promise<void>}
* @returns {Promise<void>}
*/
export const invalidateAll = /* @__PURE__ */ client_method('invalidate_all');
/**
* Programmatically preloads the given page, which means
* 1. ensuring that the code for the page is loaded, and
* 2. calling the page's load function with the appropriate options.
*
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`.
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
*
* @type {(href: string) => Promise<Record<string, any>>}
* @param {string} href Page to preload
* @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>}
*/
export const preloadData = /* @__PURE__ */ client_method('preload_data');
/**
* Programmatically imports the code for routes that haven't yet been fetched.
* Typically, you might call this to speed up subsequent navigation.
*
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
*
* Unlike `preloadData`, this won't call `load` functions.
* Returns a Promise that resolves when the modules have been imported.
*
* @type {(url: string) => Promise<void>}
* @param {string} url
* @returns {Promise<void>}
*/
export const preloadCode = /* @__PURE__ */ client_method('preload_code');
/**
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
*
* Calling `cancel()` will prevent the navigation from completing. If `navigation.type === 'leave'` — meaning the user is navigating away from the app (or closing the tab) — calling `cancel` will trigger the native browser unload confirmation dialog. In this case, the navigation may or may not be cancelled depending on the user's response.
*
* When a navigation isn't to a SvelteKit-owned route (and therefore controlled by SvelteKit's client-side router), `navigation.to.route.id` will be `null`.
*
* If the navigation will (if not cancelled) cause the document to unload — in other words `'leave'` navigations and `'link'` navigations where `navigation.to.route === null` — `navigation.willUnload` is `true`.
*
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
* @type {(callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void}
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback
* @returns {void}
*/
export const beforeNavigate = /* @__PURE__ */ client_method('before_navigate');
/**
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations.
*
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
*
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated.
*
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
* @type {(callback: (navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<(() => void) | void>) => void}
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => void} callback
* @returns {void}
*/
export const onNavigate = /* @__PURE__ */ client_method('on_navigate');
/**
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
*
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
* @type {(callback: (navigation: import('@sveltejs/kit').AfterNavigate) => void) => void}
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback
* @returns {void}
*/
export const afterNavigate = /* @__PURE__ */ client_method('after_navigate');
/**
* Programmatically create a new history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
*
* @type {(url: string | URL, state: App.PageState) => void}
* @param {string | URL} url
* @param {App.PageState} state
* @returns {void}
*/
export const pushState = /* @__PURE__ */ client_method('push_state');
/**
* Programmatically replace the current history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
*
* @type {(url: string | URL, state: App.PageState) => void}
* @param {string | URL} url
* @param {App.PageState} state
* @returns {void}
*/
export const replaceState = /* @__PURE__ */ client_method('replace_state');

@@ -1,1 +0,23 @@

export { base, assets } from '../shared.js';
export { base, assets } from '__sveltekit/paths';
import { base } from '__sveltekit/paths';
import { resolve_route } from '../../utils/routing.js';
/**
* Populate a route ID with params to resolve a pathname.
* @example
* ```js
* resolveRoute(
* `/blog/[slug]/[...somethingElse]`,
* {
* slug: 'hello-world',
* somethingElse: 'something/else'
* }
* ); // `/blog/hello-world/something/else`
* ```
* @param {string} id
* @param {Record<string, string | undefined>} params
* @returns {string}
*/
export function resolveRoute(id, params) {
return base + resolve_route(id, params);
}

@@ -6,3 +6,4 @@ import { getContext } from 'svelte';

/**
* @type {import('$app/stores').getStores}
* A function that returns all of the contextual stores. On the server, this must be called during component initialization.
* Only use this if you need to defer store subscription until after the component has mounted, for some reason.
*/

@@ -13,8 +14,11 @@ export const getStores = () => {

return {
/** @type {typeof page} */
page: {
subscribe: stores.page.subscribe
},
/** @type {typeof navigating} */
navigating: {
subscribe: stores.navigating.subscribe
},
/** @type {typeof updated} */
updated: stores.updated

@@ -24,7 +28,12 @@ };

/** @type {typeof import('$app/stores').page} */
/**
* A readable store whose value contains page data.
*
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
*
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Page>}
*/
export const page = {
/** @param {(value: any) => void} fn */
subscribe(fn) {
const store = getStores().page;
const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
return store.subscribe(fn);

@@ -34,6 +43,13 @@ }

/** @type {typeof import('$app/stores').navigating} */
/**
* A readable store.
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
* When navigating finishes, its value reverts to `null`.
*
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Navigation | null>}
*/
export const navigating = {
subscribe(fn) {
const store = getStores().navigating;
const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
return store.subscribe(fn);

@@ -43,6 +59,11 @@ }

/** @type {typeof import('$app/stores').updated} */
/**
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
*
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
* @type {import('svelte/store').Readable<boolean> & { check(): Promise<boolean> }}
*/
export const updated = {
subscribe(fn) {
const store = getStores().updated;
const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;

@@ -58,6 +79,22 @@ if (browser) {

browser
? `Cannot check updated store before subscribing`
: `Can only check updated store in browser`
? 'Cannot check updated store before subscribing'
: 'Can only check updated store in browser'
);
}
};
/**
* @template {keyof ReturnType<typeof getStores>} Name
* @param {Name} name
* @returns {ReturnType<typeof getStores>[Name]}
*/
function get_store(name) {
try {
return getStores()[name];
} catch (e) {
throw new Error(
`Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.` +
'For more information, see https://kit.svelte.dev/docs/state-management#avoid-shared-state-on-the-server'
);
}
}

@@ -0,4 +1,9 @@

export const SNAPSHOT_KEY = 'sveltekit:snapshot';
export const SCROLL_KEY = 'sveltekit:scroll';
export const INDEX_KEY = 'sveltekit:index';
export const STATES_KEY = 'sveltekit:states';
export const PAGE_URL_KEY = 'sveltekit:pageurl';
export const HISTORY_INDEX = 'sveltekit:history';
export const NAVIGATION_INDEX = 'sveltekit:navigation';
export const PRELOAD_PRIORITIES = /** @type {const} */ ({

@@ -9,3 +14,4 @@ tap: 1,

eager: 4,
off: -1
off: -1,
false: -1
});

@@ -26,2 +26,6 @@ import { DEV } from 'esm-env';

/**
* @param {RequestInfo | URL} input
* @param {RequestInit & Record<string, any> | undefined} init
*/
window.fetch = (input, init) => {

@@ -33,15 +37,17 @@ // Check if fetch was called via load_node. the lock method only checks if it was called at the

const stack_array = /** @type {string} */ (new Error().stack).split('\n');
// We need to do some Firefox-specific cutoff because it (impressively) maintains the stack
// across events and for example traces a `fetch` call triggered from a button back
// to the creation of the event listener and the element creation itself,
// We need to do a cutoff because Safari and Firefox maintain the stack
// across events and for example traces a `fetch` call triggered from a button
// back to the creation of the event listener and the element creation itself,
// where at some point client.js will show up, leading to false positives.
const firefox_cutoff = stack_array.findIndex((a) => a.includes('*listen@'));
const stack = stack_array
.slice(0, firefox_cutoff !== -1 ? firefox_cutoff : undefined)
.join('\n');
const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
const stack = stack_array.slice(0, cutoff + 2).join('\n');
const heuristic = can_inspect_stack_trace
const in_load_heuristic = can_inspect_stack_trace
? stack.includes('src/runtime/client/client.js')
: loading;
if (heuristic) {
// This flag is set in initial_fetch and subsequent_fetch
const used_kit_fetch = init?.__sveltekit_fetch__;
if (in_load_heuristic && !used_kit_fetch) {
console.warn(

@@ -75,2 +81,18 @@ `Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/load#making-fetch-requests`

/**
* @param {string} text
* @returns {ArrayBufferLike}
*/
function b64_decode(text) {
const d = atob(text);
const u8 = new Uint8Array(d.length);
for (let i = 0; i < d.length; i++) {
u8[i] = d.charCodeAt(i);
}
return u8.buffer;
}
/**
* Should be called on the initial run of load functions that hydrate the page.

@@ -86,6 +108,12 @@ * Saves any requests with cache-control max-age to the cache.

if (script?.textContent) {
const { body, ...init } = JSON.parse(script.textContent);
let { body, ...init } = JSON.parse(script.textContent);
const ttl = script.getAttribute('data-ttl');
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
const b64 = script.getAttribute('data-b64');
if (b64 !== null) {
// Can't use native_fetch('data:...;base64,${body}')
// csp can block the request
body = b64_decode(body);
}

@@ -95,3 +123,3 @@ return Promise.resolve(new Response(body, init));

return native_fetch(resource, opts);
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts);
}

@@ -122,6 +150,21 @@

return native_fetch(resolved, opts);
return DEV ? dev_fetch(resolved, opts) : window.fetch(resolved, opts);
}
/**
* @param {RequestInfo | URL} resource
* @param {RequestInit & Record<string, any> | undefined} opts
*/
function dev_fetch(resource, opts) {
const patched_opts = { ...opts };
// This assigns the __sveltekit_fetch__ flag and makes it non-enumerable
Object.defineProperty(patched_opts, '__sveltekit_fetch__', {
value: true,
writable: true,
configurable: true
});
return window.fetch(resource, patched_opts);
}
/**
* Build the cache key for a given request

@@ -136,4 +179,15 @@ * @param {URL | RequestInfo} resource

if (opts?.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
selector += `[data-hash="${hash(opts.body)}"]`;
if (opts?.headers || opts?.body) {
/** @type {import('types').StrictBody[]} */
const values = [];
if (opts.headers) {
values.push([...new Headers(opts.headers)].join(','));
}
if (opts.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
values.push(opts.body);
}
selector += `[data-hash="${hash(...values)}"]`;
}

@@ -140,0 +194,0 @@

import { exec, parse_route_id } from '../../utils/routing.js';
/**
* @param {import('types').CSRPageNodeLoader[]} nodes
* @param {number[]} server_loads
* @param {typeof import('__CLIENT__/manifest.js').dictionary} dictionary
* @param {Record<string, (param: string) => boolean>} matchers
* @param {import('./types.js').SvelteKitApp} app
* @returns {import('types').CSRRoute[]}
*/
export function parse(nodes, server_loads, dictionary, matchers) {
export function parse({ nodes, server_loads, dictionary, matchers }) {
const layouts_with_server_load = new Set(server_loads);

@@ -12,0 +9,0 @@

import { writable } from 'svelte/store';
import { create_updated_store, notifiable_store } from './utils.js';
import { BROWSER } from 'esm-env';
/** @type {import('./types').Client} */
/** @type {import('./types.js').Client} */
export let client;

@@ -9,3 +10,3 @@

* @param {{
* client: import('./types').Client;
* client: import('./types.js').Client;
* }} opts

@@ -17,7 +18,44 @@ */

/**
* @template {keyof typeof client} T
* @param {T} key
* @returns {typeof client[T]}
*/
export function client_method(key) {
if (!BROWSER) {
if (
key === 'before_navigate' ||
key === 'after_navigate' ||
key === 'on_navigate' ||
key === 'push_state' ||
key === 'replace_state'
) {
// @ts-expect-error doesn't recognize that both keys here return void so expects a async function
return () => {};
} else {
/** @type {Record<string, string>} */
const name_lookup = {
disable_scroll_handling: 'disableScrollHandling',
preload_data: 'preloadData',
preload_code: 'preloadCode',
invalidate_all: 'invalidateAll'
};
return () => {
throw new Error(`Cannot call ${name_lookup[key] ?? key}(...) on the server`);
};
}
} else {
// @ts-expect-error
return (...args) => client[key](...args);
}
}
export const stores = {
url: notifiable_store({}),
page: notifiable_store({}),
navigating: writable(/** @type {import('types').Navigation | null} */ (null)),
updated: create_updated_store()
url: /* @__PURE__ */ notifiable_store({}),
page: /* @__PURE__ */ notifiable_store({}),
navigating: /* @__PURE__ */ writable(
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
),
updated: /* @__PURE__ */ create_updated_store()
};
import { DEV } from 'esm-env';
import { create_client } from './client.js';
import { init } from './singletons.js';
import { set_paths, set_version, set_public_env } from '../shared.js';
/**
* @param {{
* env: Record<string, string>;
* hydrate: Parameters<import('./types').Client['_hydrate']>[0];
* paths: {
* assets: string;
* base: string;
* },
* target: HTMLElement;
* version: string;
* }} opts
* @param {import('./types.js').SvelteKitApp} app
* @param {HTMLElement} target
* @param {Parameters<import('./types.js').Client['_hydrate']>[0]} [hydrate]
*/
export async function start({ env, hydrate, paths, target, version }) {
set_public_env(env);
set_paths(paths);
set_version(version);
export async function start(app, target, hydrate) {
if (DEV && target === document.body) {
console.warn(
`Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>`
'Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>'
);
}
const client = create_client({
target,
base: paths.base
});
const client = create_client(app, target);

@@ -34,0 +19,0 @@ init({ client });

@@ -1,5 +0,6 @@

import { applyAction } from '$app/forms';
import { applyAction } from '../app/forms.js';
import {
afterNavigate,
beforeNavigate,
onNavigate,
goto,

@@ -9,7 +10,37 @@ invalidate,

preloadCode,
preloadData
} from '$app/navigation';
preloadData,
pushState,
replaceState
} from '../app/navigation.js';
import { SvelteComponent } from 'svelte';
import { CSRPageNode, CSRPageNodeLoader, CSRRoute, Page, TrailingSlash, Uses } from 'types';
import { ClientHooks, CSRPageNode, CSRPageNodeLoader, CSRRoute, TrailingSlash, Uses } from 'types';
import { Page, ParamMatcher } from '@sveltejs/kit';
export interface SvelteKitApp {
/**
* A list of all the error/layout/page nodes used in the app
*/
nodes: CSRPageNodeLoader[];
/**
* A list of all layout node ids that have a server load function.
* Pages are not present because it's shorter to encode it on the leaf itself.
*/
server_loads: number[];
/**
* A map of `[routeId: string]: [leaf, layouts, errors]` tuples, which
* is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`.
* If the leaf number is negative, it means it does use a server load function and the complement is the node index.
* The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply.
*/
dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
matchers: Record<string, ParamMatcher>;
hooks: ClientHooks;
root: typeof SvelteComponent;
}
export interface Client {

@@ -19,8 +50,11 @@ // public API, exposed via $app/navigation

before_navigate: typeof beforeNavigate;
on_navigate: typeof onNavigate;
disable_scroll_handling(): void;
goto: typeof goto;
invalidate: typeof invalidate;
invalidateAll: typeof invalidateAll;
invalidate_all: typeof invalidateAll;
preload_code: typeof preloadCode;
preload_data: typeof preloadData;
push_state: typeof pushState;
replace_state: typeof replaceState;
apply_action: typeof applyAction;

@@ -65,4 +99,5 @@

props: {
components: Array<typeof SvelteComponent>;
page?: Page;
constructors: Array<typeof SvelteComponent>;
components?: Array<SvelteComponent>;
page: Page;
form?: Record<string, any> | null;

@@ -69,0 +104,0 @@ [key: `data_${number}`]: Record<string, any>;

import { BROWSER, DEV } from 'esm-env';
import { writable } from 'svelte/store';
import { assets, version } from '../shared.js';
import { assets } from '__sveltekit/paths';
import { version } from '__sveltekit/environment';
import { PRELOAD_PRIORITIES } from './constants.js';

@@ -8,12 +9,16 @@

/** @param {HTMLDocument} doc */
export function get_base_uri(doc) {
let baseURI = doc.baseURI;
export const origin = BROWSER ? location.origin : '';
/** @param {string | URL} url */
export function resolve_url(url) {
if (url instanceof URL) return url;
let baseURI = document.baseURI;
if (!baseURI) {
const baseTags = doc.getElementsByTagName('base');
baseURI = baseTags.length ? baseTags[0].href : doc.URL;
const baseTags = document.getElementsByTagName('base');
baseURI = baseTags.length ? baseTags[0].href : document.URL;
}
return baseURI;
return new URL(url, baseURI);
}

@@ -33,6 +38,8 @@

const valid_link_options = /** @type {const} */ ({
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'tap', 'hover'],
noscroll: ['', 'off'],
reload: ['', 'off']
'preload-code': ['', 'off', 'false', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'false', 'tap', 'hover'],
keepfocus: ['', 'true', 'off', 'false'],
noscroll: ['', 'true', 'off', 'false'],
reload: ['', 'true', 'off', 'false'],
replacestate: ['', 'true', 'off', 'false']
});

@@ -135,6 +142,7 @@

is_external_url(url, base) ||
(a.getAttribute('rel') || '').split(/\s+/).includes('external') ||
a.hasAttribute('download');
(a.getAttribute('rel') || '').split(/\s+/).includes('external');
return { url, external, target };
const download = url?.origin === origin && a.hasAttribute('download');
return { url, external, target, download };
}

@@ -146,2 +154,5 @@

export function get_router_options(element) {
/** @type {ValidLinkOptions<'keepfocus'> | null} */
let keepfocus = null;
/** @type {ValidLinkOptions<'noscroll'> | null} */

@@ -159,2 +170,5 @@ let noscroll = null;

/** @type {ValidLinkOptions<'replacestate'> | null} */
let replace_state = null;
/** @type {Element} */

@@ -166,4 +180,6 @@ let el = element;

if (preload_data === null) preload_data = link_option(el, 'preload-data');
if (keepfocus === null) keepfocus = link_option(el, 'keepfocus');
if (noscroll === null) noscroll = link_option(el, 'noscroll');
if (reload === null) reload = link_option(el, 'reload');
if (replace_state === null) replace_state = link_option(el, 'replacestate');

@@ -173,7 +189,23 @@ el = /** @type {Element} */ (parent_element(el));

/** @param {string | null} value */
function get_option_state(value) {
switch (value) {
case '':
case 'true':
return true;
case 'off':
case 'false':
return false;
default:
return undefined;
}
}
return {
preload_code: levels[preload_code ?? 'off'],
preload_data: levels[preload_data ?? 'off'],
noscroll: noscroll === 'off' ? false : noscroll === '' ? true : null,
reload: reload === 'off' ? false : reload === '' ? true : null
keepfocus: get_option_state(keepfocus),
noscroll: get_option_state(noscroll),
reload: get_option_state(reload),
replace_state: get_option_state(replace_state)
};

@@ -215,2 +247,9 @@ }

if (DEV || !BROWSER) {
return {
subscribe,
check: async () => false
};
}
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;

@@ -223,4 +262,2 @@

async function check() {
if (DEV || !BROWSER) return false;
clearTimeout(timeout);

@@ -230,10 +267,14 @@

const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
headers: {
pragma: 'no-cache',
'cache-control': 'no-cache'
try {
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
headers: {
pragma: 'no-cache',
'cache-control': 'no-cache'
}
});
if (!res.ok) {
return false;
}
});
if (res.ok) {
const data = await res.json();

@@ -248,4 +289,4 @@ const updated = data.version !== version;

return updated;
} else {
throw new Error(`Version check failed: ${res.status}`);
} catch {
return false;
}

@@ -267,3 +308,3 @@ }

export function is_external_url(url, base) {
return url.origin !== location.origin || !url.pathname.startsWith(base);
return url.origin !== origin || !url.pathname.startsWith(base);
}

@@ -1,2 +0,2 @@

export let HttpError = class HttpError {
export class HttpError {
/**

@@ -20,5 +20,5 @@ * @param {number} status

}
};
}
export let Redirect = class Redirect {
export class Redirect {
/**

@@ -32,11 +32,29 @@ * @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status

}
};
}
/**
* An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404.
* `SvelteKitError` goes through `handleError`.
* @extends Error
*/
export class SvelteKitError extends Error {
/**
* @param {number} status
* @param {string} text
* @param {string} message
*/
constructor(status, text, message) {
super(message);
this.status = status;
this.text = text;
}
}
/**
* @template {Record<string, unknown> | undefined} [T=undefined]
*/
export let ActionFailure = class ActionFailure {
export class ActionFailure {
/**
* @param {number} status
* @param {T} [data]
* @param {T} data
*/

@@ -47,3 +65,3 @@ constructor(status, data) {

}
};
}

@@ -60,8 +78,14 @@ /**

* Redirect: typeof Redirect;
* SvelteKitError: typeof SvelteKitError;
* }} implementations
*/
export function replace_implementations(implementations) {
ActionFailure = implementations.ActionFailure;
HttpError = implementations.HttpError;
Redirect = implementations.Redirect;
// @ts-expect-error
ActionFailure = implementations.ActionFailure; // eslint-disable-line no-class-assign
// @ts-expect-error
HttpError = implementations.HttpError; // eslint-disable-line no-class-assign
// @ts-expect-error
Redirect = implementations.Redirect; // eslint-disable-line no-class-assign
// @ts-expect-error
SvelteKitError = implementations.SvelteKitError; // eslint-disable-line no-class-assign
}

@@ -1,1 +0,1 @@

export { private_env as env } from '../../shared.js';
export { private_env as env } from '../../shared-server.js';

@@ -1,1 +0,1 @@

export { public_env as env } from '../../shared.js';
export { public_env as env } from '../../shared-server.js';
/**
* Hash using djb2
* @param {import('types').StrictBody} value
* @param {import('types').StrictBody[]} values
*/
export function hash(value) {
export function hash(...values) {
let hash = 5381;
if (typeof value === 'string') {
let i = value.length;
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
} else if (ArrayBuffer.isView(value)) {
const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
let i = buffer.length;
while (i) hash = (hash * 33) ^ buffer[--i];
} else {
throw new TypeError('value must be a string or TypedArray');
for (const value of values) {
if (typeof value === 'string') {
let i = value.length;
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
} else if (ArrayBuffer.isView(value)) {
const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
let i = buffer.length;
while (i) hash = (hash * 33) ^ buffer[--i];
} else {
throw new TypeError('value must be a string or TypedArray');
}
}

@@ -18,0 +20,0 @@

declare module '__SERVER__/internal.js' {
export const options: import('types').SSROptions;
export const get_hooks: () => Promise<{
handle?: import('types').Handle;
handleError?: import('types').HandleServerError;
handleFetch?: import('types').HandleFetch;
handle?: import('@sveltejs/kit').Handle;
handleError?: import('@sveltejs/kit').HandleServerError;
handleFetch?: import('@sveltejs/kit').HandleFetch;
}>;
}
import { parse, serialize } from 'cookie';
import { normalize_path } from '../../utils/url.js';
import { add_data_suffix, normalize_path, resolve } from '../../utils/url.js';

@@ -17,2 +17,10 @@ /**

// TODO 3.0 remove this check
/** @param {import('./page/types.js').Cookie['options']} options */
function validate_options(options) {
if (options?.path === undefined) {
throw new Error('You must specify a `path` when setting, deleting or serializing cookies');
}
}
/**

@@ -28,26 +36,4 @@ * @param {Request} request

const normalized_url = normalize_path(url.pathname, trailing_slash);
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
if (__SVELTEKIT_DEV__) {
// TODO this could theoretically be wrong if the cookie was set unencoded?
const initial_decoded_cookies = parse(header, { decode: decodeURIComponent });
// Remove all cookies that no longer exist according to the request
for (const name of Object.keys(cookie_paths)) {
cookie_paths[name] = new Set(
[...cookie_paths[name]].filter(
(path) => !path_matches(normalized_url, path) || name in initial_decoded_cookies
)
);
}
// Add all new cookies we might not have seen before
for (const name in initial_decoded_cookies) {
cookie_paths[name] = cookie_paths[name] ?? new Set();
if (![...cookie_paths[name]].some((path) => path_matches(normalized_url, path))) {
cookie_paths[name].add(default_path);
}
}
}
/** @type {Record<string, import('./page/types').Cookie>} */
/** @type {Record<string, import('./page/types.js').Cookie>} */
const new_cookies = {};

@@ -62,7 +48,7 @@

/** @type {import('types').Cookies} */
/** @type {import('@sveltejs/kit').Cookies} */
const cookies = {
// The JSDoc param annotations appearing below for get, set and delete
// are necessary to expose the `cookie` library types to
// typescript users. `@type {import('types').Cookies}` above is not
// typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
// sufficient to do so.

@@ -88,57 +74,49 @@

if (!__SVELTEKIT_DEV__ || cookie) {
return cookie;
// in development, if the cookie was set during this session with `cookies.set`,
// but at a different path, warn the user. (ignore cookies from request headers,
// since we don't know which path they were set at)
if (__SVELTEKIT_DEV__ && !cookie) {
const paths = Array.from(cookie_paths[name] ?? []).filter((path) => {
// we only care about paths that are _more_ specific than the current path
return path_matches(path, url.pathname) && path !== url.pathname;
});
if (paths.length > 0) {
console.warn(
// prettier-ignore
`'${name}' cookie does not exist for ${url.pathname}, but was previously set at ${conjoin([...paths])}. Did you mean to set its 'path' to '/' instead?`
);
}
}
const paths = new Set([...(cookie_paths[name] ?? [])]);
if (c) {
paths.add(c.options.path ?? default_path);
}
if (paths.size > 0) {
console.warn(
// prettier-ignore
`Cookie with name '${name}' was not found at path '${url.pathname}', but a cookie with that name exists at these paths: '${[...paths].join("', '")}'. Did you mean to set its 'path' to '/' instead?`
);
}
return cookie;
},
/**
* @param {string} name
* @param {string} value
* @param {import('cookie').CookieSerializeOptions} opts
* @param {import('cookie').CookieParseOptions} opts
*/
set(name, value, opts = {}) {
let path = opts.path ?? default_path;
getAll(opts) {
const decoder = opts?.decode || decodeURIComponent;
const cookies = parse(header, { decode: decoder });
new_cookies[name] = {
name,
value,
options: {
...defaults,
...opts,
path
for (const c of Object.values(new_cookies)) {
if (
domain_matches(url.hostname, c.options.domain) &&
path_matches(url.pathname, c.options.path)
) {
cookies[c.name] = c.value;
}
};
}
if (__SVELTEKIT_DEV__) {
const serialized = serialize(name, value, new_cookies[name].options);
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
}
return Object.entries(cookies).map(([name, value]) => ({ name, value }));
},
cookie_paths[name] = cookie_paths[name] ?? new Set();
if (!value) {
if (!cookie_paths[name].has(path) && cookie_paths[name].size > 0) {
const paths = `'${Array.from(cookie_paths[name]).join("', '")}'`;
console.warn(
`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at these paths: ${paths}.`
);
}
cookie_paths[name].delete(path);
} else {
// We could also emit a warning here if the cookie already exists at a different path,
// but that's more likely a false positive because it's valid to set the same name at different paths
cookie_paths[name].add(path);
}
}
/**
* @param {string} name
* @param {string} value
* @param {import('./page/types.js').Cookie['options']} options
*/
set(name, value, options) {
validate_options(options);
set_internal(name, value, { ...defaults, ...options });
},

@@ -148,9 +126,7 @@

* @param {string} name
* @param {import('cookie').CookieSerializeOptions} opts
* @param {import('./page/types.js').Cookie['options']} options
*/
delete(name, opts = {}) {
cookies.set(name, '', {
...opts,
maxAge: 0
});
delete(name, options) {
validate_options(options);
cookies.set(name, '', { ...options, maxAge: 0 });
},

@@ -161,9 +137,14 @@

* @param {string} value
* @param {import('cookie').CookieSerializeOptions} opts
* @param {import('./page/types.js').Cookie['options']} options
*/
serialize(name, value, opts) {
return serialize(name, value, {
...defaults,
...opts
});
serialize(name, value, options) {
validate_options(options);
let path = options.path;
if (!options.domain || options.domain === url.hostname) {
path = resolve(normalized_url, path);
}
return serialize(name, value, { ...defaults, ...options, path });
}

@@ -206,3 +187,33 @@ };

return { cookies, new_cookies, get_cookie_header };
/**
* @param {string} name
* @param {string} value
* @param {import('./page/types.js').Cookie['options']} options
*/
function set_internal(name, value, options) {
let path = options.path;
if (!options.domain || options.domain === url.hostname) {
path = resolve(normalized_url, path);
}
new_cookies[name] = { name, value, options: { ...options, path } };
if (__SVELTEKIT_DEV__) {
const serialized = serialize(name, value, new_cookies[name].options);
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
}
cookie_paths[name] ??= new Set();
if (!value) {
cookie_paths[name].delete(path);
} else {
cookie_paths[name].add(path);
}
}
}
return { cookies, new_cookies, get_cookie_header, set_internal };
}

@@ -238,3 +249,3 @@

* @param {Headers} headers
* @param {import('./page/types').Cookie[]} cookies
* @param {import('./page/types.js').Cookie[]} cookies
*/

@@ -245,3 +256,19 @@ export function add_cookies_to_headers(headers, cookies) {

headers.append('set-cookie', serialize(name, value, options));
// special case — for routes ending with .html, the route data lives in a sibling
// `.html__data.json` file rather than a child `/__data.json` file, which means
// we need to duplicate the cookie
if (options.path.endsWith('.html')) {
const path = add_data_suffix(options.path);
headers.append('set-cookie', serialize(name, value, { ...options, path }));
}
}
}
/**
* @param {string[]} array
*/
function conjoin(array) {
if (array.length <= 2) return array.join(' and ');
return `${array.slice(0, -1).join(', ')} and ${array.at(-1)}`;
}

@@ -1,16 +0,18 @@

import { HttpError, Redirect } from '../../control.js';
import { HttpError, SvelteKitError, Redirect } from '../../control.js';
import { normalize_error } from '../../../utils/error.js';
import { once } from '../../../utils/functions.js';
import { load_server_data } from '../page/load_data.js';
import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
import { clarify_devalue_error, handle_error_and_jsonify, stringify_uses } from '../utils.js';
import { normalize_path } from '../../../utils/url.js';
import { text } from '../../../exports/index.js';
import * as devalue from 'devalue';
import { create_async_iterator } from '../../../utils/streaming.js';
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
const encoder = new TextEncoder();
/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSRRoute} route
* @param {import('types').SSROptions} options
* @param {import('types').SSRManifest} manifest
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state

@@ -37,4 +39,2 @@ * @param {boolean[] | undefined} invalidated_data_nodes

state.initiator = route;
try {

@@ -62,2 +62,3 @@ const node_ids = [...route.page.layouts, route.page.leaf];

const node = n == undefined ? n : await manifest._.nodes[n]();
// load this. for the child, return as is. for the final result, stream things
return load_server_data({

@@ -113,3 +114,6 @@ event: new_event,

error: await handle_error_and_jsonify(event, options, error),
status: error instanceof HttpError ? error.status : undefined
status:
error instanceof HttpError || error instanceof SvelteKitError
? error.status
: undefined
});

@@ -120,11 +124,31 @@ })

try {
const stubs = nodes.slice(0, length).map(serialize_data_node);
const { data, chunks } = get_data_json(event, options, nodes);
const json = `{"type":"data","nodes":[${stubs.join(',')}]}`;
return json_response(json);
} catch (e) {
const error = /** @type {any} */ (e);
return json_response(JSON.stringify(clarify_devalue_error(event, error)), 500);
if (!chunks) {
// use a normal JSON response where possible, so we get `content-length`
// and can use browser JSON devtools for easier inspecting
return json_response(data);
}
return new Response(
new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode(data));
for await (const chunk of chunks) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
type: 'bytes'
}),
{
headers: {
// we use a proprietary content type to prevent buffering.
// the `text` prefix makes it inspectable
'content-type': 'text/sveltekit-data',
'cache-control': 'private, no-store'
}
}
);
} catch (e) {

@@ -136,4 +160,3 @@ const error = normalize_error(e);

} else {
// TODO make it clearer that this was an unexpected error
return json_response(JSON.stringify(await handle_error_and_jsonify(event, options, error)));
return json_response(await handle_error_and_jsonify(event, options, error), 500);
}

@@ -144,7 +167,7 @@ }

/**
* @param {string} json
* @param {Record<string, any> | string} json
* @param {number} [status]
*/
function json_response(json, status = 200) {
return text(json, {
return text(typeof json === 'string' ? json : JSON.stringify(json), {
status,

@@ -162,8 +185,88 @@ headers: {

export function redirect_json_response(redirect) {
return json_response(
JSON.stringify({
type: 'redirect',
location: redirect.location
})
);
return json_response({
type: 'redirect',
location: redirect.location
});
}
/**
* If the serialized data contains promises, `chunks` will be an
* async iterable containing their resolutions
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {Array<import('types').ServerDataSkippedNode | import('types').ServerDataNode | import('types').ServerErrorNode | null | undefined>} nodes
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
*/
export function get_data_json(event, options, nodes) {
let promise_id = 1;
let count = 0;
const { iterator, push, done } = create_async_iterator();
const reducers = {
/** @param {any} thing */
Promise: (thing) => {
if (typeof thing?.then === 'function') {
const id = promise_id++;
count += 1;
/** @type {'data' | 'error'} */
let key = 'data';
thing
.catch(
/** @param {any} e */ async (e) => {
key = 'error';
return handle_error_and_jsonify(event, options, /** @type {any} */ (e));
}
)
.then(
/** @param {any} value */
async (value) => {
let str;
try {
str = devalue.stringify(value, reducers);
} catch (e) {
const error = await handle_error_and_jsonify(
event,
options,
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
);
key = 'error';
str = devalue.stringify(error, reducers);
}
count -= 1;
push(`{"type":"chunk","id":${id},"${key}":${str}}\n`);
if (count === 0) done();
}
);
return id;
}
}
};
try {
const strings = nodes.map((node) => {
if (!node) return 'null';
if (node.type === 'error' || node.type === 'skip') {
return JSON.stringify(node);
}
return `{"type":"data","data":${devalue.stringify(node.data, reducers)},${stringify_uses(
node
)}${node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''}}`;
});
return {
data: `{"type":"data","nodes":[${strings.join(',')}]}\n`,
chunks: count > 0 ? iterator : null
};
} catch (e) {
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
}
}

@@ -0,1 +1,2 @@

import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
import { negotiate } from '../../utils/http.js';

@@ -6,3 +7,3 @@ import { Redirect } from '../control.js';

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSREndpoint} mod

@@ -15,5 +16,5 @@ * @param {import('types').SSRState} state

let handler = mod[method];
let handler = mod[method] || mod.fallback;
if (!handler && method === 'HEAD') {
if (method === 'HEAD' && mod.GET && !mod.HEAD) {
handler = mod.GET;

@@ -33,3 +34,3 @@ }

if (state.prerendering && !prerender) {
if (state.initiator) {
if (state.depth > 0) {
// if request came from a prerendered page, bail

@@ -45,4 +46,4 @@ throw new Error(`${event.route.id} is not prerenderable`);

try {
const response = await handler(
/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
let response = await handler(
/** @type {import('@sveltejs/kit').RequestEvent<Record<string, any>>} */ (event)
);

@@ -57,2 +58,9 @@

if (state.prerendering) {
// the returned Response might have immutable Headers
// so we should clone them before trying to mutate them
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: new Headers(response.headers)
});
response.headers.set('x-sveltekit-prerender', String(prerender));

@@ -75,3 +83,3 @@ }

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
*/

@@ -81,4 +89,4 @@ export function is_endpoint_request(event) {

if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
// These methods exist exclusively for endpoints
// These methods exist exclusively for endpoints
if (ENDPOINT_METHODS.includes(method) && !PAGE_METHODS.includes(method)) {
return true;

@@ -85,0 +93,0 @@ }

import * as set_cookie_parser from 'set-cookie-parser';
import { respond } from './respond.js';
import * as paths from '../shared.js';
import * as paths from '__sveltekit/paths';
/**
* @param {{
* event: import('types').RequestEvent;
* event: import('@sveltejs/kit').RequestEvent;
* options: import('types').SSROptions;
* manifest: import('types').SSRManifest;
* manifest: import('@sveltejs/kit').SSRManifest;
* state: import('types').SSRState;
* get_cookie_header: (url: URL, header: string | null) => string;
* set_internal: (name: string, value: string, opts: import('./page/types.js').Cookie['options']) => void;
* }} opts
* @returns {typeof fetch}
*/
export function create_fetch({ event, options, manifest, state, get_cookie_header }) {
return async (info, init) => {
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) {
/**
* @type {typeof fetch}
*/
const server_fetch = async (info, init) => {
const original_request = normalize_fetch_input(info, init, event.url);
const request_body = init?.body;
// some runtimes (e.g. Cloudflare) error if you access `request.mode`,

@@ -27,3 +29,3 @@ // annoyingly, so we need to read the value from the `init` object instead

return await options.hooks.handleFetch({
return options.hooks.handleFetch({
event,

@@ -56,3 +58,3 @@ request: original_request,

if (url.origin !== event.url.origin) {
// allow cookie passthrough for "same-origin"
// Allow cookie passthrough for "credentials: same-origin" and "credentials: include"
// if SvelteKit is serving my.domain.com:

@@ -65,2 +67,4 @@ // - domain.com WILL NOT receive cookies

// leading dot prevents mydomain.com matching domain.com
// Do not forward other cookies for "credentials: include" because we don't know
// which cookie belongs to which domain (browser does not pass this info)
if (`.${url.hostname}`.endsWith(`.${event.url.hostname}`) && credentials !== 'omit') {

@@ -74,5 +78,2 @@ const cookie = get_cookie_header(url, request.headers.get('cookie'));

/** @type {Response} */
let response;
// handle fetch requests for static assets. e.g. prebaked data, etc.

@@ -118,11 +119,2 @@ // we need to support everything the browser's fetch supports

if (request_body && typeof request_body !== 'string' && !ArrayBuffer.isView(request_body)) {
// TODO is this still necessary? we just bail out below
// per https://developer.mozilla.org/en-US/docs/Web/API/Request/Request, this can be a
// Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object.
// non-string bodies are irksome to deal with, but luckily aren't particularly useful
// in this context anyway, so we take the easy route and ban them
throw new Error('Request body must be a string or TypedArray');
}
if (!request.headers.has('accept')) {

@@ -139,3 +131,7 @@ request.headers.set('accept', '*/*');

response = await respond(request, options, manifest, state);
/** @type {Response} */
const response = await respond(request, options, manifest, {
...state,
depth: state.depth + 1
});

@@ -147,8 +143,9 @@ const set_cookie = response.headers.get('set-cookie');

const path = options.path ?? (url.pathname.split('/').slice(0, -1).join('/') || '/');
// options.sameSite is string, something more specific is required - type cast is safe
event.cookies.set(
name,
value,
/** @type {import('cookie').CookieSerializeOptions} */ (options)
);
set_internal(name, value, {
path,
.../** @type {import('cookie').CookieSerializeOptions} */ (options)
});
}

@@ -161,2 +158,11 @@ }

};
// Don't make this function `async`! Otherwise, the user has to `catch` promises they use for streaming responses or else
// it will be an unhandled rejection. Instead, we add a `.catch(() => {})` ourselves below to this from happening.
return (input, init) => {
// See docs in fetch.js for why we need to do this
const response = server_fetch(input, init);
response.catch(() => {});
return response;
};
}

@@ -163,0 +169,0 @@

import { respond } from './respond.js';
import { set_private_env, set_public_env } from '../shared.js';
import { set_private_env, set_public_env, set_safe_public_env } from '../shared-server.js';
import { options, get_hooks } from '__SERVER__/internal.js';
import { DEV } from 'esm-env';
import { filter_private_env, filter_public_env } from '../../utils/env.js';
import { prerendering } from '__sveltekit/environment';
/** @type {ProxyHandler<{ type: 'public' | 'private' }>} */
const prerender_env_handler = {
get({ type }, prop) {
throw new Error(
`Cannot read values from $env/dynamic/${type} while prerendering (attempted to read env.${prop.toString()}). Use $env/static/${type} instead`
);
}
};
export class Server {

@@ -9,6 +21,6 @@ /** @type {import('types').SSROptions} */

/** @type {import('types').SSRManifest} */
/** @type {import('@sveltejs/kit').SSRManifest} */
#manifest;
/** @param {import('types').SSRManifest} manifest */
/** @param {import('@sveltejs/kit').SSRManifest} manifest */
constructor(manifest) {

@@ -29,20 +41,42 @@ /** @type {import('types').SSROptions} */

// been done already.
const entries = Object.entries(env);
const prefix = this.#options.env_public_prefix;
const prv = Object.fromEntries(entries.filter(([k]) => !k.startsWith(prefix)));
const pub = Object.fromEntries(entries.filter(([k]) => k.startsWith(prefix)));
// set env, in case it's used in initialisation
const prefixes = {
public_prefix: this.#options.env_public_prefix,
private_prefix: this.#options.env_private_prefix
};
set_private_env(prv);
set_public_env(pub);
const private_env = filter_private_env(env, prefixes);
const public_env = filter_public_env(env, prefixes);
set_private_env(
prerendering ? new Proxy({ type: 'private' }, prerender_env_handler) : private_env
);
set_public_env(
prerendering ? new Proxy({ type: 'public' }, prerender_env_handler) : public_env
);
set_safe_public_env(public_env);
if (!this.#options.hooks) {
const module = await get_hooks();
try {
const module = await get_hooks();
this.#options.hooks = {
handle: module.handle || (({ event, resolve }) => resolve(event)),
// @ts-expect-error
handleError: module.handleError || (({ error }) => console.error(error?.stack)),
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
};
this.#options.hooks = {
handle: module.handle || (({ event, resolve }) => resolve(event)),
handleError: module.handleError || (({ error }) => console.error(error)),
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
};
} catch (error) {
if (DEV) {
this.#options.hooks = {
handle: () => {
throw error;
},
handleError: ({ error }) => console.error(error),
handleFetch: ({ request, fetch }) => fetch(request)
};
} else {
throw error;
}
}
}

@@ -56,11 +90,8 @@ }

async respond(request, options) {
// TODO this should probably have been removed for 1.0 — i think we can get rid of it?
if (!(request instanceof Request)) {
throw new Error(
'The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details'
);
}
return respond(request, this.#options, this.#manifest, options);
return respond(request, this.#options, this.#manifest, {
...options,
error: false,
depth: 0
});
}
}
import * as devalue from 'devalue';
import { error, json } from '../../../exports/index.js';
import { normalize_error } from '../../../utils/error.js';
import { json } from '../../../exports/index.js';
import { get_status, normalize_error } from '../../../utils/error.js';
import { is_form_content_type, negotiate } from '../../../utils/http.js';
import { HttpError, Redirect, ActionFailure } from '../../control.js';
import { HttpError, Redirect, ActionFailure, SvelteKitError } from '../../control.js';
import { handle_error_and_jsonify } from '../utils.js';
/** @param {import('types').RequestEvent} event */
/** @param {import('@sveltejs/kit').RequestEvent} event */
export function is_action_json_request(event) {

@@ -19,3 +19,3 @@ const accept = negotiate(event.request.headers.get('accept') ?? '*/*', [

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options

@@ -28,4 +28,7 @@ * @param {import('types').SSRNode['server'] | undefined} server

if (!actions) {
// TODO should this be a different error altogether?
const no_actions_error = error(405, 'POST method not allowed. No actions exist for this page');
const no_actions_error = new SvelteKitError(
405,
'Method Not Allowed',
'POST method not allowed. No actions exist for this page'
);
return action_json(

@@ -77,7 +80,3 @@ {

if (err instanceof Redirect) {
return action_json({
type: 'redirect',
status: err.status,
location: err.location
});
return action_json_redirect(err);
}

@@ -91,3 +90,3 @@

{
status: err instanceof HttpError ? err.status : 500
status: get_status(err)
}

@@ -103,3 +102,3 @@ );

return error instanceof ActionFailure
? new Error(`Cannot "throw fail()". Use "return fail()"`)
? new Error('Cannot "throw fail()". Use "return fail()"')
: error;

@@ -109,3 +108,14 @@ }

/**
* @param {import('types').ActionResult} data
* @param {import('@sveltejs/kit').Redirect} redirect
*/
export function action_json_redirect(redirect) {
return action_json({
type: 'redirect',
status: redirect.status,
location: redirect.location
});
}
/**
* @param {import('@sveltejs/kit').ActionResult} data
* @param {ResponseInit} [init]

@@ -118,3 +128,3 @@ */

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
*/

@@ -126,5 +136,5 @@ export function is_action_request(event) {

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSRNode['server'] | undefined} server
* @returns {Promise<import('types').ActionResult>}
* @returns {Promise<import('@sveltejs/kit').ActionResult>}
*/

@@ -143,3 +153,7 @@ export async function handle_action_request(event, server) {

type: 'error',
error: error(405, 'POST method not allowed. No actions exist for this page')
error: new SvelteKitError(
405,
'Method Not Allowed',
'POST method not allowed. No actions exist for this page'
)
};

@@ -190,3 +204,3 @@ }

/**
* @param {import('types').Actions} actions
* @param {import('@sveltejs/kit').Actions} actions
*/

@@ -196,3 +210,3 @@ function check_named_default_separate(actions) {

throw new Error(
`When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions`
'When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions'
);

@@ -203,5 +217,5 @@ }

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
* @throws {Redirect | ActionFailure | HttpError | Error}
* @throws {Redirect | HttpError | SvelteKitError | Error}
*/

@@ -224,8 +238,12 @@ async function call_action(event, actions) {

if (!action) {
throw new Error(`No action with name '${name}' found`);
throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`);
}
if (!is_form_content_type(event.request)) {
throw new Error(
`Actions expect form-encoded data (received ${event.request.headers.get('content-type')}`
throw new SvelteKitError(
415,
'Unsupported Media Type',
`Form actions expect form-encoded data — received ${event.request.headers.get(
'content-type'
)}`
);

@@ -240,9 +258,7 @@ }

if (data instanceof Redirect) {
throw new Error(`Cannot \`return redirect(...)\` — use \`throw redirect(...)\` instead`);
throw new Error('Cannot `return redirect(...)` — use `redirect(...)` instead');
}
if (data instanceof HttpError) {
throw new Error(
`Cannot \`return error(...)\` — use \`throw error(...)\` or \`return fail(...)\` instead`
);
throw new Error('Cannot `return error(...)` — use `error(...)` or `return fail(...)` instead');
}

@@ -249,0 +265,0 @@ }

@@ -181,4 +181,9 @@ import { escape_html_attr } from '../../../utils/escape.js';

get_meta() {
const content = escape_html_attr(this.get_header(true));
return `<meta http-equiv="content-security-policy" content=${content}>`;
const content = this.get_header(true);
if (!content) {
return;
}
return `<meta http-equiv="content-security-policy" content=${escape_html_attr(content)}>`;
}

@@ -222,4 +227,4 @@ }

/**
* @param {import('./types').CspConfig} config
* @param {import('./types').CspOpts} opts
* @param {import('./types.js').CspConfig} config
* @param {import('./types.js').CspOpts} opts
*/

@@ -226,0 +231,0 @@ constructor({ mode, directives, reportOnly }, { prerender }) {

import { text } from '../../../exports/index.js';
import { compact } from '../../../utils/array.js';
import { normalize_error } from '../../../utils/error.js';
import { get_status, normalize_error } from '../../../utils/error.js';
import { add_data_suffix } from '../../../utils/url.js';
import { HttpError, Redirect } from '../../control.js';
import { Redirect } from '../../control.js';
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
import {
get_option,
redirect_response,
static_error_page,
handle_error_and_jsonify,
serialize_data_node
} from '../utils.js';
import {
handle_action_json_request,

@@ -22,9 +16,15 @@ handle_action_request,

import { respond_with_error } from './respond_with_error.js';
import { get_option } from '../../../utils/options.js';
import { get_data_json } from '../data/index.js';
/**
* @param {import('types').RequestEvent} event
* @param {import('types').SSRRoute} route
* The maximum request depth permitted before assuming we're stuck in an infinite loop
*/
const MAX_DEPTH = 10;
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').PageNodeIndexes} page
* @param {import('types').SSROptions} options
* @param {import('types').SSRManifest} manifest
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state

@@ -34,12 +34,10 @@ * @param {import('types').RequiredResolveOptions} resolve_opts

*/
export async function render_page(event, route, page, options, manifest, state, resolve_opts) {
if (state.initiator === route) {
export async function render_page(event, page, options, manifest, state, resolve_opts) {
if (state.depth > MAX_DEPTH) {
// infinite request cycle detected
return text(`Not found: ${event.url.pathname}`, {
status: 404
status: 404 // TODO in some cases this should be 500. not sure how to differentiate
});
}
state.initiator = route;
if (is_action_json_request(event)) {

@@ -61,3 +59,3 @@ const node = await manifest._.nodes[page.leaf]();

/** @type {import('types').ActionResult | undefined} */
/** @type {import('@sveltejs/kit').ActionResult | undefined} */
let action_result = undefined;

@@ -73,4 +71,3 @@

if (action_result?.type === 'error') {
const error = action_result.error;
status = error instanceof HttpError ? error.status : 500;
status = get_status(action_result.error);
}

@@ -82,3 +79,3 @@ if (action_result?.type === 'failure') {

const should_prerender_data = nodes.some((node) => node?.server);
const should_prerender_data = nodes.some((node) => node?.server?.load);
const data_pathname = add_data_suffix(event.url.pathname);

@@ -88,5 +85,4 @@

// SvelteKit will erroneously believe that the path has been prerendered,
// causing functions to be omitted from the manifesst generated later
const should_prerender = get_option(nodes, 'prerender');
// causing functions to be omitted from the manifest generated later
const should_prerender = get_option(nodes, 'prerender') ?? false;
if (should_prerender) {

@@ -98,26 +94,2 @@ const mod = leaf_node.server;

} else if (state.prerendering) {
// Try to render the shell when ssr is false and prerendering not explicitly disabled.
// People can opt out of this behavior by explicitly setting prerender to false.
if (
should_prerender !== false &&
get_option(nodes, 'ssr') === false &&
!leaf_node.server?.actions
) {
return await render_response({
branch: [],
fetched: [],
page_config: {
ssr: false,
csr: get_option(nodes, 'csr') ?? true
},
status,
error: null,
event,
options,
manifest,
state,
resolve_opts
});
}
// if the page isn't marked as prerenderable, then bail out at this point

@@ -133,6 +105,9 @@ return new Response(undefined, {

/** @type {import('./types').Fetched[]} */
/** @type {import('./types.js').Fetched[]} */
const fetched = [];
if (get_option(nodes, 'ssr') === false) {
// renders an empty 'shell' page if SSR is turned off and if there is
// no server data to prerender. As a result, the load functions and rendering
// only occur client-side.
if (get_option(nodes, 'ssr') === false && !(state.prerendering && should_prerender_data)) {
return await render_response({

@@ -156,3 +131,3 @@ branch: [],

/** @type {Array<import('./types.js').Loaded | null>} */
let branch = [];
const branch = [];

@@ -260,3 +235,3 @@ /** @type {Error | null} */

const status = err instanceof HttpError ? err.status : 500;
const status = get_status(err);
const error = await handle_error_and_jsonify(event, options, err);

@@ -303,12 +278,23 @@

if (state.prerendering && should_prerender_data) {
const body = `{"type":"data","nodes":[${branch
.map((node) => serialize_data_node(node?.server_data))
.join(',')}]}`;
// ndjson format
let { data, chunks } = get_data_json(
event,
options,
branch.map((node) => node?.server_data)
);
if (chunks) {
for await (const chunk of chunks) {
data += chunk;
}
}
state.prerendering.dependencies.set(data_pathname, {
response: text(body),
body
response: text(data),
body: data
});
}
const ssr = get_option(nodes, 'ssr') ?? true;
return await render_response({

@@ -322,7 +308,7 @@ event,

csr: get_option(nodes, 'csr') ?? true,
ssr: true
ssr
},
status,
error: null,
branch: compact(branch),
branch: ssr === false ? [] : compact(branch),
action_result,

@@ -332,3 +318,3 @@ fetched

} catch (e) {
// if we end up here, it means the data loaded successfull
// if we end up here, it means the data loaded successfully
// but the page failed to render, or that a prerendering error occurred

@@ -335,0 +321,0 @@ return await respond_with_error({

@@ -0,3 +1,4 @@

import { DEV } from 'esm-env';
import { disable_search, make_trackable } from '../../../utils/url.js';
import { unwrap_promises } from '../../../utils/promises.js';
import { validate_depends } from '../../shared.js';

@@ -7,3 +8,3 @@ /**

* @param {{
* event: import('types').RequestEvent;
* event: import('@sveltejs/kit').RequestEvent;
* state: import('types').SSRState;

@@ -18,2 +19,5 @@ * node: import('types').SSRNode | undefined;

let done = false;
let is_tracking = true;
const uses = {

@@ -24,9 +28,32 @@ dependencies: new Set(),

route: false,
url: false
url: false,
search_params: new Set()
};
const url = make_trackable(event.url, () => {
uses.url = true;
});
const url = make_trackable(
event.url,
() => {
if (DEV && done && !uses.url) {
console.warn(
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
);
}
if (is_tracking) {
uses.url = true;
}
},
(param) => {
if (DEV && done && !uses.search_params.has(param)) {
console.warn(
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
);
}
if (is_tracking) {
uses.search_params.add(param);
}
}
);
if (state.prerendering) {

@@ -40,4 +67,10 @@ disable_search(url);

const url = new URL(info instanceof Request ? info.url : info, event.url);
uses.dependencies.add(url.href);
if (DEV && done && !uses.dependencies.has(url.href)) {
console.warn(
`${node.server_id}: Calling \`event.fetch(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
);
}
// Note: server fetches are not added to uses.depends due to security concerns
return event.fetch(info, init);

@@ -49,2 +82,13 @@ },

const { href } = new URL(dep, event.url);
if (DEV) {
validate_depends(node.server_id, dep);
if (done && !uses.dependencies.has(href)) {
console.warn(
`${node.server_id}: Calling \`depends(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
);
}
}
uses.dependencies.add(href);

@@ -55,3 +99,13 @@ }

get: (target, key) => {
uses.params.add(key);
if (DEV && done && typeof key === 'string' && !uses.params.has(key)) {
console.warn(
`${node.server_id}: Accessing \`params.${String(
key
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the param changes`
);
}
if (is_tracking) {
uses.params.add(key);
}
return target[/** @type {string} */ (key)];

@@ -61,22 +115,49 @@ }

parent: async () => {
uses.parent = true;
if (DEV && done && !uses.parent) {
console.warn(
`${node.server_id}: Calling \`parent(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when parent data changes`
);
}
if (is_tracking) {
uses.parent = true;
}
return parent();
},
route: {
get id() {
uses.route = true;
return event.route.id;
route: new Proxy(event.route, {
get: (target, key) => {
if (DEV && done && typeof key === 'string' && !uses.route) {
console.warn(
`${node.server_id}: Accessing \`route.${String(
key
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the route changes`
);
}
if (is_tracking) {
uses.route = true;
}
return target[/** @type {'id'} */ (key)];
}
},
url
}),
url,
untrack(fn) {
is_tracking = false;
try {
return fn();
} finally {
is_tracking = true;
}
}
});
const data = result ? await unwrap_promises(result) : null;
if (__SVELTEKIT_DEV__) {
validate_load_response(data, /** @type {string} */ (event.route.id));
validate_load_response(result, node.server_id);
}
done = true;
return {
type: 'data',
data,
data: result ?? null,
uses,

@@ -90,4 +171,4 @@ slash: node.server.trailingSlash

* @param {{
* event: import('types').RequestEvent;
* fetched: import('./types').Fetched[];
* event: import('@sveltejs/kit').RequestEvent;
* fetched: import('./types.js').Fetched[];
* node: import('types').SSRNode | undefined;

@@ -100,3 +181,3 @@ * parent: () => Promise<Record<string, any>>;

* }} opts
* @returns {Promise<Record<string, any> | null>}
* @returns {Promise<Record<string, any | Promise<any>> | null>}
*/

@@ -127,16 +208,39 @@ export async function load_data({

depends: () => {},
parent
parent,
untrack: (fn) => fn()
});
const data = result ? await unwrap_promises(result) : null;
validate_load_response(data, /** @type {string} */ (event.route.id));
return data;
if (__SVELTEKIT_DEV__) {
validate_load_response(result, node.universal_id);
}
return result ?? null;
}
/**
* @param {Pick<import('types').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
* @param {import("types").SSRState} state
* @param {import("./types").Fetched[]} fetched
* @param {ArrayBuffer} buffer
* @returns {string}
*/
function b64_encode(buffer) {
if (globalThis.Buffer) {
return Buffer.from(buffer).toString('base64');
}
const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0;
// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's
return btoa(
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode(
new Uint16Array(new Uint8Array(buffer))
)
);
}
/**
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
* @param {import('types').SSRState} state
* @param {import('./types.js').Fetched[]} fetched
* @param {boolean} csr
* @param {Pick<Required<import("types").ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts
* @param {Pick<Required<import('@sveltejs/kit').ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts
* @returns {typeof fetch}
*/

@@ -148,4 +252,10 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts) {

*/
return async (input, init) => {
const universal_fetch = async (input, init) => {
const cloned_body = input instanceof Request && input.body ? input.clone().body : null;
const cloned_headers =
input instanceof Request && [...input.headers].length
? new Headers(input.headers)
: init?.headers;
let response = await event.fetch(input, init);

@@ -187,33 +297,29 @@

get(response, key, _receiver) {
async function text() {
const body = await response.text();
if (!body || typeof body === 'string') {
const status_number = Number(response.status);
if (isNaN(status_number)) {
throw new Error(
`response.status is not a number. value: "${
response.status
}" type: ${typeof response.status}`
);
}
fetched.push({
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
method: event.request.method,
request_body: /** @type {string | ArrayBufferView | undefined} */ (
input instanceof Request && cloned_body
? await stream_to_string(cloned_body)
: init?.body
),
response_body: body,
response: response
});
/**
* @param {string} body
* @param {boolean} is_b64
*/
async function push_fetched(body, is_b64) {
const status_number = Number(response.status);
if (isNaN(status_number)) {
throw new Error(
`response.status is not a number. value: "${
response.status
}" type: ${typeof response.status}`
);
}
if (dependency) {
dependency.body = body;
}
return body;
fetched.push({
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
method: event.request.method,
request_body: /** @type {string | ArrayBufferView | undefined} */ (
input instanceof Request && cloned_body
? await stream_to_string(cloned_body)
: init?.body
),
request_headers: cloned_headers,
response_body: body,
response,
is_b64
});
}

@@ -229,4 +335,5 @@

// TODO should buffer be inlined into the page (albeit base64'd)?
// any conditions in which it shouldn't be?
if (buffer instanceof ArrayBuffer) {
await push_fetched(b64_encode(buffer), true);
}

@@ -237,2 +344,16 @@ return buffer;

async function text() {
const body = await response.text();
if (!body || typeof body === 'string') {
await push_fetched(body, false);
}
if (dependency) {
dependency.body = body;
}
return body;
}
if (key === 'text') {

@@ -273,2 +394,11 @@ return text;

};
// Don't make this function `async`! Otherwise, the user has to `catch` promises they use for streaming responses or else
// it will be an unhandled rejection. Instead, we add a `.catch(() => {})` ourselves below to this from happening.
return (input, init) => {
// See docs in fetch.js for why we need to do this
const response = universal_fetch(input, init);
response.catch(() => {});
return response;
};
}

@@ -295,15 +425,15 @@

* @param {any} data
* @param {string} [routeId]
* @param {string} [id]
*/
function validate_load_response(data, routeId) {
function validate_load_response(data, id) {
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
throw new Error(
`a load function related to route '${routeId}' returned ${
`a load function in ${id} returned ${
typeof data !== 'object'
? `a ${typeof data}`
: data instanceof Response
? 'a Response object'
: Array.isArray(data)
? 'an array'
: 'a non-plain object'
? 'a Response object'
: Array.isArray(data)
? 'an array'
: 'a non-plain object'
}, but must return a plain object at the top level (i.e. \`return {...}\`)`

@@ -310,0 +440,0 @@ );

import * as devalue from 'devalue';
import { readable, writable } from 'svelte/store';
import { DEV } from 'esm-env';
import * as paths from '__sveltekit/paths';
import { hash } from '../../hash.js';

@@ -9,5 +10,8 @@ import { serialize_data } from './serialize_data.js';

import { uneval_action_response } from './actions.js';
import { clarify_devalue_error } from '../utils.js';
import { assets, base, version, public_env } from '../../shared.js';
import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
import { public_env, safe_public_env } from '../../shared-server.js';
import { text } from '../../../exports/index.js';
import { create_async_iterator } from '../../../utils/streaming.js';
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
import { SCHEME } from '../../../utils/url.js';

@@ -21,9 +25,11 @@ // TODO rename this function/module

const encoder = new TextEncoder();
/**
* Creates the HTML response.
* @param {{
* branch: Array<import('./types').Loaded>;
* fetched: Array<import('./types').Fetched>;
* branch: Array<import('./types.js').Loaded>;
* fetched: Array<import('./types.js').Fetched>;
* options: import('types').SSROptions;
* manifest: import('types').SSRManifest;
* manifest: import('@sveltejs/kit').SSRManifest;
* state: import('types').SSRState;

@@ -33,5 +39,5 @@ * page_config: { ssr: boolean; csr: boolean };

* error: App.Error | null;
* event: import('types').RequestEvent;
* event: import('@sveltejs/kit').RequestEvent;
* resolve_opts: import('types').RequiredResolveOptions;
* action_result?: import('types').ActionResult;
* action_result?: import('@sveltejs/kit').ActionResult;
* }} opts

@@ -62,7 +68,7 @@ */

const { entry } = manifest._;
const { client } = manifest._;
const stylesheets = new Set(entry.stylesheets);
const modulepreloads = new Set(entry.imports);
const fonts = new Set(manifest._.entry.fonts);
const modulepreloads = new Set(client.imports);
const stylesheets = new Set(client.stylesheets);
const fonts = new Set(client.fonts);

@@ -83,2 +89,28 @@ /** @type {Set<string>} */

/** @type {string} */
let base = paths.base;
/** @type {string} */
let assets = paths.assets;
/**
* An expression that will evaluate in the client to determine the resolved base path.
* We use a relative path when possible to support IPFS, the internet archive, etc.
*/
let base_expression = s(paths.base);
// if appropriate, use relative paths for greater portability
if (paths.relative && !state.prerendering?.fallback) {
const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2);
base = segments.map(() => '..').join('/') || '.';
// resolve e.g. '../..' against current location, then remove trailing slash
base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;
if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) {
assets = base;
}
}
if (page_config.ssr) {

@@ -97,3 +129,3 @@ if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {

},
components: await Promise.all(branch.map(({ node }) => node.component())),
constructors: await Promise.all(branch.map(({ node }) => node.component())),
form: form_value

@@ -118,20 +150,47 @@ };

data,
form: form_value
form: form_value,
state: {}
};
rendered = options.root.render(props);
// use relative paths during rendering, so that the resulting HTML is as
// portable as possible, but reset afterwards
if (paths.relative) paths.override({ base, assets });
for (const { node } of branch) {
if (node.imports) {
node.imports.forEach((url) => modulepreloads.add(url));
}
if (__SVELTEKIT_DEV__) {
const fetch = globalThis.fetch;
let warned = false;
globalThis.fetch = (info, init) => {
if (typeof info === 'string' && !SCHEME.test(info)) {
throw new Error(
`Cannot call \`fetch\` eagerly during server side rendering with relative URL (${info}) — put your \`fetch\` calls inside \`onMount\` or a \`load\` function instead`
);
} else if (!warned) {
console.warn(
'Avoid calling `fetch` eagerly during server side rendering — put your `fetch` calls inside `onMount` or a `load` function instead'
);
warned = true;
}
if (node.stylesheets) {
node.stylesheets.forEach((url) => stylesheets.add(url));
}
return fetch(info, init);
};
if (node.fonts) {
node.fonts.forEach((url) => fonts.add(url));
try {
rendered = options.root.render(props);
} finally {
globalThis.fetch = fetch;
paths.reset();
}
} else {
try {
rendered = options.root.render(props);
} finally {
paths.reset();
}
}
for (const { node } of branch) {
for (const url of node.imports) modulepreloads.add(url);
for (const url of node.stylesheets) stylesheets.add(url);
for (const url of node.fonts) fonts.add(url);
if (node.inline_styles) {

@@ -152,23 +211,2 @@ Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));

const target = hash(body);
/**
* The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
* @type {string}
*/
let resolved_assets;
if (assets) {
// if an asset path is specified, use it
resolved_assets = assets;
} else if (state.prerendering?.fallback) {
// if we're creating a fallback page, asset paths need to be root-relative
resolved_assets = base;
} else {
// otherwise we want asset paths to be relative to the page, so that they
// will work in odd contexts like IPFS, the internet archive, and so on
const segments = event.url.pathname.slice(base.length).split('/').slice(2);
resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
}
/** @param {string} path */

@@ -180,49 +218,7 @@ const prefixed = (path) => {

// all URLs under the base path during development.
return base + path;
return paths.base + path;
}
return `${resolved_assets}/${path}`;
return `${assets}/${path}`;
};
const serialized = { data: '', form: 'null', error: 'null' };
try {
serialized.data = `[${branch
.map(({ server_data }) => {
if (server_data?.type === 'data') {
const data = devalue.uneval(server_data.data);
const uses = [];
if (server_data.uses.dependencies.size > 0) {
uses.push(`dependencies:${s(Array.from(server_data.uses.dependencies))}`);
}
if (server_data.uses.params.size > 0) {
uses.push(`params:${s(Array.from(server_data.uses.params))}`);
}
if (server_data.uses.parent) uses.push(`parent:1`);
if (server_data.uses.route) uses.push(`route:1`);
if (server_data.uses.url) uses.push(`url:1`);
return `{type:"data",data:${data},uses:{${uses.join(',')}}${
server_data.slash ? `,slash:${s(server_data.slash)}` : ''
}}`;
}
return s(server_data);
})
.join(',')}]`;
} catch (e) {
const error = /** @type {any} */ (e);
throw new Error(clarify_devalue_error(event, error));
}
if (form_value) {
serialized.form = uneval_action_response(form_value, /** @type {string} */ (event.route.id));
}
if (error) {
serialized.error = devalue.uneval(error);
}
if (inline_styles.size > 0) {

@@ -242,16 +238,16 @@ const content = Array.from(inline_styles.values()).join('\n');

if (resolve_opts.preload({ type: 'css', path })) {
const attributes = ['rel="stylesheet"'];
const attributes = ['rel="stylesheet"'];
if (inline_styles.has(dep)) {
// don't load stylesheets that are already inlined
// include them in disabled state so that Vite can detect them and doesn't try to add them
attributes.push('disabled', 'media="(max-width: 0)"');
} else {
if (inline_styles.has(dep)) {
// don't load stylesheets that are already inlined
// include them in disabled state so that Vite can detect them and doesn't try to add them
attributes.push('disabled', 'media="(max-width: 0)"');
} else {
if (resolve_opts.preload({ type: 'css', path })) {
const preload_atts = ['rel="preload"', 'as="style"'];
link_header_preloads.add(`<${encodeURI(path)}>; ${preload_atts.join(';')}; nopush`);
}
}
head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
}
head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
}

@@ -276,14 +272,89 @@

const global = __SVELTEKIT_DEV__ ? '__sveltekit_dev' : `__sveltekit_${options.version_hash}`;
const { data, chunks } = get_data(
event,
options,
branch.map((b) => b.server_data),
global
);
if (page_config.ssr && page_config.csr) {
body += `\n\t\t\t${fetched
.map((item) =>
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
)
.join('\n\t\t\t')}`;
}
if (page_config.csr) {
const opts = [
`env: ${s(public_env)}`,
`paths: ${s({ assets, base })}`,
`target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
`version: ${s(version)}`
];
if (client.uses_env_dynamic_public && state.prerendering) {
modulepreloads.add(`${options.app_dir}/env.js`);
}
const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
(path) => resolve_opts.preload({ type: 'js', path })
);
for (const path of included_modulepreloads) {
// see the kit.output.preloadStrategy option for details on why we have multiple options here
link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
if (options.preload_strategy !== 'modulepreload') {
head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
} else if (state.prerendering) {
head += `\n\t\t<link rel="modulepreload" href="${path}">`;
}
}
const blocks = [];
const properties = [
paths.assets && `assets: ${s(paths.assets)}`,
`base: ${base_expression}`,
`env: ${!client.uses_env_dynamic_public || state.prerendering ? null : s(public_env)}`
].filter(Boolean);
if (chunks) {
blocks.push('const deferred = new Map();');
properties.push(`defer: (id) => new Promise((fulfil, reject) => {
deferred.set(id, { fulfil, reject });
})`);
properties.push(`resolve: ({ id, data, error }) => {
const { fulfil, reject } = deferred.get(id);
deferred.delete(id);
if (error) reject(error);
else fulfil(data);
}`);
}
blocks.push(`${global} = {
${properties.join(',\n\t\t\t\t\t\t')}
};`);
const args = ['app', 'element'];
blocks.push('const element = document.currentScript.parentElement;');
if (page_config.ssr) {
const serialized = { form: 'null', error: 'null' };
blocks.push(`const data = ${data};`);
if (form_value) {
serialized.form = uneval_action_response(
form_value,
/** @type {string} */ (event.route.id)
);
}
if (error) {
serialized.error = devalue.uneval(error);
}
const hydrate = [
`node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
`data: ${serialized.data}`,
'data',
`form: ${serialized.form}`,

@@ -301,64 +372,41 @@ `error: ${serialized.error}`

opts.push(`hydrate: {\n\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t')}\n\t\t\t\t}`);
args.push(`{\n\t\t\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t\t\t')}\n\t\t\t\t\t\t}`);
}
// prettier-ignore
const init_app = `
import { start } from ${s(prefixed(entry.file))};
blocks.push(`Promise.all([
import(${s(prefixed(client.start))}),
import(${s(prefixed(client.app))})
]).then(([kit, app]) => {
kit.start(${args.join(', ')});
});`);
start({
${opts.join(',\n\t\t\t\t')}
});
`;
if (options.service_worker) {
const opts = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
for (const dep of modulepreloads) {
const path = prefixed(dep);
// we use an anonymous function instead of an arrow function to support
// older browsers (https://github.com/sveltejs/kit/pull/5417)
blocks.push(`if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
});
}`);
}
if (resolve_opts.preload({ type: 'js', path })) {
link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
if (state.prerendering) {
head += `\n\t\t<link rel="modulepreload" href="${path}">`;
const init_app = `
{
${blocks.join('\n\n\t\t\t\t\t')}
}
}
}
const attributes = ['type="module"', `data-sveltekit-hydrate="${target}"`];
`;
csp.add_script(init_app);
if (csp.script_needs_nonce) {
attributes.push(`nonce="${csp.nonce}"`);
}
body += `\n\t\t<script ${attributes.join(' ')}>${init_app}</script>`;
body += `\n\t\t\t<script${
csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''
}>${init_app}</script>\n\t\t`;
}
if (page_config.ssr && page_config.csr) {
body += `\n\t${fetched
.map((item) =>
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
)
.join('\n\t')}`;
}
const headers = new Headers({
'x-sveltekit-page': 'true',
'content-type': 'text/html'
});
if (options.service_worker) {
const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
// we use an anonymous function instead of an arrow function to support
// older browsers (https://github.com/sveltejs/kit/pull/5417)
const init_service_worker = `
if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
});
}
`;
// always include service worker unless it's turned off explicitly
csp.add_script(init_service_worker);
head += `
<script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
}
if (state.prerendering) {

@@ -380,2 +428,15 @@ // TODO read headers set with setHeaders and convert into http-equiv where possible

}
} else {
const csp_header = csp.csp_provider.get_header();
if (csp_header) {
headers.set('content-security-policy', csp_header);
}
const report_only_header = csp.report_only_provider.get_header();
if (report_only_header) {
headers.set('content-security-policy-report-only', report_only_header);
}
if (link_header_preloads.size) {
headers.set('link', Array.from(link_header_preloads).join(', '));
}
}

@@ -389,5 +450,5 @@

body,
assets: resolved_assets,
assets,
nonce: /** @type {string} */ (csp.nonce),
env: public_env
env: safe_public_env
});

@@ -402,37 +463,122 @@

if (DEV && page_config.csr) {
if (transformed.split('<!--').length < html.split('<!--').length) {
// the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
// https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
console.warn(
"\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
);
if (!chunks) {
headers.set('etag', `"${hash(transformed)}"`);
}
if (DEV) {
if (page_config.csr) {
if (transformed.split('<!--').length < html.split('<!--').length) {
// the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
// https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
console.warn(
"\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
);
}
} else {
if (chunks) {
console.warn(
'\u001B[1m\u001B[31mReturning promises from server `load` functions will only work if `csr === true`\u001B[39m\u001B[22m'
);
}
}
}
const headers = new Headers({
'x-sveltekit-page': 'true',
'content-type': 'text/html',
etag: `"${hash(transformed)}"`
});
return !chunks
? text(transformed, {
status,
headers
})
: new Response(
new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode(transformed + '\n'));
for await (const chunk of chunks) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
if (!state.prerendering) {
const csp_header = csp.csp_provider.get_header();
if (csp_header) {
headers.set('content-security-policy', csp_header);
}
const report_only_header = csp.report_only_provider.get_header();
if (report_only_header) {
headers.set('content-security-policy-report-only', report_only_header);
}
type: 'bytes'
}),
{
headers: {
'content-type': 'text/html'
}
}
);
}
if (link_header_preloads.size) {
headers.set('link', Array.from(link_header_preloads).join(', '));
/**
* If the serialized data contains promises, `chunks` will be an
* async iterable containing their resolutions
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {Array<import('types').ServerDataNode | null>} nodes
* @param {string} global
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
*/
function get_data(event, options, nodes, global) {
let promise_id = 1;
let count = 0;
const { iterator, push, done } = create_async_iterator();
/** @param {any} thing */
function replacer(thing) {
if (typeof thing?.then === 'function') {
const id = promise_id++;
count += 1;
thing
.then(/** @param {any} data */ (data) => ({ data }))
.catch(
/** @param {any} error */ async (error) => ({
error: await handle_error_and_jsonify(event, options, error)
})
)
.then(
/**
* @param {{data: any; error: any}} result
*/
async ({ data, error }) => {
count -= 1;
let str;
try {
str = devalue.uneval({ id, data, error }, replacer);
} catch (e) {
error = await handle_error_and_jsonify(
event,
options,
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
);
data = undefined;
str = devalue.uneval({ id, data, error }, replacer);
}
push(`<script>${global}.resolve(${str})</script>\n`);
if (count === 0) done();
}
);
return `${global}.defer(${id})`;
}
}
return text(transformed, {
status,
headers
});
try {
const strings = nodes.map((node) => {
if (!node) return 'null';
return `{"type":"data","data":${devalue.uneval(node.data, replacer)},${stringify_uses(node)}${
node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
}}`;
});
return {
data: `[${strings.join(',')}]`,
chunks: count > 0 ? iterator : null
};
} catch (e) {
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
}
}
import { render_response } from './render.js';
import { load_data, load_server_data } from './load_data.js';
import {
handle_error_and_jsonify,
get_option,
static_error_page,
redirect_response,
GENERIC_ERROR
} from '../utils.js';
import { HttpError, Redirect } from '../../control.js';
import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
import { get_option } from '../../../utils/options.js';
import { Redirect } from '../../control.js';
import { get_status } from '../../../utils/error.js';

@@ -18,5 +14,5 @@ /**

* @param {{
* event: import('types').RequestEvent;
* event: import('@sveltejs/kit').RequestEvent;
* options: import('types').SSROptions;
* manifest: import('types').SSRManifest;
* manifest: import('@sveltejs/kit').SSRManifest;
* state: import('types').SSRState;

@@ -37,3 +33,8 @@ * status: number;

}) {
/** @type {import('./types').Fetched[]} */
// reroute to the fallback page to prevent an infinite chain of requests.
if (event.request.headers.get('x-sveltekit-error')) {
return static_error_page(options, status, /** @type {Error} */ (error).message);
}
/** @type {import('./types.js').Fetched[]} */
const fetched = [];

@@ -48,3 +49,3 @@

if (ssr) {
state.initiator = GENERIC_ERROR;
state.error = true;

@@ -91,3 +92,3 @@ const server_data_promise = load_server_data({

ssr,
csr: get_option([default_layout], 'csr') ?? true
csr
},

@@ -110,3 +111,3 @@ status,

options,
e instanceof HttpError ? e.status : 500,
get_status(e),
(await handle_error_and_jsonify(event, options, e)).message

@@ -113,0 +114,0 @@ );

@@ -49,3 +49,3 @@ import { escape_html_attr } from '../../../utils/escape.js';

let age = null;
let vary = false;
let varyAny = false;

@@ -58,4 +58,4 @@ for (const [key, value] of fetched.response.headers) {

if (key === 'cache-control') cache_control = value;
if (key === 'age') age = value;
if (key === 'vary') vary = true;
else if (key === 'age') age = value;
else if (key === 'vary' && value.trim() === '*') varyAny = true;
}

@@ -78,11 +78,25 @@

if (fetched.request_body) {
attrs.push(`data-hash=${escape_html_attr(hash(fetched.request_body))}`);
if (fetched.is_b64) {
attrs.push('data-b64');
}
if (fetched.request_headers || fetched.request_body) {
/** @type {import('types').StrictBody[]} */
const values = [];
if (fetched.request_headers) {
values.push([...new Headers(fetched.request_headers)].join(','));
}
if (fetched.request_body) {
values.push(fetched.request_body);
}
attrs.push(`data-hash="${hash(...values)}"`);
}
// Compute the time the response should be cached, taking into account max-age and age.
// Do not cache at all if a vary header is present, as this indicates that the cache is
// likely to get busted. It would also mean we'd have to add more logic to computing the
// selector on the client which results in more code for 99% of people for the 1% who use vary.
if (!prerendering && fetched.method === 'GET' && cache_control && !vary) {
// Do not cache at all if a `Vary: *` header is present, as this indicates that the
// cache is likely to get busted.
if (!prerendering && fetched.method === 'GET' && cache_control && !varyAny) {
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);

@@ -89,0 +103,0 @@ if (match) {

import { CookieSerializeOptions } from 'cookie';
import { SSRNode, CspDirectives } from 'types';
import { SSRNode, CspDirectives, ServerDataNode } from 'types';

@@ -8,4 +8,6 @@ export interface Fetched {

request_body?: string | ArrayBufferView | null;
request_headers?: HeadersInit | undefined;
response_body: string;
response: Response;
is_b64?: boolean;
}

@@ -16,3 +18,3 @@

data: Record<string, any> | null;
server_data: any;
server_data: ServerDataNode | null;
};

@@ -35,3 +37,3 @@

value: string;
options: CookieSerializeOptions;
options: CookieSerializeOptions & { path: string };
}
import { DEV } from 'esm-env';
import { base } from '__sveltekit/paths';
import { is_endpoint_request, render_endpoint } from './endpoint.js';

@@ -7,3 +8,3 @@ import { render_page } from './page/index.js';

import { is_form_content_type } from '../../utils/http.js';
import { GENERIC_ERROR, get_option, handle_fatal_error, redirect_response } from './utils.js';
import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
import {

@@ -18,13 +19,18 @@ decode_pathname,

import { exec } from '../../utils/routing.js';
import { INVALIDATED_PARAM, redirect_json_response, render_data } from './data/index.js';
import { redirect_json_response, render_data } from './data/index.js';
import { add_cookies_to_headers, get_cookies } from './cookie.js';
import { create_fetch } from './fetch.js';
import { Redirect } from '../control.js';
import { HttpError, Redirect, SvelteKitError } from '../control.js';
import {
validate_common_exports,
validate_layout_exports,
validate_layout_server_exports,
validate_page_exports,
validate_page_server_exports,
validate_server_exports
} from '../../utils/exports.js';
import { error, json, text } from '../../exports/index.js';
import * as paths from '../shared.js';
import { get_option } from '../../utils/options.js';
import { json, text } from '../../exports/index.js';
import { action_json_redirect, is_action_json_request } from './page/actions.js';
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js';
import { get_public_env } from './env_module.js';

@@ -42,15 +48,31 @@ /* global __SVELTEKIT_ADAPTER_NAME__ */

/** @type {import('types').Respond} */
const page_methods = new Set(['GET', 'HEAD', 'POST']);
const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
/**
* @param {Request} request
* @param {import('types').SSROptions} options
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state
* @returns {Promise<Response>}
*/
export async function respond(request, options, manifest, state) {
/** URL but stripped from the potential `/__data.json` suffix and its search param */
let url = new URL(request.url);
const url = new URL(request.url);
if (options.csrf_check_origin) {
const forbidden =
request.method === 'POST' &&
request.headers.get('origin') !== url.origin &&
is_form_content_type(request);
is_form_content_type(request) &&
(request.method === 'POST' ||
request.method === 'PUT' ||
request.method === 'PATCH' ||
request.method === 'DELETE') &&
request.headers.get('origin') !== url.origin;
if (forbidden) {
const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
const csrf_error = new HttpError(
403,
`Cross-site ${request.method} form submissions are forbidden`
);
if (request.headers.get('accept') === 'application/json') {

@@ -76,9 +98,13 @@ return json(csrf_error.body, { status: csrf_error.status });

if (paths.base && !state.prerendering?.fallback) {
if (!decoded.startsWith(paths.base)) {
if (base && !state.prerendering?.fallback) {
if (!decoded.startsWith(base)) {
return text('Not found', { status: 404 });
}
decoded = decoded.slice(paths.base.length) || '/';
decoded = decoded.slice(base.length) || '/';
}
if (decoded === `/${options.app_dir}/env.js`) {
return get_public_env(request);
}
const is_data_request = has_data_suffix(decoded);

@@ -89,4 +115,10 @@ /** @type {boolean[] | undefined} */

decoded = strip_data_suffix(decoded) || '/';
url.pathname = strip_data_suffix(url.pathname) || '/';
invalidated_data_nodes = url.searchParams.get(INVALIDATED_PARAM)?.split('_').map(Boolean);
url.pathname =
strip_data_suffix(url.pathname) +
(url.searchParams.get(TRAILING_SLASH_PARAM) === '1' ? '/' : '') || '/';
url.searchParams.delete(TRAILING_SLASH_PARAM);
invalidated_data_nodes = url.searchParams
.get(INVALIDATED_PARAM)
?.split('')
.map((node) => node === '1');
url.searchParams.delete(INVALIDATED_PARAM);

@@ -118,6 +150,6 @@ }

/** @type {Record<string, import('./page/types').Cookie>} */
/** @type {Record<string, import('./page/types.js').Cookie>} */
let cookies_to_add = {};
/** @type {import('types').RequestEvent} */
/** @type {import('@sveltejs/kit').RequestEvent} */
const event = {

@@ -147,3 +179,3 @@ // @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself

throw new Error(
`Use \`event.cookies.set(name, value, options)\` instead of \`event.setHeaders\` to set cookies`
'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies'
);

@@ -162,3 +194,4 @@ } else if (lower in headers) {

url,
isDataRequest: is_data_request
isDataRequest: is_data_request,
isSubRequest: state.depth > 0
};

@@ -175,4 +208,8 @@

// determine whether we need to redirect to add/remove a trailing slash
if (route && !is_data_request) {
if (route.page) {
if (route) {
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
// regardless of the `trailingSlash` route option
if (url.pathname === base || url.pathname === base + '/') {
trailing_slash = 'always';
} else if (route.page) {
const nodes = await Promise.all([

@@ -190,4 +227,7 @@ // we use == here rather than === because [undefined] serializes as "[null]"

if (layout) {
validate_common_exports(layout.server, /** @type {string} */ (layout.server_id));
validate_common_exports(
validate_layout_server_exports(
layout.server,
/** @type {string} */ (layout.server_id)
);
validate_layout_exports(
layout.universal,

@@ -201,3 +241,3 @@ /** @type {string} */ (layout.universal_id)

validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
validate_common_exports(page.universal, /** @type {string} */ (page.universal_id));
validate_page_exports(page.universal, /** @type {string} */ (page.universal_id));
}

@@ -216,19 +256,21 @@ }

const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
if (!is_data_request) {
const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
if (normalized !== url.pathname && !state.prerendering?.fallback) {
return new Response(undefined, {
status: 301,
headers: {
'x-sveltekit-normalize': '1',
location:
// ensure paths starting with '//' are not treated as protocol-relative
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
(url.search === '?' ? '' : url.search)
}
});
if (normalized !== url.pathname && !state.prerendering?.fallback) {
return new Response(undefined, {
status: 308,
headers: {
'x-sveltekit-normalize': '1',
location:
// ensure paths starting with '//' are not treated as protocol-relative
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
(url.search === '?' ? '' : url.search)
}
});
}
}
}
const { cookies, new_cookies, get_cookie_header } = get_cookies(
const { cookies, new_cookies, get_cookie_header, set_internal } = get_cookies(
request,

@@ -241,3 +283,10 @@ url,

event.cookies = cookies;
event.fetch = create_fetch({ event, options, manifest, state, get_cookie_header });
event.fetch = create_fetch({
event,
options,
manifest,
state,
get_cookie_header,
set_internal
});

@@ -315,3 +364,5 @@ if (state.prerendering && !state.prerendering.fallback) disable_search(url);

? redirect_json_response(e)
: redirect_response(e.status, e.location);
: route?.page && is_action_json_request(event)
? action_json_redirect(e)
: redirect_response(e.status, e.location);
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));

@@ -324,5 +375,4 @@ return response;

/**
*
* @param {import('types').RequestEvent} event
* @param {import('types').ResolveOptions} [opts]
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('@sveltejs/kit').ResolveOptions} [opts]
*/

@@ -332,8 +382,2 @@ async function resolve(event, opts) {

if (opts) {
if ('ssr' in opts) {
throw new Error(
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
);
}
resolve_opts = {

@@ -362,2 +406,4 @@ transformPageChunk: opts.transformPageChunk || default_transform,

if (route) {
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
/** @type {Response} */

@@ -379,11 +425,28 @@ let response;

} else if (route.page) {
response = await render_page(
event,
route,
route.page,
options,
manifest,
state,
resolve_opts
);
if (page_methods.has(method)) {
response = await render_page(event, route.page, options, manifest, state, resolve_opts);
} else {
const allowed_methods = new Set(allowed_page_methods);
const node = await manifest._.nodes[route.page.leaf]();
if (node?.server?.actions) {
allowed_methods.add('POST');
}
if (method === 'OPTIONS') {
// This will deny CORS preflight requests implicitly because we don't
// add the required CORS headers to the response.
response = new Response(null, {
status: 204,
headers: {
allow: Array.from(allowed_methods.values()).join(', ')
}
});
} else {
const mod = [...allowed_methods].reduce((acc, curr) => {
acc[curr] = true;
return acc;
}, /** @type {Record<string, any>} */ ({}));
response = method_not_allowed(mod, method);
}
}
} else {

@@ -395,6 +458,33 @@ // a route will always have a page or an endpoint, but TypeScript

// If the route contains a page and an endpoint, we need to add a
// `Vary: Accept` header to the response because of browser caching
if (request.method === 'GET' && route.page && route.endpoint) {
const vary = response.headers
.get('vary')
?.split(',')
?.map((v) => v.trim().toLowerCase());
if (!(vary?.includes('accept') || vary?.includes('*'))) {
// the returned response might have immutable headers,
// so we have to clone them before trying to mutate them
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: new Headers(response.headers)
});
response.headers.append('Vary', 'Accept');
}
}
return response;
}
if (state.initiator === GENERIC_ERROR) {
if (state.error && event.isSubRequest) {
return await fetch(request, {
headers: {
'x-sveltekit-error': 'true'
}
});
}
if (state.error) {
return text('Internal Server Error', {

@@ -406,4 +496,4 @@ status: 500

// if this request came direct from the user, rather than
// via a `fetch` in a `load`, render a 404 page
if (!state.initiator) {
// via our own `fetch`, render a 404 page
if (state.depth === 0) {
return await respond_with_error({

@@ -415,3 +505,3 @@ event,

status: 404,
error: new Error(`Not found: ${event.url.pathname}`),
error: new SvelteKitError(404, 'Not Found', `Not found: ${event.url.pathname}`),
resolve_opts

@@ -418,0 +508,0 @@ });

@@ -1,8 +0,8 @@

import * as devalue from 'devalue';
import { DEV } from 'esm-env';
import { json, text } from '../../exports/index.js';
import { coalesce_to_error } from '../../utils/error.js';
import { coalesce_to_error, get_message, get_status } from '../../utils/error.js';
import { negotiate } from '../../utils/http.js';
import { has_data_suffix } from '../../utils/url.js';
import { HttpError } from '../control.js';
import { fix_stack_trace } from '../shared.js';
import { fix_stack_trace } from '../shared-server.js';
import { ENDPOINT_METHODS } from '../../constants.js';

@@ -21,7 +21,2 @@ /** @param {any} body */

/** @type {import('types').SSRErrorPage} */
export const GENERIC_ERROR = {
id: '__error'
};
/**

@@ -44,10 +39,6 @@ * @param {Partial<Record<import('types').HttpMethod, any>>} mod

export function allowed_methods(mod) {
const allowed = [];
const allowed = ENDPOINT_METHODS.filter((method) => method in mod);
for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
if (method in mod) allowed.push(method);
}
if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
if (mod.GET || mod.HEAD) allowed.push('HEAD');
return allowed;

@@ -57,19 +48,2 @@ }

/**
* @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
* @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value
*
* @param {Array<import('types').SSRNode | undefined>} nodes
* @param {Option} option
*
* @returns {Value | undefined}
*/
export function get_option(nodes, option) {
return nodes.reduce((value, node) => {
return /** @type {any} TypeScript's too dumb to understand this */ (
node?.universal?.[option] ?? node?.server?.[option] ?? value
);
}, /** @type {Value | undefined} */ (undefined));
}
/**
* Return as a response that renders the error.html

@@ -82,3 +56,10 @@ *

export function static_error_page(options, status, message) {
return text(options.templates.error({ status, message }), {
let page = options.templates.error({ status, message });
if (DEV) {
// inject Vite HMR client, for easier debugging
page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
}
return text(page, {
headers: { 'content-type': 'text/html; charset=utf-8' },

@@ -90,3 +71,3 @@ status

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options

@@ -97,6 +78,6 @@ * @param {unknown} error

error = error instanceof HttpError ? error : coalesce_to_error(error);
const status = error instanceof HttpError ? error.status : 500;
const status = get_status(error);
const body = await handle_error_and_jsonify(event, options, error);
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
const type = negotiate(event.request.headers.get('accept') || 'text/html', [

@@ -107,3 +88,3 @@ 'application/json',

if (has_data_suffix(new URL(event.request.url).pathname) || type === 'application/json') {
if (event.isDataRequest || type === 'application/json') {
return json(body, {

@@ -118,3 +99,3 @@ status

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options

@@ -127,21 +108,12 @@ * @param {any} error

return error.body;
} else {
if (__SVELTEKIT_DEV__) {
error = new Proxy(error, {
get: (target, property) => {
if (property === 'stack') {
return fix_stack_trace(target.stack);
}
}
return Reflect.get(target, property, target);
}
});
}
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
fix_stack_trace(error);
}
return (
(await options.hooks.handleError({ error, event })) ?? {
message: event.route.id != null ? 'Internal Error' : 'Not Found'
}
);
}
const status = get_status(error);
const message = get_message(error);
return (await options.hooks.handleError({ error, event, status, message })) ?? { message };
}

@@ -162,3 +134,3 @@

/**
* @param {import('types').RequestEvent} event
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {Error & { path: string }} error

@@ -179,29 +151,39 @@ */

/** @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | import('types').ServerErrorNode | null} node */
export function serialize_data_node(node) {
if (!node) return 'null';
if (node.type === 'error' || node.type === 'skip') {
return JSON.stringify(node);
}
const stringified = devalue.stringify(node.data);
/**
* @param {import('types').ServerDataNode} node
*/
export function stringify_uses(node) {
const uses = [];
if (node.uses.dependencies.size > 0) {
if (node.uses && node.uses.dependencies.size > 0) {
uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
}
if (node.uses.params.size > 0) {
if (node.uses && node.uses.search_params.size > 0) {
uses.push(`"search_params":${JSON.stringify(Array.from(node.uses.search_params))}`);
}
if (node.uses && node.uses.params.size > 0) {
uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
}
if (node.uses.parent) uses.push(`"parent":1`);
if (node.uses.route) uses.push(`"route":1`);
if (node.uses.url) uses.push(`"url":1`);
if (node.uses?.parent) uses.push('"parent":1');
if (node.uses?.route) uses.push('"route":1');
if (node.uses?.url) uses.push('"url":1');
return `{"type":"data","data":${stringified},"uses":{${uses.join(',')}}${
node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
}}`;
return `"uses":{${uses.join(',')}}`;
}
/**
* @param {string} message
* @param {number} offset
*/
export function warn_with_callsite(message, offset = 0) {
if (DEV) {
const stack = fix_stack_trace(new Error()).split('\n');
const line = stack.at(3 + offset);
message += `\n${line}`;
}
console.warn(message);
}

@@ -1,44 +0,16 @@

export let assets = '';
export let base = '';
export let building = false;
export let version = '';
/** @type {Record<string, string>} */
export let private_env = {};
/** @type {Record<string, string>} */
export let public_env = {};
/** @param {string} stack */
export let fix_stack_trace = (stack) => stack;
/** @param {{ base: string, assets: string }} paths */
export function set_paths(paths) {
base = paths.base;
assets = paths.assets || base;
/**
* @param {string} route_id
* @param {string} dep
*/
export function validate_depends(route_id, dep) {
const match = /^(moz-icon|view-source|jar):/.exec(dep);
if (match) {
console.warn(
`${route_id}: Calling \`depends('${dep}')\` will throw an error in Firefox because \`${match[1]}\` is a special URI scheme`
);
}
}
/** @param {boolean} value */
export function set_building(value) {
building = value;
}
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
/** @type {(environment: Record<string, string>) => void} */
export function set_private_env(environment) {
private_env = environment;
}
/** @type {(environment: Record<string, string>) => void} */
export function set_public_env(environment) {
public_env = environment;
}
/** @param {string} value */
export function set_version(value) {
version = value;
}
/** @param {(stack: string) => string} value */
export function set_fix_stack_trace(value) {
fix_stack_trace = value;
}
export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash';

@@ -1,2 +0,2 @@

import { HttpError, Redirect } from '../runtime/control.js';
import { HttpError, SvelteKitError } from '../runtime/control.js';

@@ -21,3 +21,19 @@ /**

export function normalize_error(error) {
return /** @type {Redirect | HttpError | Error} */ (error);
return /** @type {import('../runtime/control.js').Redirect | HttpError | SvelteKitError | Error} */ (
error
);
}
/**
* @param {unknown} error
*/
export function get_status(error) {
return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
}
/**
* @param {unknown} error
*/
export function get_message(error) {
return error instanceof SvelteKitError ? error.text : 'Internal Error';
}
/**
* @param {string[]} expected
* @param {Set<string>} expected
*/
function validator(expected) {
const set = new Set(expected);
/**
* @param {any} module
* @param {string} [route_id]
* @param {string} [file]
*/
function validate(module, route_id) {
function validate(module, file) {
if (!module) return;
for (const key in module) {
if (key[0] !== '_' && !set.has(key)) {
const valid = expected.join(', ');
throw new Error(
`Invalid export '${key}'${
route_id ? ` in ${route_id}` : ''
} (valid exports are ${valid}, or anything with a '_' prefix)`
);
}
if (key[0] === '_' || expected.has(key)) continue; // key is valid in this module
const values = [...expected.values()];
const hint =
hint_for_supported_files(key, file?.slice(file.lastIndexOf('.'))) ??
`valid exports are ${values.join(', ')}, or anything with a '_' prefix`;
throw new Error(`Invalid export '${key}'${file ? ` in ${file}` : ''} (${hint})`);
}

@@ -29,11 +28,38 @@ }

export const validate_common_exports = validator([
'load',
'prerender',
'csr',
'ssr',
'trailingSlash'
]);
/**
* @param {string} key
* @param {string} ext
* @returns {string | void}
*/
function hint_for_supported_files(key, ext = '.js') {
const supported_files = [];
export const validate_page_server_exports = validator([
if (valid_layout_exports.has(key)) {
supported_files.push(`+layout${ext}`);
}
if (valid_page_exports.has(key)) {
supported_files.push(`+page${ext}`);
}
if (valid_layout_server_exports.has(key)) {
supported_files.push(`+layout.server${ext}`);
}
if (valid_page_server_exports.has(key)) {
supported_files.push(`+page.server${ext}`);
}
if (valid_server_exports.has(key)) {
supported_files.push(`+server${ext}`);
}
if (supported_files.length > 0) {
return `'${key}' is a valid export in ${supported_files.slice(0, -1).join(', ')}${
supported_files.length > 1 ? ' or ' : ''
}${supported_files.at(-1)}`;
}
}
const valid_layout_exports = new Set([
'load',

@@ -43,7 +69,9 @@ 'prerender',

'ssr',
'actions',
'trailingSlash'
'trailingSlash',
'config'
]);
export const validate_server_exports = validator([
const valid_page_exports = new Set([...valid_layout_exports, 'entries']);
const valid_layout_server_exports = new Set([...valid_layout_exports]);
const valid_page_server_exports = new Set([...valid_layout_server_exports, 'actions', 'entries']);
const valid_server_exports = new Set([
'GET',

@@ -54,4 +82,15 @@ 'POST',

'DELETE',
'OPTIONS',
'HEAD',
'fallback',
'prerender',
'trailingSlash'
'trailingSlash',
'config',
'entries'
]);
export const validate_layout_exports = validator(valid_layout_exports);
export const validate_page_exports = validator(valid_page_exports);
export const validate_layout_server_exports = validator(valid_layout_server_exports);
export const validate_page_server_exports = validator(valid_page_server_exports);
export const validate_server_exports = validator(valid_server_exports);

@@ -1,3 +0,3 @@

import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';

@@ -4,0 +4,0 @@ /** @param {string} dir */

import { fileURLToPath } from 'node:url';
import child_process from 'node:child_process';
import { Worker, parentPort } from 'node:worker_threads';

@@ -14,19 +14,17 @@ /**

export function forked(module, callback) {
if (process.env.SVELTEKIT_FORK && process.send) {
process.send({ type: 'ready', module });
process.on(
if (process.env.SVELTEKIT_FORK && parentPort) {
parentPort.on(
'message',
/** @param {any} data */ async (data) => {
if (data?.type === 'args' && data.module === module) {
if (process.send) {
process.send({
type: 'result',
module,
payload: await callback(data.payload)
});
}
parentPort?.postMessage({
type: 'result',
module,
payload: await callback(data.payload)
});
}
}
);
parentPort.postMessage({ type: 'ready', module });
}

@@ -38,17 +36,16 @@

*/
const fn = function (opts) {
return function (opts) {
return new Promise((fulfil, reject) => {
const child = child_process.fork(fileURLToPath(module), {
stdio: 'inherit',
const worker = new Worker(fileURLToPath(module), {
env: {
...process.env,
SVELTEKIT_FORK: 'true'
},
serialization: 'advanced'
}
});
child.on(
worker.on(
'message',
/** @param {any} data */ (data) => {
if (data?.type === 'ready' && data.module === module) {
child.send({
worker.postMessage({
type: 'args',

@@ -61,3 +58,3 @@ module,

if (data?.type === 'result' && data.module === module) {
child.kill();
worker.unref();
fulfil(data.payload);

@@ -68,3 +65,3 @@ }

child.on('exit', (code) => {
worker.on('exit', (code) => {
if (code) {

@@ -76,4 +73,2 @@ reject(new Error(`Failed with code ${code}`));

};
return fn;
}

@@ -62,5 +62,5 @@ /**

*/
export function is_content_type(request, ...types) {
function is_content_type(request, ...types) {
const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
return types.includes(type);
return types.includes(type.toLowerCase());
}

@@ -72,3 +72,10 @@

export function is_form_content_type(request) {
return is_content_type(request, 'application/x-www-form-urlencoded', 'multipart/form-data');
// These content types must be protected against CSRF
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
return is_content_type(
request,
'application/x-www-form-urlencoded',
'multipart/form-data',
'text/plain'
);
}

@@ -94,3 +94,3 @@ const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;

.join('')}/?$`
);
);

@@ -100,3 +100,14 @@ return { pattern, params };

const optional_param_regex = /\/\[\[\w+?(?:=\w+)?\]\]/;
/**
* Removes optional params from a route ID.
* @param {string} id
* @returns The route id with optional params removed
*/
export function remove_optional_params(id) {
return id.replace(optional_param_regex, '');
}
/**
* Returns `false` for `(group)` segments

@@ -123,3 +134,3 @@ * @param {string} segment

* @param {import('types').RouteParam[]} params
* @param {Record<string, import('types').ParamMatcher>} matchers
* @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
*/

@@ -131,54 +142,58 @@ export function exec(match, params, matchers) {

const values = match.slice(1);
const values_needing_match = values.filter((value) => value !== undefined);
let buffered = '';
let buffered = 0;
for (let i = 0; i < params.length; i += 1) {
const param = params[i];
let value = values[i];
let value = values[i - buffered];
// in the `[[a=b]]/.../[...rest]` case, if one or more optional parameters
// weren't matched, roll the skipped values into the rest
if (param.chained && param.rest && buffered) {
// in the `[[lang=lang]]/[...rest]` case, if `lang` didn't
// match, we roll it over into the rest value
value = value ? buffered + '/' + value : buffered;
value = values
.slice(i - buffered, i + 1)
.filter((s) => s)
.join('/');
buffered = 0;
}
buffered = '';
// if `value` is undefined, it means this is an optional or rest parameter
if (value === undefined) {
// if `value` is undefined, it means this is
// an optional or rest parameter
if (param.rest) result[param.name] = '';
} else {
if (param.matcher && !matchers[param.matcher](value)) {
// in the `/[[a=b]]/[[c=d]]` case, if the value didn't satisfy the `b` matcher,
// try again with the next segment by shifting values rightwards
if (param.optional && param.chained) {
// @ts-expect-error TypeScript is... wrong
let j = values.indexOf(undefined, i);
continue;
}
if (j === -1) {
// we can't shift values any further, so hang on to this value
// so it can be rolled into a subsequent `[...rest]` param
const next = params[i + 1];
if (next?.rest && next.chained) {
buffered = value;
} else {
return;
}
}
if (!param.matcher || matchers[param.matcher](value)) {
result[param.name] = value;
while (j >= i) {
values[j] = values[j - 1];
j -= 1;
}
// Now that the params match, reset the buffer if the next param isn't the [...rest]
// and the next value is defined, otherwise the buffer will cause us to skip values
const next_param = params[i + 1];
const next_value = values[i + 1];
if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
buffered = 0;
}
continue;
}
// otherwise, if the matcher returns `false`, the route did not match
return;
// There are no more params and no more values, but all non-empty values have been matched
if (
!next_param &&
!next_value &&
Object.keys(result).length === values_needing_match.length
) {
buffered = 0;
}
continue;
}
result[param.name] = value;
// in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
// keep track of the number of skipped optional parameters and continue
if (param.optional && param.chained) {
buffered++;
continue;
}
// otherwise, if the matcher returns `false`, the route did not match
return;
}

@@ -206,1 +221,47 @@

}
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
/**
* Populate a route ID with params to resolve a pathname.
* @example
* ```js
* resolveRoute(
* `/blog/[slug]/[...somethingElse]`,
* {
* slug: 'hello-world',
* somethingElse: 'something/else'
* }
* ); // `/blog/hello-world/something/else`
* ```
* @param {string} id
* @param {Record<string, string | undefined>} params
* @returns {string}
*/
export function resolve_route(id, params) {
const segments = get_route_segments(id);
return (
'/' +
segments
.map((segment) =>
segment.replace(basic_param_pattern, (_, optional, rest, name) => {
const param_value = params[name];
// This is nested so TS correctly narrows the type
if (!param_value) {
if (optional) return '';
if (rest && param_value !== undefined) return '';
throw new Error(`Missing parameter '${name}' in route ${id}`);
}
if (param_value.startsWith('/') || param_value.endsWith('/'))
throw new Error(
`Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
);
return param_value;
})
)
.filter(Boolean)
.join('/')
);
}
import { BROWSER } from 'esm-env';
const absolute = /^([a-z]+:)?\/?\//;
const scheme = /^[a-z]+:/;
/**
* Matches a URI scheme. See https://www.rfc-editor.org/rfc/rfc3986#section-3.1
* @type {RegExp}
*/
export const SCHEME = /^[a-z][a-z\d+\-.]+:/i;
const internal = new URL('sveltekit-internal://');
/**

@@ -11,27 +16,9 @@ * @param {string} base

export function resolve(base, path) {
if (scheme.test(path)) return path;
if (path[0] === '#') return base + path;
// special case
if (path[0] === '/' && path[1] === '/') return path;
const base_match = absolute.exec(base);
const path_match = absolute.exec(path);
let url = new URL(base, internal);
url = new URL(path, url);
if (!base_match) {
throw new Error(`bad base path: "${base}"`);
}
const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/');
const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/');
baseparts.pop();
for (let i = 0; i < pathparts.length; i += 1) {
const part = pathparts[i];
if (part === '.') continue;
else if (part === '..') baseparts.pop();
else baseparts.push(part);
}
const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || '';
return `${prefix}${baseparts.join('/')}`;
return url.protocol === internal.protocol ? url.pathname + url.search + url.hash : url.href;
}

@@ -80,7 +67,35 @@

/**
* The error when a URL is malformed is not very helpful, so we augment it with the URI
* @param {string} uri
*/
export function decode_uri(uri) {
try {
return decodeURI(uri);
} catch (e) {
if (e instanceof Error) {
e.message = `Failed to decode URI: ${uri}\n` + e.message;
}
throw e;
}
}
/**
* Returns everything up to the first `#` in a URL
* @param {{href: string}} url_like
*/
export function strip_hash({ href }) {
return href.split('#')[0];
}
/**
* URL properties that could change during the lifetime of the page,
* which excludes things like `origin`
* @type {Array<keyof URL>}
*/
const tracked_url_properties = ['href', 'pathname', 'search', 'searchParams', 'toString', 'toJSON'];
const tracked_url_properties = /** @type {const} */ ([
'href',
'pathname',
'search',
'toString',
'toJSON'
]);

@@ -90,13 +105,34 @@ /**

* @param {() => void} callback
* @param {(search_param: string) => void} search_params_callback
*/
export function make_trackable(url, callback) {
export function make_trackable(url, callback, search_params_callback) {
const tracked = new URL(url);
Object.defineProperty(tracked, 'searchParams', {
value: new Proxy(tracked.searchParams, {
get(obj, key) {
if (key === 'get' || key === 'getAll' || key === 'has') {
return (/**@type {string}*/ param) => {
search_params_callback(param);
return obj[key](param);
};
}
// if they try to access something different from what is in `tracked_search_params_properties`
// we track the whole url (entries, values, keys etc)
callback();
const value = Reflect.get(obj, key);
return typeof value === 'function' ? value.bind(obj) : value;
}
}),
enumerable: true,
configurable: true
});
for (const property of tracked_url_properties) {
let value = tracked[property];
Object.defineProperty(tracked, property, {
get() {
callback();
return value;
return url[property];
},

@@ -126,2 +162,4 @@

export function disable_hash(url) {
allow_nodejs_console_log(url);
Object.defineProperty(url, 'hash', {

@@ -141,2 +179,4 @@ get() {

export function disable_search(url) {
allow_nodejs_console_log(url);
for (const property of ['search', 'searchParams']) {

@@ -151,7 +191,21 @@ Object.defineProperty(url, property, {

/**
* Allow URL to be console logged, bypassing disabled properties.
* @param {URL} url
*/
function allow_nodejs_console_log(url) {
if (!BROWSER) {
// @ts-ignore
url[Symbol.for('nodejs.util.inspect.custom')] = (depth, opts, inspect) => {
return inspect(new URL(url), opts);
};
}
}
const DATA_SUFFIX = '/__data.json';
const HTML_DATA_SUFFIX = '.html__data.json';
/** @param {string} pathname */
export function has_data_suffix(pathname) {
return pathname.endsWith(DATA_SUFFIX);
return pathname.endsWith(DATA_SUFFIX) || pathname.endsWith(HTML_DATA_SUFFIX);
}

@@ -161,2 +215,3 @@

export function add_data_suffix(pathname) {
if (pathname.endsWith('.html')) return pathname.replace(/\.html$/, HTML_DATA_SUFFIX);
return pathname.replace(/\/$/, '') + DATA_SUFFIX;

@@ -167,3 +222,7 @@ }

export function strip_data_suffix(pathname) {
if (pathname.endsWith(HTML_DATA_SUFFIX)) {
return pathname.slice(0, -HTML_DATA_SUFFIX.length) + '.html';
}
return pathname.slice(0, -DATA_SUFFIX.length);
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc