From ee8066fcb29ed1e7e3ab513cabb7997e38c984f2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 9 Feb 2023 03:07:55 -0500 Subject: [PATCH] fix: print useful errors when subscribing to stores during SSR (#8960) * print useful errors when subscribing to stores during SSR * changeset --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/large-toes-fetch.md | 5 +++++ packages/kit/src/runtime/app/stores.js | 21 ++++++++++++++++++--- packages/kit/types/ambient.d.ts | 8 +++++++- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 .changeset/large-toes-fetch.md diff --git a/.changeset/large-toes-fetch.md b/.changeset/large-toes-fetch.md new file mode 100644 index 000000000000..f9695953cbf7 --- /dev/null +++ b/.changeset/large-toes-fetch.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: print useful error when subscribing to SvelteKit's stores at the wrong time during SSR diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js index 5c79a7ad3f94..67727a119d82 100644 --- a/packages/kit/src/runtime/app/stores.js +++ b/packages/kit/src/runtime/app/stores.js @@ -23,7 +23,7 @@ export const getStores = () => { export const page = { /** @param {(value: any) => void} fn */ subscribe(fn) { - const store = getStores().page; + const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page; return store.subscribe(fn); } }; @@ -31,7 +31,7 @@ export const page = { /** @type {typeof import('$app/stores').navigating} */ export const navigating = { subscribe(fn) { - const store = getStores().navigating; + const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating; return store.subscribe(fn); } }; @@ -39,7 +39,7 @@ export const navigating = { /** @type {typeof import('$app/stores').updated} */ export const updated = { subscribe(fn) { - const store = getStores().updated; + const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated; if (browser) { updated.check = store.check; @@ -55,3 +55,18 @@ export const updated = { ); } }; + +/** + * @template {keyof ReturnType} Name + * @param {Name} name + * @returns {ReturnType[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.` + ); + } +} diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index 606475b4c380..5573b72ce59d 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -292,16 +292,22 @@ declare module '$app/stores' { /** * 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. */ export const page: Readable; /** * 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. */ export const navigating: Readable; /** - * 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. + * 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. */ export const updated: Readable & { check(): Promise };