feat: docker compose maybe

This commit is contained in:
2023-11-13 16:10:04 -05:00
parent 180b261e40
commit b625ccd8d6
8031 changed files with 2182966 additions and 0 deletions

1
node_modules/@sveltejs/kit/src/runtime/app/env.js generated vendored Normal file
View File

@ -0,0 +1 @@
throw new Error('$app/env has been renamed to $app/environment');

View File

@ -0,0 +1,12 @@
import { BROWSER, DEV } from 'esm-env';
export { building, version } from '__sveltekit/environment';
/**
* `true` if the app is running in the browser.
*/
export const browser = BROWSER;
/**
* Whether the dev server is running. This is not guaranteed to correspond to `NODE_ENV` or `MODE`.
*/
export const dev = DEV;

254
node_modules/@sveltejs/kit/src/runtime/app/forms.js generated vendored Normal file
View File

@ -0,0 +1,254 @@
import * as devalue from 'devalue';
import { BROWSER, DEV } from 'esm-env';
import { client } from '../client/singletons.js';
import { invalidateAll } from './navigation.js';
/**
* 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>}
*/
export function applyAction(result) {
if (BROWSER) {
return client.apply_action(result);
} else {
throw new Error('Cannot call applyAction(...) on the server');
}
}
/**
* 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) {
const parsed = JSON.parse(result);
if (parsed.data) {
parsed.data = devalue.parse(parsed.data);
}
return parsed;
}
/**
* @param {string} old_name
* @param {string} new_name
* @param {string} call_location
* @returns void
*/
function warn_on_access(old_name, new_name, call_location) {
if (!DEV) return;
// TODO 2.0: Remove this code
console.warn(
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})`
);
}
/**
* 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"');
}
/**
* @param {{
* action: URL;
* invalidateAll?: boolean;
* result: import('@sveltejs/kit').ActionResult;
* reset?: boolean
* }} opts
*/
const fallback_callback = async ({
action,
result,
reset = true,
invalidateAll: shouldInvalidateAll = true
}) => {
if (result.type === 'success') {
if (reset) {
// We call reset from the prototype to avoid DOM clobbering
HTMLFormElement.prototype.reset.call(form_element);
}
if (shouldInvalidateAll) {
await invalidateAll();
}
}
// For success/failure results, only apply action if it belongs to the
// current page, otherwise `form` will be updated erroneously
if (
location.origin + location.pathname === action.origin + action.pathname ||
result.type === 'redirect' ||
result.type === 'error'
) {
applyAction(result);
}
};
/** @param {SubmitEvent} event */
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();
const action = new URL(
// We can't do submitter.formAction directly because that property is always set
event.submitter?.hasAttribute('formaction')
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
: clone(form_element).action
);
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) {
// TODO 2.0: Upgrade to `throw Error`
console.warn(
'Your form contains <input type="file"> fields, but is missing the `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. This will be upgraded to an error in v2.0.'
);
break;
}
}
}
const submitter_name = event.submitter?.getAttribute('name');
if (submitter_name) {
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
}
const controller = new AbortController();
let cancelled = false;
const cancel = () => (cancelled = true);
// TODO 2.0: Remove `data` and `form`
const callback =
(await submit({
action,
cancel,
controller,
get data() {
warn_on_access('data', 'formData', 'use:enhance submit function');
return form_data;
},
formData: form_data,
get form() {
warn_on_access('form', 'formElement', 'use:enhance submit function');
return form_element;
},
formElement: form_element,
submitter: event.submitter
})) ?? fallback_callback;
if (cancelled) return;
/** @type {import('@sveltejs/kit').ActionResult} */
let result;
try {
const response = await fetch(action, {
method: 'POST',
headers: {
accept: 'application/json',
'x-sveltekit-action': 'true'
},
cache: 'no-store',
body: form_data,
signal: controller.signal
});
result = deserialize(await response.text());
if (result.type === 'error') result.status = response.status;
} catch (error) {
if (/** @type {any} */ (error)?.name === 'AbortError') return;
result = { type: 'error', error };
}
callback({
action,
get data() {
warn_on_access('data', 'formData', 'callback returned from use:enhance submit function');
return form_data;
},
formData: form_data,
get form() {
warn_on_access('form', 'formElement', 'callback returned from use:enhance submit function');
return form_element;
},
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
result
});
}
// @ts-expect-error
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit);
return {
destroy() {
// @ts-expect-error
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit);
}
};
}

View File

@ -0,0 +1,126 @@
import { client_method } from '../client/singletons.js';
/**
* 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}
*/
export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scroll_handling');
/**
* 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?: any
* }) => 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} [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 {any} [opts.state] The state of the new/updated history entry
* @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 when the preload is complete.
*
* @type {(href: string) => Promise<void>}
* @param {string} href Page to preload
* @returns {Promise<void>}
*/
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 {(...urls: string[]) => Promise<void>}
* @param {...string[]} urls
* @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 the navigation would have directly unloaded the current page, calling `cancel` will trigger the native
* browser unload confirmation dialog. In these cases, `navigation.willUnload` is `true`.
*
* When a navigation isn't client side, `navigation.to.route.id` will be `null`.
*
* `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.
*
* 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/internal.js').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');

1
node_modules/@sveltejs/kit/src/runtime/app/paths.js generated vendored Normal file
View File

@ -0,0 +1 @@
export { base, assets } from '__sveltekit/paths';

94
node_modules/@sveltejs/kit/src/runtime/app/stores.js generated vendored Normal file
View File

@ -0,0 +1,94 @@
import { getContext } from 'svelte';
import { browser } from './environment.js';
import { stores as browser_stores } from '../client/singletons.js';
/**
* 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.
*/
export const getStores = () => {
const stores = browser ? browser_stores : getContext('__svelte__');
return {
/** @type {typeof page} */
page: {
subscribe: stores.page.subscribe
},
/** @type {typeof navigating} */
navigating: {
subscribe: stores.navigating.subscribe
},
/** @type {typeof updated} */
updated: stores.updated
};
};
/**
* 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 = {
subscribe(fn) {
const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
return store.subscribe(fn);
}
};
/**
* 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 = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
return store.subscribe(fn);
}
};
/**
* 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 = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
if (browser) {
updated.check = store.check;
}
return store.subscribe(fn);
},
check: () => {
throw new Error(
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'
);
}
}

2090
node_modules/@sveltejs/kit/src/runtime/client/client.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
export const SNAPSHOT_KEY = 'sveltekit:snapshot';
export const SCROLL_KEY = 'sveltekit:scroll';
export const INDEX_KEY = 'sveltekit:index';
export const PRELOAD_PRIORITIES = /** @type {const} */ ({
tap: 1,
hover: 2,
viewport: 3,
eager: 4,
off: -1
});

View File

@ -0,0 +1,167 @@
import { DEV } from 'esm-env';
import { hash } from '../hash.js';
let loading = 0;
export const native_fetch = window.fetch;
export function lock_fetch() {
loading += 1;
}
export function unlock_fetch() {
loading -= 1;
}
if (DEV) {
let can_inspect_stack_trace = false;
const check_stack_trace = async () => {
const stack = /** @type {string} */ (new Error().stack);
can_inspect_stack_trace = stack.includes('check_stack_trace');
};
check_stack_trace();
/**
* @param {RequestInfo | URL} input
* @param {RequestInit & Record<string, any> | undefined} init
*/
window.fetch = (input, init) => {
// Check if fetch was called via load_node. the lock method only checks if it was called at the
// same time, but not necessarily if it was called from `load`.
// We use just the filename as the method name sometimes does not appear on the CI.
const url = input instanceof Request ? input.url : input.toString();
const stack_array = /** @type {string} */ (new Error().stack).split('\n');
// 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 cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
const stack = stack_array.slice(0, cutoff + 2).join('\n');
const in_load_heuristic = can_inspect_stack_trace
? stack.includes('src/runtime/client/client.js')
: loading;
// 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(
`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`
);
}
const method = input instanceof Request ? input.method : init?.method || 'GET';
if (method !== 'GET') {
cache.delete(build_selector(input));
}
return native_fetch(input, init);
};
} else {
window.fetch = (input, init) => {
const method = input instanceof Request ? input.method : init?.method || 'GET';
if (method !== 'GET') {
cache.delete(build_selector(input));
}
return native_fetch(input, init);
};
}
const cache = new Map();
/**
* Should be called on the initial run of load functions that hydrate the page.
* Saves any requests with cache-control max-age to the cache.
* @param {URL | string} resource
* @param {RequestInit} [opts]
*/
export function initial_fetch(resource, opts) {
const selector = build_selector(resource, opts);
const script = document.querySelector(selector);
if (script?.textContent) {
const { body, ...init } = JSON.parse(script.textContent);
const ttl = script.getAttribute('data-ttl');
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
return Promise.resolve(new Response(body, init));
}
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts);
}
/**
* Tries to get the response from the cache, if max-age allows it, else does a fetch.
* @param {URL | string} resource
* @param {string} resolved
* @param {RequestInit} [opts]
*/
export function subsequent_fetch(resource, resolved, opts) {
if (cache.size > 0) {
const selector = build_selector(resource, opts);
const cached = cache.get(selector);
if (cached) {
// https://developer.mozilla.org/en-US/docs/Web/API/Request/cache#value
if (
performance.now() < cached.ttl &&
['default', 'force-cache', 'only-if-cached', undefined].includes(opts?.cache)
) {
return new Response(cached.body, cached.init);
}
cache.delete(selector);
}
}
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
* @param {URL | RequestInfo} resource
* @param {RequestInit} [opts]
*/
function build_selector(resource, opts) {
const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
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)}"]`;
}
return selector;
}

57
node_modules/@sveltejs/kit/src/runtime/client/parse.js generated vendored Normal file
View File

@ -0,0 +1,57 @@
import { exec, parse_route_id } from '../../utils/routing.js';
/**
* @param {import('./types.js').SvelteKitApp} app
* @returns {import('types').CSRRoute[]}
*/
export function parse({ nodes, server_loads, dictionary, matchers }) {
const layouts_with_server_load = new Set(server_loads);
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
const { pattern, params } = parse_route_id(id);
const route = {
id,
/** @param {string} path */
exec: (path) => {
const match = pattern.exec(path);
if (match) return exec(match, params, matchers);
},
errors: [1, ...(errors || [])].map((n) => nodes[n]),
layouts: [0, ...(layouts || [])].map(create_layout_loader),
leaf: create_leaf_loader(leaf)
};
// bit of a hack, but ensures that layout/error node lists are the same
// length, without which the wrong data will be applied if the route
// manifest looks like `[[a, b], [c,], d]`
route.errors.length = route.layouts.length = Math.max(
route.errors.length,
route.layouts.length
);
return route;
});
/**
* @param {number} id
* @returns {[boolean, import('types').CSRPageNodeLoader]}
*/
function create_leaf_loader(id) {
// whether or not the route uses the server data is
// encoded using the ones' complement, to save space
const uses_server_data = id < 0;
if (uses_server_data) id = ~id;
return [uses_server_data, nodes[id]];
}
/**
* @param {number | undefined} id
* @returns {[boolean, import('types').CSRPageNodeLoader] | undefined}
*/
function create_layout_loader(id) {
// whether or not the layout uses the server data is
// encoded in the layouts array, to save space
return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]];
}
}

