UNPKG

@sveltejs/kit

Version:

SvelteKit is the fastest way to build Svelte apps

179 lines (154 loc) • 5 kB
/** @import { PromiseWithResolvers } from '../../utils/promise.js' */ import { with_resolvers } from '../../utils/promise.js'; import { IN_WEBCONTAINER } from './constants.js'; 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_env } from '../../utils/env.js'; import { format_server_error } from './utils.js'; import { set_read_implementation, set_manifest } from '__sveltekit/server'; import { set_app } from './app.js'; /** @type {Promise<any>} */ let init_promise; /** @type {Promise<void> | null} */ let current = null; 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; // Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_store` // in `src/exports/internal/event.js` and handle only one request at a time. if (IN_WEBCONTAINER) { const respond = this.respond.bind(this); /** @type {typeof respond} */ this.respond = async (...args) => { const { promise, resolve } = /** @type {PromiseWithResolvers<void>} */ (with_resolvers()); const previous = current; current = promise; await previous; return respond(...args).finally(resolve); }; } set_manifest(manifest); } /** * @param {import('@sveltejs/kit').ServerInitOptions} opts */ async init({ env, read }) { // 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 const { env_public_prefix, env_private_prefix } = this.#options; set_private_env(filter_env(env, env_private_prefix, env_public_prefix)); set_public_env(filter_env(env, env_public_prefix, env_private_prefix)); if (read) { // Wrap the read function to handle MaybePromise<ReadableStream> // and ensure the public API stays synchronous /** @param {string} file */ const wrapped_read = (file) => { const result = read(file); if (result instanceof ReadableStream) { return result; } else { return new ReadableStream({ async start(controller) { try { const stream = await Promise.resolve(result); if (!stream) { controller.close(); return; } const reader = stream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; controller.enqueue(value); } controller.close(); } catch (error) { controller.error(error); } } }); } }; set_read_implementation(wrapped_read); } // During DEV and for some adapters this function might be called in quick succession, // so we need to make sure we're not invoking this logic (most notably the init hook) multiple times await (init_promise ??= (async () => { try { const module = await get_hooks(); this.#options.hooks = { handle: module.handle || (({ event, resolve }) => resolve(event)), handleError: module.handleError || (({ status, error, event }) => { const error_message = format_server_error( status, /** @type {Error} */ (error), event ); console.error(error_message); }), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), handleValidationError: module.handleValidationError || (({ issues }) => { console.error('Remote function schema validation failed:', issues); return { message: 'Bad Request' }; }), reroute: module.reroute || (() => {}), transport: module.transport || {} }; set_app({ decoders: module.transport ? Object.fromEntries(Object.entries(module.transport).map(([k, v]) => [k, v.decode])) : {} }); if (module.init) { await module.init(); } } catch (e) { if (DEV) { this.#options.hooks = { handle: () => { throw e; }, handleError: ({ error }) => console.error(error), handleFetch: ({ request, fetch }) => fetch(request), handleValidationError: () => { return { message: 'Bad Request' }; }, reroute: () => {}, transport: {} }; set_app({ decoders: {} }); } else { throw e; } } })()); } /** * @param {Request} request * @param {import('types').RequestOptions} options */ async respond(request, options) { return respond(request, this.#options, this.#manifest, { ...options, error: false, depth: 0 }); } }