/** * A helper function for sequencing multiple `handle` calls in a middleware-like manner. * The behavior for the `handle` options is as follows: * - `transformPageChunk` is applied in reverse order and merged * - `preload` is applied in forward order, the first option "wins" and no `preload` options after it are called * - `filterSerializedResponseHeaders` behaves the same as `preload` * * ```js * /// file: src/hooks.server.js * import { sequence } from '@sveltejs/kit/hooks'; * * /// type: import('@sveltejs/kit').Handle * async function first({ event, resolve }) { * console.log('first pre-processing'); * const result = await resolve(event, { * transformPageChunk: ({ html }) => { * // transforms are applied in reverse order * console.log('first transform'); * return html; * }, * preload: () => { * // this one wins as it's the first defined in the chain * console.log('first preload'); * } * }); * console.log('first post-processing'); * return result; * } * * /// type: import('@sveltejs/kit').Handle * async function second({ event, resolve }) { * console.log('second pre-processing'); * const result = await resolve(event, { * transformPageChunk: ({ html }) => { * console.log('second transform'); * return html; * }, * preload: () => { * console.log('second preload'); * }, * filterSerializedResponseHeaders: () => { * // this one wins as it's the first defined in the chain * console.log('second filterSerializedResponseHeaders'); * } * }); * console.log('second post-processing'); * return result; * } * * export const handle = sequence(first, second); * ``` * * The example above would print: * * ``` * first pre-processing * first preload * second pre-processing * second filterSerializedResponseHeaders * second transform * first transform * second post-processing * first post-processing * ``` * * @param {...import('@sveltejs/kit').Handle} handlers The chain of `handle` functions * @returns {import('@sveltejs/kit').Handle} */ export function sequence(...handlers) { const length = handlers.length; if (!length) return ({ event, resolve }) => resolve(event); return ({ event, resolve }) => { return apply_handle(0, event, {}); /** * @param {number} i * @param {import('@sveltejs/kit').RequestEvent} event * @param {import('@sveltejs/kit').ResolveOptions | undefined} parent_options * @returns {import('types').MaybePromise} */ function apply_handle(i, event, parent_options) { const handle = handlers[i]; return handle({ event, resolve: (event, options) => { /** @type {import('@sveltejs/kit').ResolveOptions['transformPageChunk']} */ const transformPageChunk = async ({ html, done }) => { if (options?.transformPageChunk) { html = (await options.transformPageChunk({ html, done })) ?? ''; } if (parent_options?.transformPageChunk) { html = (await parent_options.transformPageChunk({ html, done })) ?? ''; } return html; }; /** @type {import('@sveltejs/kit').ResolveOptions['filterSerializedResponseHeaders']} */ const filterSerializedResponseHeaders = parent_options?.filterSerializedResponseHeaders ?? options?.filterSerializedResponseHeaders; /** @type {import('@sveltejs/kit').ResolveOptions['preload']} */ const preload = parent_options?.preload ?? options?.preload; return i < length - 1 ? apply_handle(i + 1, event, { transformPageChunk, filterSerializedResponseHeaders, preload }) : resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload }); } }); } }; }