View File

@ -0,0 +1,25 @@
/**
* Read a value from `sessionStorage`
* @param {string} key
*/
export function get(key) {
try {
return JSON.parse(sessionStorage[key]);
} catch {
// do nothing
}
}
/**
* Write a value to `sessionStorage`
* @param {string} key
* @param {any} value
*/
export function set(key, value) {
const json = JSON.stringify(value);
try {
sessionStorage[key] = json;
} catch {
// do nothing
}
}

View File

@ -0,0 +1,53 @@
import { writable } from 'svelte/store';
import { create_updated_store, notifiable_store } from './utils.js';
import { BROWSER } from 'esm-env';
/** @type {import('./types.js').Client} */
export let client;
/**
* @param {{
* client: import('./types.js').Client;
* }} opts
*/
export function init(opts) {
client = opts.client;
}
/**
* @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') {
// @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: /* @__PURE__ */ notifiable_store({}),
page: /* @__PURE__ */ notifiable_store({}),
navigating: /* @__PURE__ */ writable(
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
),
updated: /* @__PURE__ */ create_updated_store()
};

28
node_modules/@sveltejs/kit/src/runtime/client/start.js generated vendored Normal file
View File

@ -0,0 +1,28 @@
import { DEV } from 'esm-env';
import { create_client } from './client.js';
import { init } from './singletons.js';
/**
* @param {import('./types.js').SvelteKitApp} app
* @param {HTMLElement} target
* @param {Parameters<import('./types.js').Client['_hydrate']>[0]} [hydrate]
*/
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>'
);
}
const client = create_client(app, target);
init({ client });
if (hydrate) {
await client._hydrate(hydrate);
} else {
client.goto(location.href, { replaceState: true });
}
client._start_router();
}

View File

@ -0,0 +1,123 @@
import { applyAction } from '../app/forms.js';
import {
afterNavigate,
beforeNavigate,
onNavigate,
goto,
invalidate,
invalidateAll,
preloadCode,
preloadData
} from '../app/navigation.js';
import { SvelteComponent } from 'svelte';
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 {
// public API, exposed via $app/navigation
after_navigate: typeof afterNavigate;
before_navigate: typeof beforeNavigate;
on_navigate: typeof onNavigate;
disable_scroll_handling(): void;
goto: typeof goto;
invalidate: typeof invalidate;
invalidate_all: typeof invalidateAll;
preload_code: typeof preloadCode;
preload_data: typeof preloadData;
apply_action: typeof applyAction;
// private API
_hydrate(opts: {
status: number;
error: App.Error | null;
node_ids: number[];
params: Record<string, string>;
route: { id: string | null };
data: Array<import('types').ServerDataNode | null>;
form: Record<string, any> | null;
}): Promise<void>;
_start_router(): void;
}
export type NavigationIntent = {
/** `url.pathname + url.search` */
id: string;
/** Whether we are invalidating or navigating */
invalidating: boolean;
/** The route parameters */
params: Record<string, string>;
/** The route that matches `path` */
route: CSRRoute;
/** The destination URL */
url: URL;
};
export type NavigationResult = NavigationRedirect | NavigationFinished;
export type NavigationRedirect = {
type: 'redirect';
location: string;
};
export type NavigationFinished = {
type: 'loaded';
state: NavigationState;
props: {
constructors: Array<typeof SvelteComponent>;
components?: Array<SvelteComponent>;
page?: Page;
form?: Record<string, any> | null;
[key: `data_${number}`]: Record<string, any>;
};
};
export type BranchNode = {
node: CSRPageNode;
loader: CSRPageNodeLoader;
server: DataNode | null;
universal: DataNode | null;
data: Record<string, any> | null;
slash?: TrailingSlash;
};
export interface DataNode {
type: 'data';
data: Record<string, any> | null;
uses: Uses;
slash?: TrailingSlash;
}
export interface NavigationState {
branch: Array<BranchNode | undefined>;
error: App.Error | null;
params: Record<string, string>;
route: CSRRoute | null;
url: URL;
}

296
node_modules/@sveltejs/kit/src/runtime/client/utils.js generated vendored Normal file
View File

@ -0,0 +1,296 @@
import { BROWSER, DEV } from 'esm-env';
import { writable } from 'svelte/store';
import { assets } from '__sveltekit/paths';
import { version } from '__sveltekit/environment';
import { PRELOAD_PRIORITIES } from './constants.js';
/* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
export const origin = BROWSER ? location.origin : '';
/** @param {HTMLDocument} doc */
export function get_base_uri(doc) {
let baseURI = doc.baseURI;
if (!baseURI) {
const baseTags = doc.getElementsByTagName('base');
baseURI = baseTags.length ? baseTags[0].href : doc.URL;
}
return baseURI;
}
export function scroll_state() {
return {
x: pageXOffset,
y: pageYOffset
};
}
const warned = new WeakSet();
/** @typedef {keyof typeof valid_link_options} LinkOptionName */
const valid_link_options = /** @type {const} */ ({
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'tap', 'hover'],
keepfocus: ['', 'true', 'off', 'false'],
noscroll: ['', 'true', 'off', 'false'],
reload: ['', 'true', 'off', 'false'],
replacestate: ['', 'true', 'off', 'false']
});
/**
* @template {LinkOptionName} T
* @typedef {typeof valid_link_options[T][number]} ValidLinkOptions
*/
/**
* @template {LinkOptionName} T
* @param {Element} element
* @param {T} name
*/
function link_option(element, name) {
const value = /** @type {ValidLinkOptions<T> | null} */ (
element.getAttribute(`data-sveltekit-${name}`)
);
if (DEV) {
validate_link_option(element, name, value);
}
return value;
}
/**
* @template {LinkOptionName} T
* @template {ValidLinkOptions<T> | null} U
* @param {Element} element
* @param {T} name
* @param {U} value
*/
function validate_link_option(element, name, value) {
if (value === null) return;
// @ts-expect-error - includes is dumb
if (!warned.has(element) && !valid_link_options[name].includes(value)) {
console.error(
`Unexpected value for ${name} — should be one of ${valid_link_options[name]
.map((option) => JSON.stringify(option))
.join(', ')}`,
element
);
warned.add(element);
}
}
const levels = {
...PRELOAD_PRIORITIES,
'': PRELOAD_PRIORITIES.hover
};
/**
* @param {Element} element
* @returns {Element | null}
*/
function parent_element(element) {
let parent = element.assignedSlot ?? element.parentNode;
// @ts-expect-error handle shadow roots
if (parent?.nodeType === 11) parent = parent.host;
return /** @type {Element} */ (parent);
}
/**
* @param {Element} element
* @param {Element} target
*/
export function find_anchor(element, target) {
while (element && element !== target) {
if (element.nodeName.toUpperCase() === 'A' && element.hasAttribute('href')) {
return /** @type {HTMLAnchorElement | SVGAElement} */ (element);
}
element = /** @type {Element} */ (parent_element(element));
}
}
/**
* @param {HTMLAnchorElement | SVGAElement} a
* @param {string} base
*/
export function get_link_info(a, base) {
/** @type {URL | undefined} */
let url;
try {
url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
} catch {}
const target = a instanceof SVGAElement ? a.target.baseVal : a.target;
const external =
!url ||
!!target ||
is_external_url(url, base) ||
(a.getAttribute('rel') || '').split(/\s+/).includes('external');
const download = url?.origin === origin && a.hasAttribute('download');
return { url, external, target, download };
}
/**
* @param {HTMLFormElement | HTMLAnchorElement | SVGAElement} element
*/
export function get_router_options(element) {
/** @type {ValidLinkOptions<'keepfocus'> | null} */
let keep_focus = null;
/** @type {ValidLinkOptions<'noscroll'> | null} */
let noscroll = null;
/** @type {ValidLinkOptions<'preload-code'> | null} */
let preload_code = null;
/** @type {ValidLinkOptions<'preload-data'> | null} */
let preload_data = null;
/** @type {ValidLinkOptions<'reload'> | null} */
let reload = null;
/** @type {ValidLinkOptions<'replacestate'> | null} */
let replace_state = null;
/** @type {Element} */
let el = element;
while (el && el !== document.documentElement) {
if (preload_code === null) preload_code = link_option(el, 'preload-code');
if (preload_data === null) preload_data = link_option(el, 'preload-data');
if (keep_focus === null) keep_focus = 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');
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 null;
}
}
return {
preload_code: levels[preload_code ?? 'off'],
preload_data: levels[preload_data ?? 'off'],
keep_focus: get_option_state(keep_focus),
noscroll: get_option_state(noscroll),
reload: get_option_state(reload),
replace_state: get_option_state(replace_state)
};
}
/** @param {any} value */
export function notifiable_store(value) {
const store = writable(value);
let ready = true;
function notify() {
ready = true;
store.update((val) => val);
}
/** @param {any} new_value */
function set(new_value) {
ready = false;
store.set(new_value);
}
/** @param {(value: any) => void} run */
function subscribe(run) {
/** @type {any} */
let old_value;
return store.subscribe((new_value) => {
if (old_value === undefined || (ready && new_value !== old_value)) {
run((old_value = new_value));
}
});
}
return { notify, set, subscribe };
}
export function create_updated_store() {
const { set, subscribe } = writable(false);
if (DEV || !BROWSER) {
return {
subscribe,
check: async () => false
};
}
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
/** @type {NodeJS.Timeout} */
let timeout;
/** @type {() => Promise<boolean>} */
async function check() {
clearTimeout(timeout);
if (interval) timeout = setTimeout(check, interval);
try {
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
headers: {
pragma: 'no-cache',
'cache-control': 'no-cache'
}
});
if (!res.ok) {
return false;
}
const data = await res.json();
const updated = data.version !== version;
if (updated) {
set(true);
clearTimeout(timeout);
}
return updated;
} catch {
return false;
}
}
if (interval) timeout = setTimeout(check, interval);
return {
subscribe,
check
};
}
/**
* @param {URL} url
* @param {string} base
*/
export function is_external_url(url, base) {
return url.origin !== origin || !url.pathname.startsWith(base);
}

View File

@ -0,0 +1,6 @@
<script>
import { page } from '$app/stores';
</script>
<h1>{$page.status}</h1>
<p>{$page.error?.message}</p>

View File

@ -0,0 +1 @@
<slot></slot>

66
node_modules/@sveltejs/kit/src/runtime/control.js generated vendored Normal file
View File

