Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions packages/kit/src/exports/hooks/sequence.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/** @import { RequestEvent } from '@sveltejs/kit' */
/** @import { Handle, ResolveOptions } from '@sveltejs/kit/hooks' */
import {
merge_tracing,
get_request_store,
record_span,
with_request_store
} from '@sveltejs/kit/internal/server';
import { merge_tracing, record_span, with_event } from '@sveltejs/kit/internal/server';

/**
* A helper function for sequencing multiple `handle` calls in a middleware-like manner.
Expand Down Expand Up @@ -85,7 +80,6 @@ export function sequence(...handlers) {
if (!length) return ({ event, resolve }) => resolve(event);

return ({ event, resolve }) => {
const { state } = get_request_store();
return apply_handle(0, event, {});

/**
Expand All @@ -102,7 +96,7 @@ export function sequence(...handlers) {
attributes: {},
fn: async (current) => {
const traced_event = merge_tracing(event, current);
return await with_request_store({ event: traced_event, state }, () =>
return await with_event(traced_event, () =>
handle({
event: traced_event,
resolve: (event, options) => {
Expand Down
6 changes: 1 addition & 5 deletions packages/kit/src/exports/hooks/sequence.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/** @import { RequestEvent } from '@sveltejs/kit' */
/** @import { RequestState } from 'types' */
import { assert, expect, test, vi } from 'vitest';
import { sequence } from './sequence.js';

Expand All @@ -13,10 +12,7 @@ vi.mock(import('@sveltejs/kit/internal/server'), async (actualPromise) => {
const actual = await actualPromise();
return {
...actual,
get_request_store: () => ({
event: /** @type {any} */ (dummy_event),
state: /** @type {RequestState} */ (/** @type {unknown} */ ({}))
})
get_event: () => /** @type {any} */ (dummy_event)
};
});

Expand Down
64 changes: 39 additions & 25 deletions packages/kit/src/exports/internal/server/event.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @import { Cookies, RequestEvent as Interface } from '@sveltejs/kit' */
/** @import { RequestState, RequestStore } from 'types' */
/** @import { RequestState } from 'types' */
/** @import { AsyncLocalStorage } from 'node:async_hooks' */
import { DEV } from 'esm-env';
import { IN_WEBCONTAINER } from '../../../constants.js';
Expand All @@ -15,6 +15,9 @@ export const RENDER = 16;
/** The kinds on the stack, kept under a symbol so it is not part of the public shape */
export const CONTEXT = Symbol('sveltekit.context');

/** The request state, shared by every view of the request */
const STATE = Symbol('sveltekit.state');

/** What a query view copies its page fields from */
const NO_PAGE = /** @type {Interface} */ ({});

