diff --git a/.changeset/quiet-pumas-dance.md b/.changeset/quiet-pumas-dance.md new file mode 100644 index 000000000000..a91805ee1859 --- /dev/null +++ b/.changeset/quiet-pumas-dance.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: allow handle functions composed with sequence to run without a request store diff --git a/packages/kit/src/exports/hooks/sequence.js b/packages/kit/src/exports/hooks/sequence.js index 892dc973fbd4..e1715085ae9e 100644 --- a/packages/kit/src/exports/hooks/sequence.js +++ b/packages/kit/src/exports/hooks/sequence.js @@ -1,9 +1,14 @@ /** @import { Handle, RequestEvent, ResolveOptions } from '@sveltejs/kit' */ +/** @import { RecordSpan } from 'types' */ import { merge_tracing, - get_request_store, + try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server'; +import { noop_span } from '../../runtime/telemetry/noop.js'; + +/** @type {RecordSpan} */ +const noop_record_span = ({ fn }) => fn(noop_span); /** * A helper function for sequencing multiple `handle` calls in a middleware-like manner. @@ -80,11 +85,11 @@ import { */ export function sequence(...handlers) { const length = handlers.length; - if (!length) return ({ event, resolve }) => resolve(event); return ({ event, resolve }) => { - const { state } = get_request_store(); - return apply_handle(0, event, {}); + const store = try_get_request_store(); + const record_span = store ? store.state.tracing.record_span : noop_record_span; + return apply_handle(0, event, undefined); /** * @param {number} i @@ -93,51 +98,47 @@ export function sequence(...handlers) { * @returns {Promise} */ function apply_handle(i, event, parent_options) { + if (i === length) return resolve(event, parent_options); + const handle = handlers[i]; - return state.tracing.record_span({ + return record_span({ name: `sveltekit.handle.sequenced.${handle.name ? handle.name : i}`, attributes: {}, fn: async (current) => { const traced_event = merge_tracing(event, current); - return await with_request_store({ event: traced_event, state }, () => - handle({ - event: traced_event, - resolve: (event, options) => { - /** @type {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; - }; + return await with_request_store( + store && { event: traced_event, state: store.state }, + () => + handle({ + event: traced_event, + resolve: (event, options) => { + /** @type {ResolveOptions} */ + const merged = { + transformPageChunk: + options?.transformPageChunk || parent_options?.transformPageChunk + ? async ({ html, done }) => { + if (options?.transformPageChunk) { + html = (await options.transformPageChunk({ html, done })) ?? ''; + } - /** @type {ResolveOptions['filterSerializedResponseHeaders']} */ - const filterSerializedResponseHeaders = - parent_options?.filterSerializedResponseHeaders ?? - options?.filterSerializedResponseHeaders; + if (parent_options?.transformPageChunk) { + html = + (await parent_options.transformPageChunk({ html, done })) ?? ''; + } - /** @type {ResolveOptions['preload']} */ - const preload = parent_options?.preload ?? options?.preload; + return html; + } + : undefined, + filterSerializedResponseHeaders: + parent_options?.filterSerializedResponseHeaders ?? + options?.filterSerializedResponseHeaders, + preload: parent_options?.preload ?? options?.preload + }; - return i < length - 1 - ? apply_handle(i + 1, event, { - transformPageChunk, - filterSerializedResponseHeaders, - preload - }) - : resolve(event, { - transformPageChunk, - filterSerializedResponseHeaders, - preload - }); - } - }) + return apply_handle(i + 1, event, merged); + } + }) ); } }); diff --git a/packages/kit/src/exports/hooks/sequence.spec.js b/packages/kit/src/exports/hooks/sequence.spec.js index 7fe3afa49bdc..df8e889c0323 100644 --- a/packages/kit/src/exports/hooks/sequence.spec.js +++ b/packages/kit/src/exports/hooks/sequence.spec.js @@ -1,31 +1,23 @@ /** @import { RequestEvent } from '@sveltejs/kit' */ -/** @import { RequestState } from 'types' */ -import { assert, expect, test, vi } from 'vitest'; +import { assert, expect, test } from 'vitest'; import { sequence } from './sequence.js'; -import { noop_span } from '../../runtime/telemetry/noop.js'; - -const dummy_event = vi.hoisted( - () => - /** @type {RequestEvent} */ ({ - tracing: { - root: {} - } - }) -); - -vi.mock(import('@sveltejs/kit/internal/server'), async (actualPromise) => { - const actual = await actualPromise(); - return { - ...actual, - get_request_store: () => ({ - event: dummy_event, - state: /** @type {RequestState} */ ({ - tracing: { - record_span: ({ fn }) => fn(noop_span) - } - }) - }) - }; + +const dummy_event = /** @type {RequestEvent} */ ({ + tracing: { + root: {} + } +}); + +test('runs without a request store', async () => { + const handler = sequence( + async ({ event, resolve }) => resolve(event), + async ({ event, resolve }) => resolve(event) + ); + + const response = new Response(); + const event = /** @type {RequestEvent} */ ({}); + + assert.equal(await handler({ event, resolve: () => Promise.resolve(response) }), response); }); test('applies handlers in sequence', async () => {