@ -0,0 +1,66 @@
export class HttpError {
/**
* @param {number} status
* @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body
*/
constructor(status, body) {
this.status = status;
if (typeof body === 'string') {
this.body = { message: body };
} else if (body) {
this.body = body;
} else {
this.body = { message: `Error: ${status}` };
}
}
toString() {
return JSON.stringify(this.body);
}
}
export class Redirect {
/**
* @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status
* @param {string} location
*/
constructor(status, location) {
this.status = status;
this.location = location;
}
}
/**
* @template {Record<string, unknown> | undefined} [T=undefined]
*/
export class ActionFailure {
/**
* @param {number} status
* @param {T} [data]
*/
constructor(status, data) {
this.status = status;
this.data = data;
}
}
/**
* This is a grotesque hack that, in dev, allows us to replace the implementations
* of these classes that you'd get by importing them from `@sveltejs/kit` with the
* ones that are imported via Vite and loaded internally, so that instanceof
* checks work even though SvelteKit imports this module via Vite and consumers
* import it via Node
* @param {{
* ActionFailure: typeof ActionFailure;
* HttpError: typeof HttpError;
* Redirect: typeof Redirect;
* }} implementations
*/
export function replace_implementations(implementations) {
// @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
}

View File

@ -0,0 +1 @@
export { private_env as env } from '../../shared-server.js';

View File

@ -0,0 +1 @@
export { public_env as env } from '../../shared-server.js';

22
node_modules/@sveltejs/kit/src/runtime/hash.js generated vendored Normal file
View File

@ -0,0 +1,22 @@
/**
* Hash using djb2
* @param {import('types').StrictBody[]} values
*/
export function hash(...values) {
let hash = 5381;
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');
}
}
return (hash >>> 0).toString(36);
}

View File

@ -0,0 +1,8 @@
declare module '__SERVER__/internal.js' {
export const options: import('types').SSROptions;
export const get_hooks: () => Promise<{
handle?: import('@sveltejs/kit').Handle;
handleError?: import('@sveltejs/kit').HandleServerError;
handleFetch?: import('@sveltejs/kit').HandleFetch;
}>;
}

250
node_modules/@sveltejs/kit/src/runtime/server/cookie.js generated vendored Normal file
View File

@ -0,0 +1,250 @@
import { parse, serialize } from 'cookie';
import { normalize_path } from '../../utils/url.js';
/**
* Tracks all cookies set during dev mode so we can emit warnings
* when we detect that there's likely cookie misusage due to wrong paths
*
* @type {Record<string, Set<string>>} */
const cookie_paths = {};
/**
* Cookies that are larger than this size (including the name and other
* attributes) are discarded by browsers
*/
const MAX_COOKIE_SIZE = 4129;
/**
* @param {Request} request
* @param {URL} url
* @param {import('types').TrailingSlash} trailing_slash
*/
export function get_cookies(request, url, trailing_slash) {
const header = request.headers.get('cookie') ?? '';
const initial_cookies = parse(header, { decode: (value) => value });
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('/') || '/';
/** @type {Record<string, import('./page/types.js').Cookie>} */
const new_cookies = {};
/** @type {import('cookie').CookieSerializeOptions} */
const defaults = {
httpOnly: true,
sameSite: 'lax',
secure: url.hostname === 'localhost' && url.protocol === 'http:' ? false : true
};
/** @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('@sveltejs/kit').Cookies}` above is not
// sufficient to do so.
/**
* @param {string} name
* @param {import('cookie').CookieParseOptions} opts
*/
get(name, opts) {
const c = new_cookies[name];
if (
c &&
domain_matches(url.hostname, c.options.domain) &&
path_matches(url.pathname, c.options.path)
) {
return c.value;
}
const decoder = opts?.decode || decodeURIComponent;
const req_cookies = parse(header, { decode: decoder });
const cookie = req_cookies[name]; // the decoded string or undefined
// 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?`
);
}
}
return cookie;
},
/**
* @param {import('cookie').CookieParseOptions} opts
*/
getAll(opts) {
const decoder = opts?.decode || decodeURIComponent;
const cookies = parse(header, { decode: decoder });
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;
}
}
return Object.entries(cookies).map(([name, value]) => ({ name, value }));
},
/**
* @param {string} name
* @param {string} value
* @param {import('cookie').CookieSerializeOptions} opts
*/
set(name, value, opts = {}) {
set_internal(name, value, { ...defaults, ...opts });
},
/**
* @param {string} name
* @param {import('cookie').CookieSerializeOptions} opts
*/
delete(name, opts = {}) {
cookies.set(name, '', {
...opts,
maxAge: 0
});
},
/**
* @param {string} name
* @param {string} value
* @param {import('cookie').CookieSerializeOptions} opts
*/
serialize(name, value, opts) {
return serialize(name, value, {
...defaults,
...opts
});
}
};
/**
* @param {URL} destination
* @param {string | null} header
*/
function get_cookie_header(destination, header) {
/** @type {Record<string, string>} */
const combined_cookies = {
// cookies sent by the user agent have lowest precedence
...initial_cookies
};
// cookies previous set during this event with cookies.set have higher precedence
for (const key in new_cookies) {
const cookie = new_cookies[key];
if (!domain_matches(destination.hostname, cookie.options.domain)) continue;
if (!path_matches(destination.pathname, cookie.options.path)) continue;
const encoder = cookie.options.encode || encodeURIComponent;
combined_cookies[cookie.name] = encoder(cookie.value);
}
// explicit header has highest precedence
if (header) {
const parsed = parse(header, { decode: (value) => value });
for (const name in parsed) {
combined_cookies[name] = parsed[name];
}
}
return Object.entries(combined_cookies)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
}
/**
* @param {string} name
* @param {string} value
* @param {import('cookie').CookieSerializeOptions} opts
*/
function set_internal(name, value, opts) {
const path = opts.path ?? default_path;
new_cookies[name] = {
name,
value,
options: {
...opts,
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 };
}
/**
* @param {string} hostname
* @param {string} [constraint]
*/
export function domain_matches(hostname, constraint) {
if (!constraint) return true;
const normalized = constraint[0] === '.' ? constraint.slice(1) : constraint;
if (hostname === normalized) return true;
return hostname.endsWith('.' + normalized);
}
/**
* @param {string} path
* @param {string} [constraint]
*/
export function path_matches(path, constraint) {
if (!constraint) return true;
const normalized = constraint.endsWith('/') ? constraint.slice(0, -1) : constraint;
if (path === normalized) return true;
return path.startsWith(normalized + '/');
}
/**
* @param {Headers} headers
* @param {import('./page/types.js').Cookie[]} cookies
*/
export function add_cookies_to_headers(headers, cookies) {
for (const new_cookie of cookies) {
const { name, value, options } = new_cookie;
headers.append('set-cookie', serialize(name, value, options));
}
}
/**
* @param {string[]} array
*/
function conjoin(array) {
if (array.length <= 2) return array.join(' and ');
return `${array.slice(0, -1).join(', ')} and ${array.at(-1)}`;
}

View File

@ -0,0 +1,263 @@
import { HttpError, 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, 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';
const encoder = new TextEncoder();
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSRRoute} route
* @param {import('types').SSROptions} options
* @param {import('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state
* @param {boolean[] | undefined} invalidated_data_nodes
* @param {import('types').TrailingSlash} trailing_slash
* @returns {Promise<Response>}
*/
export async function render_data(
event,
route,
options,
manifest,
state,
invalidated_data_nodes,
trailing_slash
) {
if (!route.page) {
// requesting /__data.json should fail for a +server.js
return new Response(undefined, {
status: 404
});
}
try {
const node_ids = [...route.page.layouts, route.page.leaf];
const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
let aborted = false;
const url = new URL(event.url);
url.pathname = normalize_path(url.pathname, trailing_slash);
const new_event = { ...event, url };
const functions = node_ids.map((n, i) => {
return once(async () => {
try {
if (aborted) {
return /** @type {import('types').ServerDataSkippedNode} */ ({
type: 'skip'
});
}
// == because it could be undefined (in dev) or null (in build, because of JSON.stringify)
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({
event: new_event,
state,
node,
parent: async () => {
/** @type {Record<string, any>} */
const data = {};
for (let j = 0; j < i; j += 1) {
const parent = /** @type {import('types').ServerDataNode | null} */ (
await functions[j]()
);
if (parent) {
Object.assign(data, parent.data);
}
}
return data;
},
track_server_fetches: options.track_server_fetches
});
} catch (e) {
aborted = true;
throw e;
}
});
});
const promises = functions.map(async (fn, i) => {
if (!invalidated[i]) {
return /** @type {import('types').ServerDataSkippedNode} */ ({
type: 'skip'
});
}
return fn();
});
let length = promises.length;
const nodes = await Promise.all(
promises.map((p, i) =>
p.catch(async (error) => {
if (error instanceof Redirect) {
throw error;
}
// Math.min because array isn't guaranteed to resolve in order
length = Math.min(length, i + 1);
return /** @type {import('types').ServerErrorNode} */ ({
type: 'error',
error: await handle_error_and_jsonify(event, options, error),
status: error instanceof HttpError ? error.status : undefined
});
})
)
);
const { data, chunks } = get_data_json(event, options, nodes);
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) {
const error = normalize_error(e);
if (error instanceof Redirect) {
return redirect_json_response(error);
} else {
return json_response(await handle_error_and_jsonify(event, options, error), 500);
}
}
}
/**
* @param {Record<string, any> | string} json
* @param {number} [status]
*/
function json_response(json, status = 200) {
return text(typeof json === 'string' ? json : JSON.stringify(json), {
status,
headers: {
'content-type': 'application/json',
'cache-control': 'private, no-store'
}
});
}
/**
* @param {Redirect} redirect
*/
export function redirect_json_response(redirect) {
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)));
}
}

View File

@ -0,0 +1,94 @@
import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
import { negotiate } from '../../utils/http.js';
import { Redirect } from '../control.js';
import { method_not_allowed } from './utils.js';
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSREndpoint} mod
* @param {import('types').SSRState} state
* @returns {Promise<Response>}
*/
export async function render_endpoint(event, mod, state) {
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
let handler = mod[method] || mod.fallback;
if (method === 'HEAD' && mod.GET && !mod.HEAD) {
handler = mod.GET;
}
if (!handler) {
return method_not_allowed(mod, method);
}
const prerender = mod.prerender ?? state.prerender_default;
if (prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
throw new Error('Cannot prerender endpoints that have mutative methods');
}
if (state.prerendering && !prerender) {
if (state.depth > 0) {
// if request came from a prerendered page, bail
throw new Error(`${event.route.id} is not prerenderable`);
} else {
// if request came direct from the crawler, signal that
// this route cannot be prerendered, but don't bail
return new Response(undefined, { status: 204 });
}
}
try {
let response = await handler(
/** @type {import('@sveltejs/kit').RequestEvent<Record<string, any>>} */ (event)
);
if (!(response instanceof Response)) {
throw new Error(
`Invalid response from route ${event.url.pathname}: handler should return a Response object`
);
}
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));
}
return response;
} catch (e) {
if (e instanceof Redirect) {
return new Response(undefined, {
status: e.status,
headers: { location: e.location }
});
}
throw e;
}
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
export function is_endpoint_request(event) {
const { method, headers } = event.request;
// These methods exist exclusively for endpoints
if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) {
return true;
}
// use:enhance uses a custom header to disambiguate
if (method === 'POST' && headers.get('x-sveltekit-action') === 'true') return false;
// GET/POST requests may be for endpoints or pages. We prefer endpoints if this isn't a text/html request
const accept = event.request.headers.get('accept') ?? '*/*';
return negotiate(accept, ['*', 'text/html']) !== 'text/html';
}

