ITADN

'set' on proxy: trap returned falsish for property 'userState:user'

#56OpenZachHandley 创建于 2025-12-22
Z
ZachHandleycommented
Hey there, So according to Nanostores docs, persistentMap / atom should be SSR compatible, but every. single. time. I use them, I get this error. I am running Astro, with Vue, using SSR with Cloudflare, and if *any* component accidentally touches a nanostore's persistentAtom or persistentMap in SSR context, I get this error. It's super frustrating, almost impossible to fix easily, and inconsistent with what I would expect from the docs. ```ts TypeError: 'set' on proxy: trap returned falsish for property 'userState:user' at storeKey (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/@nanostores+persistent@0.10.2_nanostores@0.11.4/node_modules/@nanostores/persistent/index.js:106:35) at store.setKey (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/@nanostores+persistent@0.10.2_nanostores@0.11.4/node_modules/@nanostores/persistent/index.js:111:5) at restore (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/@nanostores+persistent@0.10.2_nanostores@0.11.4/node_modules/@nanostores/persistent/index.js:148:13) at file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/@nanostores+persistent@0.10.2_nanostores@0.11.4/node_modules/@nanostores/persistent/index.js:153:5 at listener (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/nanostores@0.11.4/node_modules/nanostores/lifecycle/index.js:119:19) at object.events.<computed>.reduceRight.shared (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/nanostores@0.11.4/node_modules/nanostores/lifecycle/index.js:16:58) at Array.reduceRight (<anonymous>) at file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/nanostores@0.11.4/node_modules/nanostores/lifecycle/index.js:16:31 at $store.listen (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/nanostores@0.11.4/node_modules/nanostores/lifecycle/index.js:127:9) at Object.subscribe (file:///Users/zach/GitHub/socialaize/node_modules/.pnpm/nanostores@0.11.4/node_modules/nanostores/atom/index.js:71:26) ``` I fixed it previously by using a regular map in SSR contexts, but. I'm not doing anything crazy. ```ts /** * Persistent user state store using nanostores * This is the single source of truth for all user-scoped data * Uses localStorage for fast access (unencrypted) */ // Strictly use persistentMap; SSR is handled by @nanostores/persistent const userStateStore = persistentMap<UserState>('userState:', DEFAULT_USER_STATE, { encode: JSON.stringify, decode: JSON.parse, }); /** * Global singleton composable for managing centralized user state * Provides reactive access to user data with persistence via localStorage and IndexedDB * IndexedDB stores encrypted data for security, localStorage for speed */ export const useUserState = createGlobalState(() => { const state = useStore(userStateStore); /** * Save encrypted copy of user state to IndexedDB * This provides a secure backup of sensitive user data */ async function syncToIndexedDB(state: UserState) { if (typeof window === 'undefined') return; try { const encrypted = await encryptUserState(state); await indexedDBCache.set('userProfiles', 'userState', encrypted); } catch (error) { console.warn('Failed to sync userState to IndexedDB:', error); } } /** * Load encrypted user state from IndexedDB * Used as a fallback or initial load before API hydration */ async function loadFromIndexedDB(): Promise<UserState | null> { if (typeof window === 'undefined') return null; try { const cached = await indexedDBCache.get<string>('userProfiles', 'userState'); if (cached?.data) { return await decryptUserState(cached.data); } } catch (error) { console.warn('Failed to load userState from IndexedDB:', error); } return null; } /** * Hydrates user state from the backend authentication endpoint * First attempts to load from IndexedDB cache, then fetches from API */ const hydrate = async () => { try { // Try loading from IndexedDB first const cachedState = await loadFromIndexedDB(); if (cachedState) { // Populate store with cached data Object.entries(cachedState).forEach(([key, value]) => { if (value !== null && value !== undefined) { userStateStore.setKey(key as keyof UserState, value); } }); } // Then fetch fresh data from API const res = await fetch('/api/auth/verify.json', { credentials: 'include' }); const data = await res.json(); if (data.success && data.data?.user) { setUser(data.data.user); if (data.data.session) setSession(data.data.session); } } catch (error) { console.warn('Failed to hydrate user state:', error); } }; /** * Sets the user in the state store */ const setUser = (user: Models.User<Models.Preferences>) => { userStateStore.setKey('user', user); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the session in the state store */ const setSession = (session: Models.Session) => { userStateStore.setKey('session', session); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the selected team ID in the state store */ const setSelectedTeam = (teamId: string) => { userStateStore.setKey('selectedTeam', teamId); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the billing data in the state store */ const setBilling = (billing: Billing) => { userStateStore.setKey('billing', billing); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the billing bonus data in the state store */ const setBillingBonus = (bonus: BillingBonus) => { userStateStore.setKey('billingBonus', bonus); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the AI credits count in the state store */ const setAiCredits = (credits: number) => { userStateStore.setKey('aiCredits', credits); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the OAuth accounts array in the state store */ const setOAuthAccounts = (accounts: OAuthAccount[]) => { userStateStore.setKey('oauthAccounts', accounts); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the workflows array in the state store */ const setWorkflows = (workflows: Workflow[]) => { userStateStore.setKey('workflows', workflows); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Sets the active workflow ID in the state store */ const setActiveWorkflow = (workflowId: string | null) => { userStateStore.setKey('activeWorkflow', workflowId); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; /** * Clears all user state and resets to default values */ const clear = () => { Object.entries(DEFAULT_USER_STATE).forEach(([key, value]) => { userStateStore.setKey(key as keyof UserState, value); }); syncToIndexedDB(userStateStore.get()).catch(console.warn); }; return { state, hydrate, setUser, setSession, setSelectedTeam, setBilling, setBillingBonus, setAiCredits, setOAuthAccounts, setWorkflows, setActiveWorkflow, clear, }; }); ``` So, any advice or pointing in the right direction would be awesome
3 条评论