Proposal for Svelte 5: Universal Reactivity & Safe SSR Global State? #12947
Replies: 3 comments 3 replies
-
Currently usable sample implementationI'll probably be turning this into some sort of
export const request_store = {
getStore(): symbol | undefined {
throw new Error("AsyncLocalStorage not initialized")
}
}
import { browser } from "$app/environment"
import { request_store } from "$lib/request"
export const app_state = $state(ssr_proxy({
counter: 0,
}))
function ssr_proxy<T extends object>(initial: T) {
if (browser) return { inner: initial }
const map = new WeakMap<symbol, T>()
return {
get inner() {
const req_symbol = request_store.getStore()
if (!req_symbol) throw new Error("Request symbol not initialized")
let value = map.get(req_symbol)
if (!value) {
value = structuredClone(initial)
map.set(req_symbol, value)
}
return value
}
}
}
import { AsyncLocalStorage } from "node:async_hooks"
import { request_store } from "$lib/request"
import { app_state } from "$lib/app_state.svelte"
// app_state.inner -> Error: AsyncLocalStorage not initialized
const local_storage = new AsyncLocalStorage<symbol>()
request_store.getStore = () => local_storage.getStore()
export async function handle({
event,
resolve,
}){
// app_state.inner -> Error: Request store not initialized
return local_storage.run(Symbol(), async () => {
// should always be 0 at the start of a new request
console.log(app_state.inner.counter)
app_state.inner.counter += 1 // works, unique per request
})
}
<script lang="ts">
import { app_state } from "$lib/app_state.svelte"
</script>
<!-- will be 1 on load, and then switch to 0 on client-side hydration -->
{ app_state.inner.counter } |
Beta Was this translation helpful? Give feedback.
-
This is great. I would very much love to not have to wrap everything in |
Beta Was this translation helpful? Give feedback.
-
My two cents on this:
|
Beta Was this translation helpful? Give feedback.
-
Important
I have managed to implement most of this proposal in user-land: Check out the
npm
package i've created to help solve this problem https://github.com/AlbertMarashi/safe-ssrContext
The unsafe behavior of global variables during SSR (particularly global stores) has been one of the most persistently complained about issues with SvelteKit (sveltejs/kit#4339).
The way stores are currently implemented result in state being leaked across requests which presents a security risk especially for users that are unaware of this difference of behavior in client-side vs server-side apps.
Solution
Using
AsyncLocalStorage
(node:async_hooks
) to we can implement cross-request isolation for stores, which is supported by every major server runtime.AsyncLocalStorage
is not requirednode:async_hooks
node:async_hooks
node:async_hooks
node:async_hooks
node:async_hooks
Rough Proposal / Idea
My idea is that we might be able to use this feature to have Svelte 5 runes (eg:
$state
) work in any javascript modules, including in+page.ts
,+layout.ts
,[Component].svelte
, or any javascript/typescript files such that the data within is guaranteed to be uniquely isolated between requests.Example usage
In practice, I don't expect anything much to change in behavior, except massive improvements in developer ergonomics as it would bridge the gap between client-side and server-side behavior, and make everything a bit more universal.
$lib/foo.ts
+page.ts
+page.svelte
Rough internal mechanism
svelte/internal/state.ts
hooks.server.ts
example to explain the point (although this would be moved into some other internal server-side only module)Benefits
Cannot access 'x' before initialization
Potential caveats & Open Questions
$state
(when used in normal ts/js files) should only support proxy-able types (primitive types like strings and numbers can't be proxied) Pretty sure this is already a limitation with.svelte.ts
files anyways[name].svelte.(ts|js)
files be deprecated?$derived
and other runes?Allowing classes & other non-structured-clonable types to be cloned
Related
Beta Was this translation helpful? Give feedback.
All reactions