160
node_modules/@sveltejs/kit/src/runtime/server/fetch.js generated vendored Normal file
View File

@ -0,0 +1,160 @@
import * as set_cookie_parser from 'set-cookie-parser';
import { respond } from './respond.js';
import * as paths from '__sveltekit/paths';
/**
* @param {{
* event: import('@sveltejs/kit').RequestEvent;
* options: import('types').SSROptions;
* 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('cookie').CookieSerializeOptions) => void;
* }} opts
* @returns {typeof fetch}
*/
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) {
return async (info, init) => {
const original_request = normalize_fetch_input(info, init, event.url);
// some runtimes (e.g. Cloudflare) error if you access `request.mode`,
// annoyingly, so we need to read the value from the `init` object instead
let mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
let credentials =
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
return await options.hooks.handleFetch({
event,
request: original_request,
fetch: async (info, init) => {
const request = normalize_fetch_input(info, init, event.url);
const url = new URL(request.url);
if (!request.headers.has('origin')) {
request.headers.set('origin', event.url.origin);
}
if (info !== original_request) {
mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
credentials =
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
}
// Remove Origin, according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description
if (
(request.method === 'GET' || request.method === 'HEAD') &&
((mode === 'no-cors' && url.origin !== event.url.origin) ||
url.origin === event.url.origin)
) {
request.headers.delete('origin');
}
if (url.origin !== event.url.origin) {
// Allow cookie passthrough for "credentials: same-origin" and "credentials: include"
// if SvelteKit is serving my.domain.com:
// - domain.com WILL NOT receive cookies
// - my.domain.com WILL receive cookies
// - api.domain.dom WILL NOT receive cookies
// - sub.my.domain.com WILL receive cookies
// ports do not affect the resolution
// 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') {
const cookie = get_cookie_header(url, request.headers.get('cookie'));
if (cookie) request.headers.set('cookie', cookie);
}
return fetch(request);
}
// handle fetch requests for static assets. e.g. prebaked data, etc.
// we need to support everything the browser's fetch supports
const prefix = paths.assets || paths.base;
const decoded = decodeURIComponent(url.pathname);
const filename = (
decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded
).slice(1);
const filename_html = `${filename}/index.html`; // path may also match path/index.html
const is_asset = manifest.assets.has(filename);
const is_asset_html = manifest.assets.has(filename_html);
if (is_asset || is_asset_html) {
const file = is_asset ? filename : filename_html;
if (state.read) {
const type = is_asset
? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
: 'text/html';
return new Response(state.read(file), {
headers: type ? { 'content-type': type } : {}
});
}
return await fetch(request);
}
if (credentials !== 'omit') {
const cookie = get_cookie_header(url, request.headers.get('cookie'));
if (cookie) {
request.headers.set('cookie', cookie);
}
const authorization = event.request.headers.get('authorization');
if (authorization && !request.headers.has('authorization')) {
request.headers.set('authorization', authorization);
}
}
if (!request.headers.has('accept')) {
request.headers.set('accept', '*/*');
}
if (!request.headers.has('accept-language')) {
request.headers.set(
'accept-language',
/** @type {string} */ (event.request.headers.get('accept-language'))
);
}
/** @type {Response} */
const response = await respond(request, options, manifest, {
...state,
depth: state.depth + 1
});
const set_cookie = response.headers.get('set-cookie');
if (set_cookie) {
for (const str of set_cookie_parser.splitCookiesString(set_cookie)) {
const { name, value, ...options } = set_cookie_parser.parseString(str);
// options.sameSite is string, something more specific is required - type cast is safe
set_internal(
name,
value,
/** @type {import('cookie').CookieSerializeOptions} */ (options)
);
}
}
return response;
}
});
};
}
/**
* @param {RequestInfo | URL} info
* @param {RequestInit | undefined} init
* @param {URL} url
*/
function normalize_fetch_input(info, init, url) {
if (info instanceof Request) {
return info;
}
return new Request(typeof info === 'string' ? new URL(info, url) : info, init);
}

87
node_modules/@sveltejs/kit/src/runtime/server/index.js generated vendored Normal file
View File

@ -0,0 +1,87 @@
import { respond } from './respond.js';
import { set_private_env, set_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';
export class Server {
/** @type {import('types').SSROptions} */
#options;
/** @type {import('@sveltejs/kit').SSRManifest} */
#manifest;
/** @param {import('@sveltejs/kit').SSRManifest} manifest */
constructor(manifest) {
/** @type {import('types').SSROptions} */
this.#options = options;
this.#manifest = manifest;
}
/**
* @param {{
* env: Record<string, string>
* }} opts
*/
async init({ env }) {
// Take care: Some adapters may have to call `Server.init` per-request to set env vars,
// so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't
// been done already.
// set env, in case it's used in initialisation
set_private_env(
filter_private_env(env, {
public_prefix: this.#options.env_public_prefix,
private_prefix: this.#options.env_private_prefix
})
);
set_public_env(
filter_public_env(env, {
public_prefix: this.#options.env_public_prefix,
private_prefix: this.#options.env_private_prefix
})
);
if (!this.#options.hooks) {
try {
const module = await get_hooks();
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;
}
}
}
}
/**
* @param {Request} request
* @param {import('types').RequestOptions} options
*/
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,
error: false,
depth: 0
});
}
}

View File

