Store 状态与持久化
Store 状态与持久化的安装方式、公开接口、使用示例与当前约束。
A flexible, TypeScript-first state management library with pluggable storage backends and middleware support.
Features
- Redux-like Architecture: Familiar state management patterns with state, mutations, actions, and getters
- Pluggable Storage Backends: Use the included memory backend, inject platform adapters such as
@quajs/store-webor@quajs/store-node, or create custom backends - Middleware System: Add encryption, compression, logging, validation, and more
- Snapshot System: Save and restore complete application state
- TypeScript-First: Full type safety and excellent IDE support
- Multiple Store Management: Create and manage multiple stores with ease
- Universal: Works in both browser and Node.js environments (backend-dependent)
Installation
Bashnpm install @quajs/store# orpnpm add @quajs/store
Quick Start
Basic Store Creation
TYPESCRIPTimport { createStore } from '@quajs/store'const gameStore = createStore({ name: 'gameState', state: { playerName: '', level: 1, score: 0 }, mutations: { setPlayerName: (state, name: string) => { state.playerName = name }, levelUp: (state) => { state.level += 1 }, addScore: (state, points: number) => { state.score += points } }, actions: { async startNewGame({ commit }, playerName: string) { commit('setPlayerName', playerName) commit('levelUp') // Async operations like API calls can go here } }, getters: { playerInfo: state => `${state.playerName} - Level ${state.level}`, isHighScore: state => state.score > 10000 }})// Use the storegameStore.commit('setPlayerName', 'Alice')await gameStore.dispatch('startNewGame', 'Bob')const playerInfo = gameStore.getters.playerInfo // "Bob - Level 1"
Storage Backends
Default Memory Backend
By default, store snapshots and save slots use the platform-neutral in-memory backend. This keeps @quajs/store free of Web APIs. Applications that need durable saves must inject a storage backend.
TYPESCRIPTconst store = createStore({ name: 'myStore', state: { data: 'kept in memory unless storage is configured' }})
Web IndexedDB Backend
Browser persistence lives in the Web adapter package and is injected through the same storage contract:
TYPESCRIPTimport { configureStorage } from '@quajs/store'import { createWebStoreStorage } from '@quajs/store-web'configureStorage(createWebStoreStorage({ dbName: 'MyGameSaves' }))
Node .quastore File Backend
Node persistence lives in @quajs/store-node. It writes binary .quastore files and encrypts them by default with a user-provided key.
TYPESCRIPTimport { configureStorage } from '@quajs/store'import { createNodeStoreStorage } from '@quajs/store-node'configureStorage(createNodeStoreStorage({ rootDir: './saves', encryption: { key: process.env.QUASTORE_KEY }}))
If encryption.key is omitted, the backend reads QUASTORE_KEY. To write plaintext .quastore files for development only, pass encryption: false.
Custom Storage Backend
You can specify a different storage backend:
TYPESCRIPTimport { createStore, MemoryBackend } from '@quajs/store'// Use memory storage (data lost on app close)const tempStore = createStore({ name: 'tempStore', state: { temp: 'data' }, storage: { backend: MemoryBackend }})// Custom backend with optionsconst customStore = createStore({ name: 'customStore', state: { data: 'value' }, storage: { backend: { driver: FileSystemBackend, options: { basePath: './saves' } } }})
Creating Custom Backends
Implement the StorageBackend interface:
TYPESCRIPTimport { QuaGameSavePreviewRecord, QuaGameSaveSlotIndex, QuaGameSaveSlotPayload, QuaSnapshot, StorageBackend,} from '@quajs/store'class FileSystemBackend implements StorageBackend { constructor(private basePath: string) {} async saveSnapshot(snapshot: QuaSnapshot): Promise<void> { // Implement file system storage } async getSnapshot(id: string): Promise<QuaSnapshot | undefined> { // Implement file system retrieval } // ... implement other required methods}
Middleware System
Middleware allows you to intercept and modify data during storage operations:
Middleware Example
TYPESCRIPTimport { createStore, StorageMiddleware } from '@quajs/store'class AuditMiddleware implements StorageMiddleware { beforeWrite(key, value) { return { ...value, metadata: { ...value.metadata, updatedBy: 'player-session', }, } }}const secureStore = createStore({ name: 'secureStore', state: { sensitiveData: 'secret' }, storage: { middlewares: [ new AuditMiddleware() ] }})
Custom Middleware
TYPESCRIPTimport { StorageMiddleware } from '@quajs/store'class TimestampMiddleware implements StorageMiddleware { async beforeWrite(key: string, value: any): Promise<any> { return { ...value, _timestamp: Date.now() } } async afterRead(key: string, value: any): Promise<any> { return { ...value, _readAt: Date.now() } }}
Snapshot System
Save and restore complete application state:
TYPESCRIPT// Single-store snapshotsimport { QuaStoreManager } from '@quajs/store'const snapshotId = await store.snapshot('save-point-1')await store.restore(snapshotId)// Manager single-store snapshotsconst progressionSnapshotId = await QuaStoreManager.snapshotStore('progression', 'day-3')await QuaStoreManager.restoreStore('progression', progressionSnapshotId, { force: true })// Scoped snapshots for selected storesconst scopedSnapshotId = await QuaStoreManager.snapshotStores(['engine', 'progression'], 'checkpoint-1')await QuaStoreManager.restoreStores(scopedSnapshotId, { force: true })// Global snapshots for all registered storesconst globalSnapshotId = await QuaStoreManager.snapshotAll('checkpoint-1')await QuaStoreManager.restoreAll(globalSnapshotId, { force: true })// Unified scoped APIconst allSnapshotId = await QuaStoreManager.snapshot({ scope: 'all', id: 'autosave' })await QuaStoreManager.restore(allSnapshotId, { force: true })// List all snapshotsconst snapshots = await QuaStoreManager.listSnapshots()
State Serialization
Store state serialization is pluggable. The default serializer keeps the current JSON clone behavior, while custom serializers can encode state types such as Map, Date, or domain classes before snapshots and save slots are written.
TYPESCRIPTimport { configureSerialization, createStore, QuaStateSerializer } from '@quajs/store'const mapSerializer: QuaStateSerializer = { serialize(state) { return { ...state, values: Array.from(state.values.entries()) } }, deserialize(serializedState) { return { ...serializedState, values: new Map(serializedState.values) } }}// Per-store serializerconst growthStore = createStore({ name: 'growth', state: { values: new Map([['charm', 1]]) }, serializer: mapSerializer})// Global serializer for stores created after this callconfigureSerialization(mapSerializer)
Global Configuration
Configure storage settings globally:
TYPESCRIPTimport { configureStorage, StorageMiddleware } from '@quajs/store'class EncryptSaveMiddleware implements StorageMiddleware { beforeWrite(key, value) { // Encrypt or encode the full snapshot/save slot envelope here. return value } afterRead(key, value) { // Decrypt or decode the full snapshot/save slot envelope here. return value }}configureStorage({ backend: { driver: FileSystemBackend, options: { basePath: './saves' } }, middlewares: [ new EncryptSaveMiddleware() ]})// All stores created after this will use the global config by default
Multiple Store Management
TYPESCRIPTimport { commit, dispatch, useStore } from '@quajs/store'// Create multiple storesconst userStore = createStore({ name: 'user', state: { name: '' } })const gameStore = createStore({ name: 'game', state: { level: 1 } })// Access stores by nameconst store = useStore('user')// Cross-store actions using store/action notationawait dispatch('user/login', { username: 'alice' })commit('game/levelUp')
API Reference
Core Functions
createStore(options)- Create a new storeconfigureStorage(config)- Configure global storage settingsconfigureSerialization(serializer)- Configure global state serialization for subsequently created storesuseStore(name)- Get a store by namedispatch(action, payload)- Dispatch cross-store actionscommit(mutation, payload)- Commit cross-store mutationssnapshot(options)- Snapshot one, selected, or all storesrestore(snapshotId, options?)- Restore one, selected, or all stores
Store Methods
store.commit(mutation, payload)- Commit a mutationstore.dispatch(action, payload)- Dispatch an actionstore.snapshot(id?)- Create a snapshotstore.restore(snapshotId, options?)- Restore from snapshotstore.reset()- Reset to initial state
QuaStoreManager Methods
QuaStoreManager.createStore(options)- Create and register a storeQuaStoreManager.snapshotStore(storeName, id?)- Snapshot one storeQuaStoreManager.snapshotStores(storeNames, id?)- Snapshot selected storesQuaStoreManager.snapshotAll(id?)- Snapshot all storesQuaStoreManager.snapshot(options)- Snapshot by scope (storeName,storeNames, orscope: 'all')QuaStoreManager.restoreStore(storeName, snapshotId, options?)- Restore one storeQuaStoreManager.restoreStores(snapshotId, options?)- Restore selected stores from a scoped snapshotQuaStoreManager.restoreAll(snapshotId, options?)- Restore all storesQuaStoreManager.restore(snapshotId, options?)- Restore by explicit scope or auto-detect scoped snapshotsQuaStoreManager.listSnapshots(storeName?)- List snapshotsQuaStoreManager.deleteSnapshot(id)- Delete a snapshotQuaStoreManager.clearSnapshots(storeName?)- Clear snapshots
Storage Backends
Included Backends
- MemoryBackend - In-memory storage, included in
@quajs/store - IndexedDBBackend - Browser persistence, provided by
@quajs/store-web - QuastoreFileBackend - Encrypted binary
.quastorefile persistence, provided by@quajs/store-node
Backend Interface
TYPESCRIPTinterface StorageBackend { init?: (options?: any) => Promise<void> | void saveSnapshot: (snapshot: QuaSnapshot) => Promise<void> getSnapshot: (id: string) => Promise<QuaSnapshot | undefined> deleteSnapshot: (id: string) => Promise<void> listSnapshots: (storeName?: string) => Promise<QuaSnapshotMeta[]> clearSnapshots: (storeName?: string) => Promise<void> saveGameSlotIndex: (slot: QuaGameSaveSlotIndex) => Promise<void> getGameSlotIndex: (slotId: string) => Promise<QuaGameSaveSlotIndex | undefined> listGameSlotIndexes: () => Promise<QuaGameSaveSlotIndex[]> deleteGameSlotIndex: (slotId: string) => Promise<void> saveGameSlotPayload: (slot: QuaGameSaveSlotPayload) => Promise<void> getGameSlotPayload: (slotId: string) => Promise<QuaGameSaveSlotPayload | undefined> deleteGameSlotPayload: (slotId: string) => Promise<void> saveGameSlotPreview: (preview: QuaGameSavePreviewRecord) => Promise<void> getGameSlotPreview: (previewId: string) => Promise<QuaGameSavePreviewRecord | undefined> deleteGameSlotPreview: (previewId: string) => Promise<void> clearGameSlots: () => Promise<void> close?: () => Promise<void> | void}
Middleware Interface
TYPESCRIPTinterface StorageMiddleware { beforeWrite?: (key: string, value: any) => any | Promise<any> afterRead?: (key: string, value: any) => any | Promise<any>}
Examples
Check the /examples directory for:
- Usage Examples - Basic usage patterns
- Custom Backends - Example backend implementations
- Custom Middleware - Example middleware implementations
TypeScript Support
The library is fully typed and provides excellent TypeScript support:
TYPESCRIPTinterface GameState { playerName: string level: number score: number}const typedStore = createStore({ name: 'typedGame', state: { playerName: '', level: 1, score: 0 } as GameState, mutations: { setPlayerName: (state: GameState, name: string) => { state.playerName = name // Fully typed } }})
Environment Support
- Browser: Use
@quajs/store-webfor IndexedDB persistence or inject a custom browser backend - Node.js: Use
@quajs/store-nodefor encrypted.quastorefiles or inject a custom backend - Electron/Tauri: Works with any backend, ideal for desktop apps
Error Handling
The library provides descriptive error messages:
TYPESCRIPTtry { await store.restore('non-existent-snapshot')}catch (error) { console.error(error.message) // "Snapshot with id 'non-existent-snapshot' not found."}
Performance Considerations
- Lazy Loading: Storage managers are created only when needed
- Efficient Serialization: JSON serialization with optional compression middleware
- Memory Management: Automatic cleanup when stores are unregistered
- Async Operations: All storage operations are asynchronous and non-blocking
Contributing
This package is part of the QuaEngine project. See the main repository for contribution guidelines.
License
Apache 2.0 - see the main QuaEngine repository for details.