Expand Down Expand Up @@ -135,11 +138,15 @@ export class RequestEvent {
/** @type {number} */
[CONTEXT];

/** @type {RequestState} */
[STATE];

/**
* @param {Interface} source
* @param {number} flags
* @param {RequestState} state
*/
constructor(source, flags) {
constructor(source, flags, state) {
this.cookies = source.cookies;
this.fetch = source.fetch;
this.getClientAddress = source.getClientAddress;
Expand All @@ -159,6 +166,7 @@ export class RequestEvent {
this.isRemoteRequest = source.isRemoteRequest;
this.tracing = source.tracing;
this[CONTEXT] = flags;
this[STATE] = state;
}

/**
Expand All @@ -169,7 +177,7 @@ export class RequestEvent {
* @returns {RequestEvent}
*/
static create(fields, state) {
const event = new RequestEvent(/** @type {Interface} */ (fields), 0);
const event = new RequestEvent(/** @type {Interface} */ (fields), 0, state);

event.setHeaders = (new_headers) => set_headers(state, new_headers);

Expand All @@ -182,9 +190,15 @@ export class RequestEvent {
* @returns {RequestEvent}
*/
static from(event) {
return event instanceof RequestEvent
? event
: new RequestEvent(event, /** @type {Partial<RequestEvent>} */ (event)[CONTEXT] ?? 0);
if (event instanceof RequestEvent) return event;

const { [CONTEXT]: flags = 0, [STATE]: state } = /** @type {Partial<RequestEvent>} */ (event);
return new RequestEvent(event, flags, /** @type {RequestState} */ (state));
}

/** What the runtime knows about the request, shared by every view of it */
get state() {
return this[STATE];
}

/** Inside a `query` function, however deep */
Expand Down Expand Up @@ -221,7 +235,7 @@ export class RequestEvent {
*/
clone(kind, overrides) {
const flags = this[CONTEXT] | kind;
const view = new (flags & QUERY ? QueryEvent : RequestEvent)(this, flags);
const view = new (flags & QUERY ? QueryEvent : RequestEvent)(this, flags, this[STATE]);

if (kind & (QUERY | PRERENDER | FORM | COMMAND)) {
view.cookies = new RemoteCookies(this.cookies, view.read_only);
Expand All @@ -246,10 +260,10 @@ for (const property of /** @type {const} */ (['url', 'params', 'route'])) {
});
}

/** @type {RequestStore | null} */
let sync_store = null;
/** @type {RequestEvent | null} */
let sync_event = null;

/** @type {AsyncLocalStorage<RequestStore | null> | null} */
/** @type {AsyncLocalStorage<RequestEvent | null> | null} */
let als;

import('node:async_hooks')
Expand All @@ -269,7 +283,7 @@ import('node:async_hooks')
* @returns {Interface}
*/
export function getRequestEvent() {
const event = try_get_request_store()?.event;
const event = try_get_event();

if (!event) {
let message =
Expand All @@ -286,42 +300,42 @@ export function getRequestEvent() {
return event;
}

export function get_request_store() {
const result = try_get_request_store();
if (!result) {
let message = 'Could not get the request store.';
export function get_event() {
const event = try_get_event();
if (!event) {
let message = 'Could not get the request event.';

if (als) {
message += ' This is an internal error.';
} else {
message +=
' In environments without `AsyncLocalStorage`, the request store (used by e.g. remote functions) must be accessed synchronously, not after an `await`.' +
' In environments without `AsyncLocalStorage`, the request event (used by e.g. remote functions) must be accessed synchronously, not after an `await`.' +
' If it was accessed synchronously then this is an internal error.';
}

throw new Error(message);
}
return result;
return event;
}

export function try_get_request_store() {
return sync_store ?? als?.getStore() ?? null;
export function try_get_event() {
return sync_event ?? als?.getStore() ?? null;
}

/**
* @template T
* @param {RequestStore | null} store
* @param {RequestEvent | null} event
* @param {() => T} fn
*/
export function with_request_store(store, fn) {
export function with_event(event, fn) {
try {
sync_store = store;
return als ? als.run(store, fn) : fn();
sync_event = event;
return als ? als.run(event, fn) : fn();
} finally {
// Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_store`
// Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_event`
// and handle only one request at a time in `src/runtime/server/index.js`.
if (!IN_WEBCONTAINER) {
sync_store = null;
sync_event = null;
}
}
}
8 changes: 5 additions & 3 deletions packages/kit/src/exports/internal/server/event.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @import { RequestEvent as Interface } from '@sveltejs/kit' */
/** @import { RequestState } from 'types' */
import { assert, expect, test } from 'vitest';
import { RequestEvent, CONTEXT, QUERY, COMMAND, RENDER } from './event.js';
import { RequestEvent, QUERY, COMMAND, RENDER } from './event.js';

function root() {
return new RequestEvent(
Expand All @@ -15,7 +15,8 @@ function root() {
tracing: { enabled: false }
})
),
0
0,
/** @type {any} */ ({ remote: {} })
);
}

Expand Down Expand Up @@ -66,7 +67,8 @@ test('views share the request data and own nothing else', () => {
const event = base.clone(QUERY);

assert.strictEqual(event.locals, base.locals);
assert.deepEqual(Object.getOwnPropertySymbols(event), [CONTEXT]);
assert.equal(Object.getOwnPropertySymbols(event).length, 2);
assert.strictEqual(event.state, base.state);
assert.isFalse(Object.hasOwn(event, 'url'));
});

Expand Down
10 changes: 5 additions & 5 deletions packages/kit/src/exports/internal/server/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** @import { Span } from '@opentelemetry/api' */
/** @import { RequestEvent as Interface } from '@sveltejs/kit' */
import { RequestEvent, try_get_request_store } from './event.js';
import { RequestEvent, try_get_event } from './event.js';

export function get_origin() {
// `request.url` rather than `event.url`, which throws inside queries
const request = try_get_request_store()?.event.request;
const request = try_get_event()?.request;
return request && new URL(request.url).origin;
}

Expand All @@ -18,10 +18,10 @@ export function merge_tracing(event, current) {
}

export {
with_request_store,
with_event,
getRequestEvent,
get_request_store,
try_get_request_store,
get_event,
try_get_event,
RequestEvent,
CONTEXT,
QUERY,
Expand Down
18 changes: 9 additions & 9 deletions packages/kit/src/runtime/app/paths/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { base, assets, relative } from './internal/server.js';
import { resolve_route, find_route } from '../../../utils/routing.js';
import { decode_pathname } from '../../../utils/url.js';
import { add_data_suffix } from '../../../pathname.js';
import { try_get_request_store } from '@sveltejs/kit/internal/server';
import { try_get_event } from '@sveltejs/kit/internal/server';
import { manifest } from '../../server/internal.js';
import { get_hooks } from '<sveltekit:generated>/server.js';
import { DEV } from 'esm-env';
Expand Down Expand Up @@ -39,14 +39,14 @@ export function resolve(id, params) {
}

if (relative) {
const store = try_get_request_store();
const event = try_get_event();

if (store && !store.state.prerendering?.fallback) {
if (event && !event.state.prerendering?.fallback) {
// the relative path depth must reflect the URL the browser is actually at, which
// for a data request includes the `__data.json` suffix that was stripped during routing
const pathname = store.event.isDataRequest
? add_data_suffix(store.event.url.pathname)
: store.event.url.pathname;
const pathname = event.isDataRequest
? add_data_suffix(event.url.pathname)
: event.url.pathname;
const after_base = pathname.slice(base.length);
const segments = after_base.split('/').slice(2);
const prefix = segments.map(() => '..').join('/') || '.';
Expand All @@ -60,10 +60,10 @@ export function resolve(id, params) {

/** @type {typeof import('./client.js').match} */
export async function match(url) {
const store = try_get_request_store();
const event = try_get_event();

if (typeof url === 'string') {
const origin = store?.event.url.origin ?? 'a://a';
const origin = event?.url.origin ?? 'a://a';
url = new URL(url, origin);
}

Expand All @@ -73,7 +73,7 @@ export async function match(url) {

try {
resolved_path = decode_pathname(
(await reroute?.({ url: new URL(url), fetch: store?.event.fetch ?? fetch })) ?? url.pathname
(await reroute?.({ url: new URL(url), fetch: event?.fetch ?? fetch })) ?? url.pathname
);
} catch {
return null;
Expand Down
8 changes: 3 additions & 5 deletions packages/kit/src/runtime/app/server/remote/command.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @import { RemoteCommand } from '$app/server' */
/** @import { MaybePromise, RemoteCommandInternals } from 'types' */
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
import { get_request_store, COMMAND } from '@sveltejs/kit/internal/server';
import { get_event, COMMAND } from '@sveltejs/kit/internal/server';
import { create_validator, run_remote_function } from './shared.js';
import { MUTATIVE_METHODS } from '../../../../constants.js';

Expand Down Expand Up @@ -63,7 +63,7 @@ export function command(validate_or_fn, maybe_fn) {

/** @type {RemoteCommand<Input, Output> & { __: RemoteCommandInternals }} */
const wrapper = (arg) => {
const { event, state } = get_request_store();
const event = get_event();
const nested = event.read_only;

if (nested || !MUTATIVE_METHODS.includes(event.request.method)) {
Expand All @@ -78,9 +78,7 @@ export function command(validate_or_fn, maybe_fn) {
throw new Error(`Cannot call a command (${__.name}) during server-side rendering`);
}

const promise = Promise.resolve(
run_remote_function(event, state, COMMAND, () => validate(arg), fn)
);
const promise = Promise.resolve(run_remote_function(event, COMMAND, () => validate(arg), fn));

// @ts-expect-error
promise.updates = () => {
Expand Down
Loading
Loading