@ -0,0 +1,282 @@
import * as devalue from 'devalue';
import { error, json } from '../../../exports/index.js';
import { normalize_error } from '../../../utils/error.js';
import { is_form_content_type, negotiate } from '../../../utils/http.js';
import { HttpError, Redirect, ActionFailure } from '../../control.js';
import { handle_error_and_jsonify } from '../utils.js';
/** @param {import('@sveltejs/kit').RequestEvent} event */
export function is_action_json_request(event) {
const accept = negotiate(event.request.headers.get('accept') ?? '*/*', [
'application/json',
'text/html'
]);
return accept === 'application/json' && event.request.method === 'POST';
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {import('types').SSRNode['server'] | undefined} server
*/
export async function handle_action_json_request(event, options, server) {
const actions = server?.actions;
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');
return action_json(
{
type: 'error',
error: await handle_error_and_jsonify(event, options, no_actions_error)
},
{
status: no_actions_error.status,
headers: {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: 'GET'
}
}
);
}
check_named_default_separate(actions);
try {
const data = await call_action(event, actions);
if (__SVELTEKIT_DEV__) {
validate_action_return(data);
}
if (data instanceof ActionFailure) {
return action_json({
type: 'failure',
status: data.status,
// @ts-expect-error we assign a string to what is supposed to be an object. That's ok
// because we don't use the object outside, and this way we have better code navigation
// through knowing where the related interface is used.
data: stringify_action_response(data.data, /** @type {string} */ (event.route.id))
});
} else {
return action_json({
type: 'success',
status: data ? 200 : 204,
// @ts-expect-error see comment above
data: stringify_action_response(data, /** @type {string} */ (event.route.id))
});
}
} catch (e) {
const err = normalize_error(e);
if (err instanceof Redirect) {
return action_json_redirect(err);
}
return action_json(
{
type: 'error',
error: await handle_error_and_jsonify(event, options, check_incorrect_fail_use(err))
},
{
status: err instanceof HttpError ? err.status : 500
}
);
}
}
/**
* @param {HttpError | Error} error
*/
function check_incorrect_fail_use(error) {
return error instanceof ActionFailure
? new Error('Cannot "throw fail()". Use "return fail()"')
: error;
}
/**
* @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]
*/
function action_json(data, init) {
return json(data, init);
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
export function is_action_request(event) {
return event.request.method === 'POST';
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSRNode['server'] | undefined} server
* @returns {Promise<import('@sveltejs/kit').ActionResult>}
*/
export async function handle_action_request(event, server) {
const actions = server?.actions;
if (!actions) {
// TODO should this be a different error altogether?
event.setHeaders({
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: 'GET'
});
return {
type: 'error',
error: error(405, 'POST method not allowed. No actions exist for this page')
};
}
check_named_default_separate(actions);
try {
const data = await call_action(event, actions);
if (__SVELTEKIT_DEV__) {
validate_action_return(data);
}
if (data instanceof ActionFailure) {
return {
type: 'failure',
status: data.status,
data: data.data
};
} else {
return {
type: 'success',
status: 200,
// @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
data
};
}
} catch (e) {
const err = normalize_error(e);
if (err instanceof Redirect) {
return {
type: 'redirect',
status: err.status,
location: err.location
};
}
return {
type: 'error',
error: check_incorrect_fail_use(err)
};
}
}
/**
* @param {import('@sveltejs/kit').Actions} actions
*/
function check_named_default_separate(actions) {
if (actions.default && Object.keys(actions).length > 1) {
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'
);
}
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
* @throws {Redirect | ActionFailure | HttpError | Error}
*/
async function call_action(event, actions) {
const url = new URL(event.request.url);
let name = 'default';
for (const param of url.searchParams) {
if (param[0].startsWith('/')) {
name = param[0].slice(1);
if (name === 'default') {
throw new Error('Cannot use reserved action name "default"');
}
break;
}
}
const action = actions[name];
if (!action) {
throw new Error(`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')})`
);
}
return action(event);
}
/** @param {any} data */
function validate_action_return(data) {
if (data instanceof Redirect) {
throw new Error('Cannot `return redirect(...)` — use `throw redirect(...)` instead');
}
if (data instanceof HttpError) {
throw new Error(
'Cannot `return error(...)` — use `throw error(...)` or `return fail(...)` instead'
);
}
}
/**
* Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
* @param {any} data
* @param {string} route_id
*/
export function uneval_action_response(data, route_id) {
return try_deserialize(data, devalue.uneval, route_id);
}
/**
* Try to `devalue.stringify` the data object, and if it fails, return a proper Error with context
* @param {any} data
* @param {string} route_id
*/
function stringify_action_response(data, route_id) {
return try_deserialize(data, devalue.stringify, route_id);
}
/**
* @param {any} data
* @param {(data: any) => string} fn
* @param {string} route_id
*/
function try_deserialize(data, fn, route_id) {
try {
return fn(data);
} catch (e) {
// If we're here, the data could not be serialized with devalue
const error = /** @type {any} */ (e);
if ('path' in error) {
let message = `Data returned from action inside ${route_id} is not serializable: ${error.message}`;
if (error.path !== '') message += ` (data.${error.path})`;
throw new Error(message);
}
throw error;
}
}

View File

@ -0,0 +1,239 @@
const encoder = new TextEncoder();
/**
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
* modified and redistributed under BSD license
* @param {string} data
*/
export function sha256(data) {
if (!key[0]) precompute();
const out = init.slice(0);
const array = encode(data);
for (let i = 0; i < array.length; i += 16) {
const w = array.subarray(i, i + 16);
let tmp;
let a;
let b;
let out0 = out[0];
let out1 = out[1];
let out2 = out[2];
let out3 = out[3];
let out4 = out[4];
let out5 = out[5];
let out6 = out[6];
let out7 = out[7];
/* Rationale for placement of |0 :
* If a value can overflow is original 32 bits by a factor of more than a few
* million (2^23 ish), there is a possibility that it might overflow the
* 53-bit mantissa and lose precision.
*
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
* propagates around the loop, and on the hash state out[]. I don't believe
* that the clamps on out4 and on out0 are strictly necessary, but it's close
* (for out4 anyway), and better safe than sorry.
*
* The clamps on out[] are necessary for the output to be correct even in the
* common case and for short inputs.
*/
for (let i = 0; i < 64; i++) {
// load up the input word for this round
if (i < 16) {
tmp = w[i];
} else {
a = w[(i + 1) & 15];
b = w[(i + 14) & 15];
tmp = w[i & 15] =
(((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
w[i & 15] +
w[(i + 9) & 15]) |
0;
}
tmp =
tmp +
out7 +
((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
(out6 ^ (out4 & (out5 ^ out6))) +
key[i]; // | 0;
// shift register
out7 = out6;
out6 = out5;
out5 = out4;
out4 = (out3 + tmp) | 0;
out3 = out2;
out2 = out1;
out1 = out0;
out0 =
(tmp +
((out1 & out2) ^ (out3 & (out1 ^ out2))) +
((out1 >>> 2) ^
(out1 >>> 13) ^
(out1 >>> 22) ^
(out1 << 30) ^
(out1 << 19) ^
(out1 << 10))) |
0;
}
out[0] = (out[0] + out0) | 0;
out[1] = (out[1] + out1) | 0;
out[2] = (out[2] + out2) | 0;
out[3] = (out[3] + out3) | 0;
out[4] = (out[4] + out4) | 0;
out[5] = (out[5] + out5) | 0;
out[6] = (out[6] + out6) | 0;
out[7] = (out[7] + out7) | 0;
}
const bytes = new Uint8Array(out.buffer);
reverse_endianness(bytes);
return base64(bytes);
}
/** The SHA-256 initialization vector */
const init = new Uint32Array(8);
/** The SHA-256 hash key */
const key = new Uint32Array(64);
/** Function to precompute init and key. */
function precompute() {
/** @param {number} x */
function frac(x) {
return (x - Math.floor(x)) * 0x100000000;
}
let prime = 2;
for (let i = 0; i < 64; prime++) {
let is_prime = true;
for (let factor = 2; factor * factor <= prime; factor++) {
if (prime % factor === 0) {
is_prime = false;
break;
}
}
if (is_prime) {
if (i < 8) {
init[i] = frac(prime ** (1 / 2));
}
key[i] = frac(prime ** (1 / 3));
i++;
}
}
}
/** @param {Uint8Array} bytes */
function reverse_endianness(bytes) {
for (let i = 0; i < bytes.length; i += 4) {
const a = bytes[i + 0];
const b = bytes[i + 1];
const c = bytes[i + 2];
const d = bytes[i + 3];
bytes[i + 0] = d;
bytes[i + 1] = c;
bytes[i + 2] = b;
bytes[i + 3] = a;
}
}
/** @param {string} str */
function encode(str) {
const encoded = encoder.encode(str);
const length = encoded.length * 8;
// result should be a multiple of 512 bits in length,
// with room for a 1 (after the data) and two 32-bit
// words containing the original input bit length
const size = 512 * Math.ceil((length + 65) / 512);
const bytes = new Uint8Array(size / 8);
bytes.set(encoded);
// append a 1
bytes[encoded.length] = 0b10000000;
reverse_endianness(bytes);
// add the input bit length
const words = new Uint32Array(bytes.buffer);
words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
words[words.length - 1] = length;
return words;
}
/*
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
MIT License
Copyright (c) 2020 Egor Nepomnyaschih
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
/** @param {Uint8Array} bytes */
export function base64(bytes) {
const l = bytes.length;
let result = '';
let i;
for (i = 2; i < l; i += 3) {
result += chars[bytes[i - 2] >> 2];
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
result += chars[bytes[i] & 0x3f];
}
if (i === l + 1) {
// 1 octet yet to write
result += chars[bytes[i - 2] >> 2];
result += chars[(bytes[i - 2] & 0x03) << 4];
result += '==';
}
if (i === l) {
// 2 octets yet to write
result += chars[bytes[i - 2] >> 2];
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
result += chars[(bytes[i - 1] & 0x0f) << 2];
result += '=';
}
return result;
}

View File

@ -0,0 +1,254 @@
import { escape_html_attr } from '../../../utils/escape.js';
import { base64, sha256 } from './crypto.js';
const array = new Uint8Array(16);
function generate_nonce() {
crypto.getRandomValues(array);
return base64(array);
}
const quoted = new Set([
'self',
'unsafe-eval',
'unsafe-hashes',
'unsafe-inline',
'none',
'strict-dynamic',
'report-sample',
'wasm-unsafe-eval',
'script'
]);
const crypto_pattern = /^(nonce|sha\d\d\d)-/;
// CSP and CSP Report Only are extremely similar with a few caveats
// the easiest/DRYest way to express this is with some private encapsulation
class BaseProvider {
/** @type {boolean} */
#use_hashes;
/** @type {boolean} */
#script_needs_csp;
/** @type {boolean} */
#style_needs_csp;
/** @type {import('types').CspDirectives} */
#directives;
/** @type {import('types').Csp.Source[]} */
#script_src;
/** @type {import('types').Csp.Source[]} */
#style_src;
/** @type {string} */
#nonce;
/**
* @param {boolean} use_hashes
* @param {import('types').CspDirectives} directives
* @param {string} nonce
*/
constructor(use_hashes, directives, nonce) {
this.#use_hashes = use_hashes;
this.#directives = __SVELTEKIT_DEV__ ? { ...directives } : directives; // clone in dev so we can safely mutate
const d = this.#directives;
if (__SVELTEKIT_DEV__) {
// remove strict-dynamic in dev...
// TODO reinstate this if we can figure out how to make strict-dynamic work
// if (d['default-src']) {
// d['default-src'] = d['default-src'].filter((name) => name !== 'strict-dynamic');
// if (d['default-src'].length === 0) delete d['default-src'];
// }
// if (d['script-src']) {
// d['script-src'] = d['script-src'].filter((name) => name !== 'strict-dynamic');
// if (d['script-src'].length === 0) delete d['script-src'];
// }
const effective_style_src = d['style-src'] || d['default-src'];
// ...and add unsafe-inline so we can inject <style> elements
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
d['style-src'] = [...effective_style_src, 'unsafe-inline'];
}
}
this.#script_src = [];
this.#style_src = [];
const effective_script_src = d['script-src'] || d['default-src'];
const effective_style_src = d['style-src'] || d['default-src'];
this.#script_needs_csp =
!!effective_script_src &&
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
this.#style_needs_csp =
!__SVELTEKIT_DEV__ &&
!!effective_style_src &&
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
this.#nonce = nonce;
}
/** @param {string} content */
add_script(content) {
if (this.#script_needs_csp) {
if (this.#use_hashes) {
this.#script_src.push(`sha256-${sha256(content)}`);
} else if (this.#script_src.length === 0) {
this.#script_src.push(`nonce-${this.#nonce}`);
}
}
}
/** @param {string} content */
add_style(content) {
if (this.#style_needs_csp) {
if (this.#use_hashes) {
this.#style_src.push(`sha256-${sha256(content)}`);
} else if (this.#style_src.length === 0) {
this.#style_src.push(`nonce-${this.#nonce}`);
}
}
}
/**
* @param {boolean} [is_meta]
*/
get_header(is_meta = false) {
const header = [];
// due to browser inconsistencies, we can't append sources to default-src
// (specifically, Firefox appears to not ignore nonce-{nonce} directives
// on default-src), so we ensure that script-src and style-src exist
const directives = { ...this.#directives };
if (this.#style_src.length > 0) {
directives['style-src'] = [
...(directives['style-src'] || directives['default-src'] || []),
...this.#style_src
];
}
if (this.#script_src.length > 0) {
directives['script-src'] = [
...(directives['script-src'] || directives['default-src'] || []),
...this.#script_src
];
}
for (const key in directives) {
if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
// these values cannot be used with a <meta> tag
// TODO warn?
continue;
}
// @ts-expect-error gimme a break typescript, `key` is obviously a member of internal_directives
const value = /** @type {string[] | true} */ (directives[key]);
if (!value) continue;
const directive = [key];
if (Array.isArray(value)) {
value.forEach((value) => {
if (quoted.has(value) || crypto_pattern.test(value)) {
directive.push(`'${value}'`);
} else {
directive.push(value);
}
});
}
header.push(directive.join(' '));
}
return header.join('; ');
}
}
class CspProvider extends BaseProvider {
get_meta() {
const content = this.get_header(true);
if (!content) {
return;
}
return `<meta http-equiv="content-security-policy" content=${escape_html_attr(content)}>`;
}
}
class CspReportOnlyProvider extends BaseProvider {
/**
* @param {boolean} use_hashes
* @param {import('types').CspDirectives} directives
* @param {string} nonce
*/
constructor(use_hashes, directives, nonce) {
super(use_hashes, directives, nonce);
if (Object.values(directives).filter((v) => !!v).length > 0) {
// If we're generating content-security-policy-report-only,
// if there are any directives, we need a report-uri or report-to (or both)
// else it's just an expensive noop.
const has_report_to = directives['report-to']?.length ?? 0 > 0;
const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
if (!has_report_to && !has_report_uri) {
throw Error(
'`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
);
}
}
}
}
export class Csp {
/** @readonly */
nonce = generate_nonce();
/** @type {CspProvider} */
csp_provider;
/** @type {CspReportOnlyProvider} */
report_only_provider;
/**
* @param {import('./types.js').CspConfig} config
* @param {import('./types.js').CspOpts} opts
*/
constructor({ mode, directives, reportOnly }, { prerender }) {
const use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
this.csp_provider = new CspProvider(use_hashes, directives, this.nonce);
this.report_only_provider = new CspReportOnlyProvider(use_hashes, reportOnly, this.nonce);
}
get script_needs_nonce() {
return this.csp_provider.script_needs_nonce || this.report_only_provider.script_needs_nonce;
}
get style_needs_nonce() {
return this.csp_provider.style_needs_nonce || this.report_only_provider.style_needs_nonce;
}
/** @param {string} content */
add_script(content) {
this.csp_provider.add_script(content);
this.report_only_provider.add_script(content);
}
/** @param {string} content */
add_style(content) {
this.csp_provider.add_style(content);
this.report_only_provider.add_style(content);
}
}

View File

@ -0,0 +1,315 @@
import { text } from '../../../exports/index.js';
import { compact } from '../../../utils/array.js';
import { normalize_error } from '../../../utils/error.js';
import { add_data_suffix } from '../../../utils/url.js';
import { HttpError, Redirect } from '../../control.js';
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
import {
handle_action_json_request,
handle_action_request,
is_action_json_request,
is_action_request
} from './actions.js';
import { load_data, load_server_data } from './load_data.js';
import { render_response } from './render.js';
import { respond_with_error } from './respond_with_error.js';
import { get_option } from '../../../utils/options.js';
import { get_data_json } from '../data/index.js';
/**
* 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('@sveltejs/kit').SSRManifest} manifest
* @param {import('types').SSRState} state
* @param {import('types').RequiredResolveOptions} resolve_opts
* @returns {Promise<Response>}
*/
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 // TODO in some cases this should be 500. not sure how to differentiate
});
}
if (is_action_json_request(event)) {
const node = await manifest._.nodes[page.leaf]();
return handle_action_json_request(event, options, node?.server);
}
try {
const nodes = await Promise.all([
// we use == here rather than === because [undefined] serializes as "[null]"
...page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
manifest._.nodes[page.leaf]()
]);
const leaf_node = /** @type {import('types').SSRNode} */ (nodes.at(-1));
let status = 200;
/** @type {import('@sveltejs/kit').ActionResult | undefined} */
let action_result = undefined;
if (is_action_request(event)) {
// for action requests, first call handler in +page.server.js
// (this also determines status code)
action_result = await handle_action_request(event, leaf_node.server);
if (action_result?.type === 'redirect') {
return redirect_response(action_result.status, action_result.location);
}
if (action_result?.type === 'error') {
const error = action_result.error;
status = error instanceof HttpError ? error.status : 500;
}
if (action_result?.type === 'failure') {
status = action_result.status;
}
}
const should_prerender_data = nodes.some((node) => node?.server);
const data_pathname = add_data_suffix(event.url.pathname);
// it's crucial that we do this before returning the non-SSR response, otherwise
// 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') ?? false;
if (should_prerender) {
const mod = leaf_node.server;
if (mod?.actions) {
throw new Error('Cannot prerender pages with actions');
}
} else if (state.prerendering) {
// if the page isn't marked as prerenderable, then bail out at this point
return new Response(undefined, {
status: 204
});
}
// if we fetch any endpoints while loading data for this page, they should
// inherit the prerender option of the page
state.prerender_default = should_prerender;
/** @type {import('./types.js').Fetched[]} */
const fetched = [];
if (get_option(nodes, 'ssr') === false && !state.prerendering) {
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
});
}
/** @type {Array<import('./types.js').Loaded | null>} */
const branch = [];
/** @type {Error | null} */
let load_error = null;
/** @type {Array<Promise<import('types').ServerDataNode | null>>} */
const server_promises = nodes.map((node, i) => {
if (load_error) {
// if an error happens immediately, don't bother with the rest of the nodes
throw load_error;
}
return Promise.resolve().then(async () => {
try {
if (node === leaf_node && action_result?.type === 'error') {
// we wait until here to throw the error so that we can use
// any nested +error.svelte components that were defined
throw action_result.error;
}
return await load_server_data({
event,
state,
node,
parent: async () => {
/** @type {Record<string, any>} */
const data = {};
for (let j = 0; j < i; j += 1) {
const parent = await server_promises[j];
if (parent) Object.assign(data, await parent.data);
}
return data;
},
track_server_fetches: options.track_server_fetches
});
} catch (e) {
load_error = /** @type {Error} */ (e);
throw load_error;
}
});
});
const csr = get_option(nodes, 'csr') ?? true;
/** @type {Array<Promise<Record<string, any> | null>>} */
const load_promises = nodes.map((node, i) => {
if (load_error) throw load_error;
return Promise.resolve().then(async () => {
try {
return await load_data({
event,
fetched,
node,
parent: async () => {
const data = {};
for (let j = 0; j < i; j += 1) {
Object.assign(data, await load_promises[j]);
}
return data;
},
resolve_opts,
server_data_promise: server_promises[i],
state,
csr
});
} catch (e) {
load_error = /** @type {Error} */ (e);
throw load_error;
}
});
});
// if we don't do this, rejections will be unhandled
for (const p of server_promises) p.catch(() => {});
for (const p of load_promises) p.catch(() => {});
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (node) {
try {
const server_data = await server_promises[i];
const data = await load_promises[i];
branch.push({ node, server_data, data });
} catch (e) {
const err = normalize_error(e);
if (err instanceof Redirect) {
if (state.prerendering && should_prerender_data) {
const body = JSON.stringify({
type: 'redirect',
location: err.location
});
state.prerendering.dependencies.set(data_pathname, {
response: text(body),
body
});
}
return redirect_response(err.status, err.location);
}
const status = err instanceof HttpError ? err.status : 500;
const error = await handle_error_and_jsonify(event, options, err);
while (i--) {
if (page.errors[i]) {
const index = /** @type {number} */ (page.errors[i]);
const node = await manifest._.nodes[index]();
let j = i;
while (!branch[j]) j -= 1;
return await render_response({
event,
options,
manifest,
state,
resolve_opts,
page_config: { ssr: true, csr: true },
status,
error,
branch: compact(branch.slice(0, j + 1)).concat({
node,
data: null,
server_data: null
}),
fetched
});
}
}
// if we're still here, it means the error happened in the root layout,
// which means we have to fall back to error.html
return static_error_page(options, status, error.message);
}
} else {
// push an empty slot so we can rewind past gaps to the
// layout that corresponds with an +error.svelte page
branch.push(null);
}
}
if (state.prerendering && should_prerender_data) {
// 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(data),
body: data
});
}
return await render_response({
event,
options,
manifest,
state,
resolve_opts,
page_config: {
csr: get_option(nodes, 'csr') ?? true,
ssr: true
},
status,
error: null,
branch: compact(branch),
action_result,
fetched
});
} catch (e) {
// if we end up here, it means the data loaded successfully
// but the page failed to render, or that a prerendering error occurred
return await respond_with_error({
event,
options,
manifest,
state,
status: 500,
error: e,
resolve_opts
});
}
}

View File

@ -0,0 +1,369 @@
import { disable_search, make_trackable } from '../../../utils/url.js';
import { unwrap_promises } from '../../../utils/promises.js';
import { DEV } from 'esm-env';
import { validate_depends } from '../../shared.js';
/**
* Calls the user's server `load` function.
* @param {{
* event: import('@sveltejs/kit').RequestEvent;
* state: import('types').SSRState;
* node: import('types').SSRNode | undefined;
* parent: () => Promise<Record<string, any>>;
* track_server_fetches: boolean;
* }} opts
* @returns {Promise<import('types').ServerDataNode | null>}
*/
export async function load_server_data({
event,
state,
node,
parent,
// TODO 2.0: Remove this
track_server_fetches
}) {
if (!node?.server) return null;
let done = false;
const uses = {
dependencies: new Set(),
params: new Set(),
parent: false,
route: false,
url: false
};
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`
);
}
uses.url = true;
});
if (state.prerendering) {
disable_search(url);
}
const result = await node.server.load?.call(null, {
...event,
fetch: (info, init) => {
const url = new URL(info instanceof Request ? info.url : info, event.url);
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`
);
}
// TODO 2.0: Remove this
if (track_server_fetches) {
uses.dependencies.add(url.href);
}
return event.fetch(info, init);
},
/** @param {string[]} deps */
depends: (...deps) => {
for (const dep of deps) {
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);
}
},
params: new Proxy(event.params, {
get: (target, 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`
);
}
uses.params.add(key);
return target[/** @type {string} */ (key)];
}
}),
parent: async () => {
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`
);
}
uses.parent = true;
return parent();
},
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`
);
}
uses.route = true;
return target[/** @type {'id'} */ (key)];
}
}),
url
});
const data = result ? await unwrap_promises(result) : null;
if (__SVELTEKIT_DEV__) {
validate_load_response(data, /** @type {string} */ (event.route.id));
}
done = true;
return {
type: 'data',
data,
uses,
slash: node.server.trailingSlash
};
}
/**
* Calls the user's `load` function.
* @param {{
* event: import('@sveltejs/kit').RequestEvent;
* fetched: import('./types.js').Fetched[];
* node: import('types').SSRNode | undefined;
* parent: () => Promise<Record<string, any>>;
* resolve_opts: import('types').RequiredResolveOptions;
* server_data_promise: Promise<import('types').ServerDataNode | null>;
* state: import('types').SSRState;
* csr: boolean;
* }} opts
* @returns {Promise<Record<string, any | Promise<any>> | null>}
*/
export async function load_data({
event,
fetched,
node,
parent,
server_data_promise,
state,
resolve_opts,
csr
}) {
const server_data_node = await server_data_promise;
if (!node?.universal?.load) {
return server_data_node?.data ?? null;
}
const result = await node.universal.load.call(null, {
url: event.url,
params: event.params,
data: server_data_node?.data ?? null,
route: event.route,
fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
setHeaders: event.setHeaders,
depends: () => {},
parent
});
const data = result ? await unwrap_promises(result) : null;
if (__SVELTEKIT_DEV__) {
validate_load_response(data, /** @type {string} */ (event.route.id));
}
return data;
}
/**
* @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('@sveltejs/kit').ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts
*/
export function create_universal_fetch(event, state, fetched, csr, resolve_opts) {
/**
* @param {URL | RequestInfo} input
* @param {RequestInit} [init]
*/
return 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);
const url = new URL(input instanceof Request ? input.url : input, event.url);
const same_origin = url.origin === event.url.origin;
/** @type {import('types').PrerenderDependency} */
let dependency;
if (same_origin) {
if (state.prerendering) {
dependency = { response, body: null };
state.prerendering.dependencies.set(url.pathname, dependency);
}
} else {
// simulate CORS errors and "no access to body in no-cors mode" server-side for consistency with client-side behaviour
const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
if (mode === 'no-cors') {
response = new Response('', {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} else {
const acao = response.headers.get('access-control-allow-origin');
if (!acao || (acao !== event.url.origin && acao !== '*')) {
throw new Error(
`CORS error: ${
acao ? 'Incorrect' : 'No'
} 'Access-Control-Allow-Origin' header is present on the requested resource`
);
}
}
}
const proxy = new Proxy(response, {
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
),
request_headers: cloned_headers,
response_body: body,
response
});
}
if (dependency) {
dependency.body = body;
}
return body;
}
if (key === 'arrayBuffer') {
return async () => {
const buffer = await response.arrayBuffer();
if (dependency) {
dependency.body = new Uint8Array(buffer);
}
// TODO should buffer be inlined into the page (albeit base64'd)?
// any conditions in which it shouldn't be?
return buffer;
};
}
if (key === 'text') {
return text;
}
if (key === 'json') {
return async () => {
return JSON.parse(await text());
};
}
return Reflect.get(response, key, response);
}
});
if (csr) {
// ensure that excluded headers can't be read
const get = response.headers.get;
response.headers.get = (key) => {
const lower = key.toLowerCase();
const value = get.call(response.headers, lower);
if (value && !lower.startsWith('x-sveltekit-')) {
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
if (!included) {
throw new Error(
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.route.id})`
);
}
}
return value;
};
}
return proxy;
};
}
/**
* @param {ReadableStream<Uint8Array>} stream
*/
async function stream_to_string(stream) {
let result = '';
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
result += decoder.decode(value);
}
return result;
}
/**
* @param {any} data
* @param {string} [routeId]
*/
function validate_load_response(data, routeId) {
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
throw new Error(
`a load function related to route '${routeId}' returned ${
typeof data !== 'object'
? `a ${typeof data}`
: data instanceof Response
? '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 {...}\`)`
);
}
}

View File

@ -0,0 +1,564 @@
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';
import { serialize_data } from './serialize_data.js';
import { s } from '../../../utils/misc.js';
import { Csp } from './csp.js';
import { uneval_action_response } from './actions.js';
import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
import { 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';
// TODO rename this function/module
const updated = {
...readable(false),
check: () => false
};
const encoder = new TextEncoder();
/**
* Creates the HTML response.
* @param {{
* branch: Array<import('./types.js').Loaded>;
* fetched: Array<import('./types.js').Fetched>;
* options: import('types').SSROptions;
* manifest: import('@sveltejs/kit').SSRManifest;
* state: import('types').SSRState;
* page_config: { ssr: boolean; csr: boolean };
* status: number;
* error: App.Error | null;
* event: import('@sveltejs/kit').RequestEvent;
* resolve_opts: import('types').RequiredResolveOptions;
* action_result?: import('@sveltejs/kit').ActionResult;
* }} opts
*/
export async function render_response({
branch,
fetched,
options,
manifest,
state,
page_config,
status,
error = null,
event,
resolve_opts,
action_result
}) {
if (state.prerendering) {
if (options.csp.mode === 'nonce') {
throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
}
if (options.app_template_contains_nonce) {
throw new Error('Cannot use prerendering if page template contains %sveltekit.nonce%');
}
}
const { client } = manifest._;
const modulepreloads = new Set(client.imports);
const stylesheets = new Set(client.stylesheets);
const fonts = new Set(client.fonts);
/** @type {Set<string>} */
const link_header_preloads = new Set();
/** @type {Map<string, string>} */
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
const inline_styles = new Map();
let rendered;
const form_value =
action_result?.type === 'success' || action_result?.type === 'failure'
? action_result.data ?? null
: null;
/** @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 !== false && !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) {
if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {
// Can only be the leaf, layouts have a fallback component generated
throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
}
/** @type {Record<string, any>} */
const props = {
stores: {
page: writable(null),
navigating: writable(null),
updated
},
constructors: await Promise.all(branch.map(({ node }) => node.component())),
form: form_value
};
let data = {};
// props_n (instead of props[n]) makes it easy to avoid
// unnecessary updates for layout components
for (let i = 0; i < branch.length; i += 1) {
data = { ...data, ...branch[i].data };
props[`data_${i}`] = data;
}
props.page = {
error,
params: /** @type {Record<string, any>} */ (event.params),
route: event.route,
status,
url: event.url,
data,
form: form_value
};
// 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 });
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;
}
return fetch(info, init);
};
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) {
Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));
}
}
} else {
rendered = { head: '', html: '', css: { code: '', map: null } };
}
let head = '';
let body = rendered.html;
const csp = new Csp(options.csp, {
prerender: !!state.prerendering
});
/** @param {string} path */
const prefixed = (path) => {
if (path.startsWith('/')) {
// Vite makes the start script available through the base path and without it.
// We load it via the base path in order to support remote IDE environments which proxy
// all URLs under the base path during development.
return paths.base + path;
}
return `${assets}/${path}`;
};
if (inline_styles.size > 0) {
const content = Array.from(inline_styles.values()).join('\n');
const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
csp.add_style(content);
head += `\n\t<style${attributes.join('')}>${content}</style>`;
}
for (const dep of stylesheets) {
const path = prefixed(dep);
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 (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(' ')}>`;
}
for (const dep of fonts) {
const path = prefixed(dep);
if (resolve_opts.preload({ type: 'font', path })) {
const ext = dep.slice(dep.lastIndexOf('.') + 1);
const attributes = [
'rel="preload"',
'as="font"',
`type="font/${ext}"`,
`href="${path}"`,
'crossorigin'
];
head += `\n\t\t<link ${attributes.join(' ')}>`;
}
}
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 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: ${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',
`form: ${serialized.form}`,
`error: ${serialized.error}`
];
if (status !== 200) {
hydrate.push(`status: ${status}`);
}
if (options.embedded) {
hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
}
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}`);
}
blocks.push(`Promise.all([
import(${s(prefixed(client.start))}),
import(${s(prefixed(client.app))})
]).then(([kit, app]) => {
kit.start(${args.join(', ')});
});`);
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)
blocks.push(`if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
});
}`);
}
const init_app = `
{
${blocks.join('\n\n\t\t\t\t\t')}
}
`;
csp.add_script(init_app);
body += `\n\t\t\t<script${
csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''
}>${init_app}</script>\n\t\t`;
}
const headers = new Headers({
'x-sveltekit-page': 'true',
'content-type': 'text/html'
});
if (state.prerendering) {
// TODO read headers set with setHeaders and convert into http-equiv where possible
const http_equiv = [];
const csp_headers = csp.csp_provider.get_meta();
if (csp_headers) {
http_equiv.push(csp_headers);
}
if (state.prerendering.cache) {
http_equiv.push(`<meta http-equiv="cache-control" content="${state.prerendering.cache}">`);
}
if (http_equiv.length > 0) {
head = http_equiv.join('\n') + head;
}
} 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(', '));
}
}
// add the content after the script/css links so the link elements are parsed first
head += rendered.head;
const html = options.templates.app({
head,
body,
assets,
nonce: /** @type {string} */ (csp.nonce),
env: public_env
});
// TODO flush chunks as early as we can
const transformed =
(await resolve_opts.transformPageChunk({
html,
done: true
})) || '';
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'
);
}
}
}
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();
},
type: 'bytes'
}),
{
headers: {
'content-type': 'text/html'
}
}
);
}
/**
* 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})`;
}
}
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)));
}
}

View File

@ -0,0 +1,111 @@
import { render_response } from './render.js';
import { load_data, load_server_data } from './load_data.js';
import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
import { get_option } from '../../../utils/options.js';
import { HttpError, Redirect } from '../../control.js';
/**
* @typedef {import('./types.js').Loaded} Loaded
*/
/**
* @param {{
* event: import('@sveltejs/kit').RequestEvent;
* options: import('types').SSROptions;
* manifest: import('@sveltejs/kit').SSRManifest;
* state: import('types').SSRState;
* status: number;
* error: unknown;
* resolve_opts: import('types').RequiredResolveOptions;
* }} opts
*/
export async function respond_with_error({
event,
options,
manifest,
state,
status,
error,
resolve_opts
}) {
// 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 = [];
try {
const branch = [];
const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
const ssr = get_option([default_layout], 'ssr') ?? true;
const csr = get_option([default_layout], 'csr') ?? true;
if (ssr) {
state.error = true;
const server_data_promise = load_server_data({
event,
state,
node: default_layout,
parent: async () => ({}),
track_server_fetches: options.track_server_fetches
});
const server_data = await server_data_promise;
const data = await load_data({
event,
fetched,
node: default_layout,
parent: async () => ({}),
resolve_opts,
server_data_promise,
state,
csr
});
branch.push(
{
node: default_layout,
server_data,
data
},
{
node: await manifest._.nodes[1](), // 1 is always the root error
data: null,
server_data: null
}
);
}
return await render_response({
options,
manifest,
state,
page_config: {
ssr,
csr: get_option([default_layout], 'csr') ?? true
},
status,
error: await handle_error_and_jsonify(event, options, error),
branch,
fetched,
event,
resolve_opts
});
} catch (e) {
// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
// we end up here.
if (e instanceof Redirect) {
return redirect_response(e.status, e.location);
}
return static_error_page(
options,
e instanceof HttpError ? e.status : 500,
(await handle_error_and_jsonify(event, options, e)).message
);
}
}

View File

@ -0,0 +1,103 @@
import { escape_html_attr } from '../../../utils/escape.js';
import { hash } from '../../hash.js';
/**
* Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
*
* The first closes the script element, so everything after is treated as raw HTML.
* The second disables further parsing until `-->`, so the script element might be unexpectedly
* kept open until until an unrelated HTML comment in the page.
*
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
* browsers.
*
* @see tests for unsafe parsing examples.
* @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
* @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
* @see https://github.com/tc39/proposal-json-superset
* @type {Record<string, string>}
*/
const replacements = {
'<': '\\u003C',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
/**
* Generates a raw HTML string containing a safe script element carrying data and associated attributes.
*
* It escapes all the special characters needed to guarantee the element is unbroken, but care must
* be taken to ensure it is inserted in the document at an acceptable position for a script element,
* and that the resulting string isn't further modified.
*
* @param {import('./types.js').Fetched} fetched
* @param {(name: string, value: string) => boolean} filter
* @param {boolean} [prerendering]
* @returns {string} The raw HTML of a script element carrying the JSON payload.
* @example const html = serialize_data('/data.json', null, { foo: 'bar' });
*/
export function serialize_data(fetched, filter, prerendering = false) {
/** @type {Record<string, string>} */
const headers = {};
let cache_control = null;
let age = null;
let varyAny = false;
for (const [key, value] of fetched.response.headers) {
if (filter(key, value)) {
headers[key] = value;
}
if (key === 'cache-control') cache_control = value;
else if (key === 'age') age = value;
else if (key === 'vary' && value.trim() === '*') varyAny = true;
}
const payload = {
status: fetched.response.status,
statusText: fetched.response.statusText,
headers,
body: fetched.response_body
};
const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]);
const attrs = [
'type="application/json"',
'data-sveltekit-fetched',
`data-url=${escape_html_attr(fetched.url)}`
];
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.
if (!prerendering && fetched.method === 'GET' && cache_control && !varyAny) {
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
if (match) {
const ttl = +match[1] - +(age ?? '0');
attrs.push(`data-ttl="${ttl}"`);
}
}
return `<script ${attrs.join(' ')}>${safe_payload}</script>`;
}

View File

@ -0,0 +1,35 @@
import { CookieSerializeOptions } from 'cookie';
import { SSRNode, CspDirectives, ServerDataNode } from 'types';
export interface Fetched {
url: string;
method: string;
request_body?: string | ArrayBufferView | null;
request_headers?: HeadersInit | undefined;
response_body: string;
response: Response;
}
export type Loaded = {
node: SSRNode;
data: Record<string, any> | null;
server_data: ServerDataNode | null;
};
type CspMode = 'hash' | 'nonce' | 'auto';
export interface CspConfig {
mode: CspMode;
directives: CspDirectives;
reportOnly: CspDirectives;
}
export interface CspOpts {
prerender: boolean;
}
export interface Cookie {
name: string;
value: string;
options: CookieSerializeOptions;
}

View File

@ -0,0 +1,511 @@
import { DEV } from 'esm-env';
import { base } from '__sveltekit/paths';
import { is_endpoint_request, render_endpoint } from './endpoint.js';
import { render_page } from './page/index.js';
import { render_response } from './page/render.js';
import { respond_with_error } from './page/respond_with_error.js';
import { is_form_content_type } from '../../utils/http.js';
import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
import {
decode_pathname,
decode_params,
disable_search,
has_data_suffix,
normalize_path,
strip_data_suffix
} from '../../utils/url.js';
import { exec } from '../../utils/routing.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 {
validate_layout_exports,
validate_layout_server_exports,
validate_page_exports,
validate_page_server_exports,
validate_server_exports
} from '../../utils/exports.js';
import { get_option } from '../../utils/options.js';
import { error, 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';
/* global __SVELTEKIT_ADAPTER_NAME__ */
/** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
const default_transform = ({ html }) => html;
/** @type {import('types').RequiredResolveOptions['filterSerializedResponseHeaders']} */
const default_filter = () => false;
/** @type {import('types').RequiredResolveOptions['preload']} */
const default_preload = ({ type }) => type === 'js' || type === 'css';
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 */
const url = new URL(request.url);
if (options.csrf_check_origin) {
const forbidden =
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`);
if (request.headers.get('accept') === 'application/json') {
return json(csrf_error.body, { status: csrf_error.status });
}
return text(csrf_error.body.message, { status: csrf_error.status });
}
}
let decoded;
try {
decoded = decode_pathname(url.pathname);
} catch {
return text('Malformed URI', { status: 400 });
}
/** @type {import('types').SSRRoute | null} */
let route = null;
/** @type {Record<string, string>} */
let params = {};
if (base && !state.prerendering?.fallback) {
if (!decoded.startsWith(base)) {
return text('Not found', { status: 404 });
}
decoded = decoded.slice(base.length) || '/';
}
const is_data_request = has_data_suffix(decoded);
/** @type {boolean[] | undefined} */
let invalidated_data_nodes;
if (is_data_request) {
decoded = strip_data_suffix(decoded) || '/';
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);
}
if (!state.prerendering?.fallback) {
// TODO this could theoretically break — should probably be inside a try-catch
const matchers = await manifest._.matchers();
for (const candidate of manifest._.routes) {
const match = candidate.pattern.exec(decoded);
if (!match) continue;
const matched = exec(match, candidate.params, matchers);
if (matched) {
route = candidate;
params = decode_params(matched);
break;
}
}
}
/** @type {import('types').TrailingSlash | void} */
let trailing_slash = undefined;
/** @type {Record<string, string>} */
const headers = {};
/** @type {Record<string, import('./page/types.js').Cookie>} */
let cookies_to_add = {};
/** @type {import('@sveltejs/kit').RequestEvent} */
const event = {
// @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself
cookies: null,
// @ts-expect-error
fetch: null,
getClientAddress:
state.getClientAddress ||
(() => {
throw new Error(
`${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
);
}),
locals: {},
params,
platform: state.platform,
request,
route: { id: route?.id ?? null },
setHeaders: (new_headers) => {
for (const key in new_headers) {
const lower = key.toLowerCase();
const value = new_headers[key];
if (lower === 'set-cookie') {
throw new Error(
'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies'
);
} else if (lower in headers) {
throw new Error(`"${key}" header is already set`);
} else {
headers[lower] = value;
if (state.prerendering && lower === 'cache-control') {
state.prerendering.cache = /** @type {string} */ (value);
}
}
}
},
url,
isDataRequest: is_data_request,
isSubRequest: state.depth > 0
};
/** @type {import('types').RequiredResolveOptions} */
let resolve_opts = {
transformPageChunk: default_transform,
filterSerializedResponseHeaders: default_filter,
preload: default_preload
};
try {
// determine whether we need to redirect to add/remove a trailing slash
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([
// we use == here rather than === because [undefined] serializes as "[null]"
...route.page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
manifest._.nodes[route.page.leaf]()
]);
if (DEV) {
const layouts = nodes.slice(0, -1);
const page = nodes.at(-1);
for (const layout of layouts) {
if (layout) {
validate_layout_server_exports(
layout.server,
/** @type {string} */ (layout.server_id)
);
validate_layout_exports(
layout.universal,
/** @type {string} */ (layout.universal_id)
);
}
}
if (page) {
validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
validate_page_exports(page.universal, /** @type {string} */ (page.universal_id));
}
}
trailing_slash = get_option(nodes, 'trailingSlash');
} else if (route.endpoint) {
const node = await route.endpoint();
trailing_slash = node.trailingSlash;
if (DEV) {
validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
}
}
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: 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, set_internal } = get_cookies(
request,
url,
trailing_slash ?? 'never'
);
cookies_to_add = new_cookies;
event.cookies = cookies;
event.fetch = create_fetch({
event,
options,
manifest,
state,
get_cookie_header,
set_internal
});
if (state.prerendering && !state.prerendering.fallback) disable_search(url);
const response = await options.hooks.handle({
event,
resolve: (event, opts) =>
resolve(event, opts).then((response) => {
// add headers/cookies here, rather than inside `resolve`, so that we
// can do it once for all responses instead of once per `return`
for (const key in headers) {
const value = headers[key];
response.headers.set(key, /** @type {string} */ (value));
}
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
if (state.prerendering && event.route.id !== null) {
response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
}
return response;
})
});
// respond with 304 if etag matches
if (response.status === 200 && response.headers.has('etag')) {
let if_none_match_value = request.headers.get('if-none-match');
// ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
if (if_none_match_value?.startsWith('W/"')) {
if_none_match_value = if_none_match_value.substring(2);
}
const etag = /** @type {string} */ (response.headers.get('etag'));
if (if_none_match_value === etag) {
const headers = new Headers({ etag });
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
for (const key of [
'cache-control',
'content-location',
'date',
'expires',
'vary',
'set-cookie'
]) {
const value = response.headers.get(key);
if (value) headers.set(key, value);
}
return new Response(undefined, {
status: 304,
headers
});
}
}
// Edge case: If user does `return Response(30x)` in handle hook while processing a data request,
// we need to transform the redirect response to a corresponding JSON response.
if (is_data_request && response.status >= 300 && response.status <= 308) {
const location = response.headers.get('location');
if (location) {
return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location));
}
}
return response;
} catch (e) {
if (e instanceof Redirect) {
const response = is_data_request
? redirect_json_response(e)
: 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));
return response;
}
return await handle_fatal_error(event, options, e);
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('@sveltejs/kit').ResolveOptions} [opts]
*/
async function resolve(event, opts) {
try {
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 = {
transformPageChunk: opts.transformPageChunk || default_transform,
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
preload: opts.preload || default_preload
};
}
if (state.prerendering?.fallback) {
return await render_response({
event,
options,
manifest,
state,
page_config: { ssr: false, csr: true },
status: 200,
error: null,
branch: [],
fetched: [],
resolve_opts
});
}
if (route) {
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
/** @type {Response} */
let response;
if (is_data_request) {
response = await render_data(
event,
route,
options,
manifest,
state,
invalidated_data_nodes,
trailing_slash ?? 'never'
);
} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
response = await render_endpoint(event, await route.endpoint(), state);
} else if (route.page) {
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 {
// a route will always have a page or an endpoint, but TypeScript
// doesn't know that
throw new Error('This should never happen');
}
// 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.error && event.isSubRequest) {
return await fetch(request, {
headers: {
'x-sveltekit-error': 'true'
}
});
}
if (state.error) {
return text('Internal Server Error', {
status: 500
});
}
// if this request came direct from the user, rather than
// via our own `fetch`, render a 404 page
if (state.depth === 0) {
return await respond_with_error({
event,
options,
manifest,
state,
status: 404,
error: new Error(`Not found: ${event.url.pathname}`),
resolve_opts
});
}
if (state.prerendering) {
return text('not found', { status: 404 });
}
// we can't load the endpoint from our own manifest,
// so we need to make an actual HTTP request
return await fetch(request);
} catch (e) {
// TODO if `e` is instead named `error`, some fucked up Vite transformation happens
// and I don't even know how to describe it. need to investigate at some point
// HttpError from endpoint can end up here - TODO should it be handled there instead?
return await handle_fatal_error(event, options, e);
} finally {
event.cookies.set = () => {
throw new Error('Cannot use `cookies.set(...)` after the response has been generated');
};
event.setHeaders = () => {
throw new Error('Cannot use `setHeaders(...)` after the response has been generated');
};
}
}
}

161
node_modules/@sveltejs/kit/src/runtime/server/utils.js generated vendored Normal file
View File

@ -0,0 +1,161 @@
import { DEV } from 'esm-env';
import { json, text } from '../../exports/index.js';
import { coalesce_to_error } from '../../utils/error.js';
import { negotiate } from '../../utils/http.js';
import { HttpError } from '../control.js';
import { fix_stack_trace } from '../shared-server.js';
import { ENDPOINT_METHODS } from '../../constants.js';
/** @param {any} body */
export function is_pojo(body) {
if (typeof body !== 'object') return false;
if (body) {
if (body instanceof Uint8Array) return false;
if (body instanceof ReadableStream) return false;
}
return true;
}
/**
* @param {Partial<Record<import('types').HttpMethod, any>>} mod
* @param {import('types').HttpMethod} method
*/
export function method_not_allowed(mod, method) {
return text(`${method} method not allowed`, {
status: 405,
headers: {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: allowed_methods(mod).join(', ')
}
});
}
/** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
export function allowed_methods(mod) {
const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod);
if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
return allowed;
}
/**
* Return as a response that renders the error.html
*
* @param {import('types').SSROptions} options
* @param {number} status
* @param {string} message
*/
export function static_error_page(options, 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' },
status
});
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {unknown} error
*/
export async function handle_fatal_error(event, options, error) {
error = error instanceof HttpError ? error : coalesce_to_error(error);
const status = error instanceof HttpError ? error.status : 500;
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
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
'application/json',
'text/html'
]);
if (event.isDataRequest || type === 'application/json') {
return json(body, {
status
});
}
return static_error_page(options, status, body.message);
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {import('types').SSROptions} options
* @param {any} error
* @returns {Promise<App.Error>}
*/
export async function handle_error_and_jsonify(event, options, error) {
if (error instanceof HttpError) {
return error.body;
} else {
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'
}
);
}
}
/**
* @param {number} status
* @param {string} location
*/
export function redirect_response(status, location) {
const response = new Response(undefined, {
status,
headers: { location }
});
return response;
}
/**
* @param {import('@sveltejs/kit').RequestEvent} event
* @param {Error & { path: string }} error
*/
export function clarify_devalue_error(event, error) {
if (error.path) {
return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (data${error.path})`;
}
if (error.path === '') {
return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
}
// belt and braces — this should never happen
return error.message;
}
/**
* @param {import('types').ServerDataNode} node
*/
export function stringify_uses(node) {
const uses = [];
if (node.uses && node.uses.dependencies.size > 0) {
uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
}
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');
return `"uses":{${uses.join(',')}}`;
}

View File

@ -0,0 +1,23 @@
/** @type {Record<string, string>} */
export let private_env = {};
/** @type {Record<string, string>} */
export let public_env = {};
/** @param {any} error */
export let fix_stack_trace = (error) => error?.stack;
/** @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 {(error: Error) => string} value */
export function set_fix_stack_trace(value) {
fix_stack_trace = value;
}

16
node_modules/@sveltejs/kit/src/runtime/shared.js generated vendored Normal file
View File

@ -0,0 +1,16 @@
/**
* @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`
);
}
}
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash';