import { create } from 'zustand'
import { request } from './socket.service'
import { useCommonStore } from './common.store'
import { useRedisStateStore } from './redis-state.store'
import { useSettingsStore } from './settings.store'
import { useI18nStore } from './i18n.store'
import { useOverlayStore } from './overlay.store'
import { navigateTo } from './navigation.store'
import { parseRedisInfo } from './redis-parser'
// --- Event bus for component communication ---
type Callback = (...args: any[]) => void
type VoidCallback = () => void
const cmdListeners: Record<string, Set<Callback>> = {}
export function onCommandEvent(event: string, cb: Callback): VoidCallback {
if (!cmdListeners[event]) cmdListeners[event] = new Set()
cmdListeners[event].add(cb)
return () => { cmdListeners[event].delete(cb) }
}
export function emitCommand(event: string, data?: any) {
cmdListeners[event]?.forEach(cb => cb(data))
}
interface MainCommandState {
connect: (conn: any) => Promise<void>
selectDatabase: (dbIndex: number) => Promise<void>
save: () => Promise<void>
refresh: (options?: { withoutParent?: boolean; force?: boolean }) => Promise<void>
statistics: () => Promise<void>
disconnect: () => Promise<void>
addKey: (options: { event: Event; node?: any }) => void
getCurrentDatabase: () => number
setCurrentDatabase: (value: number) => void
}
let lastRefreshAt = 0
export const useMainCommandStore = create<MainCommandState>(() => ({
connect: async (conn: any) => {
const cloned = structuredClone(conn)
const settings = useSettingsStore.getState()
const overlay = useOverlayStore.getState()
try {
const dbStorageKey = settings.getStorageKeyCurrentDatabase(cloned.id)
let db: string | undefined
try { db = localStorage.getItem(dbStorageKey) ?? undefined } catch {}
if (cloned.askAuth === true) {
try {
const auth = await useCommonStore.getState().askAuth()
cloned.username = auth.username || undefined
cloned.password = auth.password || undefined
} catch { return }
}
const strings = useI18nStore.getState().strings
overlay.show({ message: strings?.title?.connectingRedis })
useRedisStateStore.setState({ connectionState: 'connecting' })
const response = await request({
action: 'connection/connect',
payload: { connection: cloned, db },
})
const databaseIndexes: number[] = []
let i = 0
while (i < response.databases) databaseIndexes.push(i++)
const commands: string[] = []
Object.keys(response.commands ?? {}).forEach(k => commands.push(response.commands[k][0]))
commands.sort()
const modules = Array.isArray(response.modules) ? response.modules : []
useRedisStateStore.setState({
page: 1, monitor: false, dbsize: response.dbsize,
databaseIndexes, connection: cloned, commands,
commandsMeta: response.commandsMeta ?? {}, modules,
hasReJSON: modules.some((m: any) => m.name === 'ReJSON'),
hasRediSearch: modules.some((m: any) => m.name === 'search'),
hasTimeSeries: modules.some((m: any) => m.name === 'timeseries' || m.name === 'Timeseries'),
hasBloom: modules.some((m: any) => m.name === 'bf'),
connectionState: 'connected',
})
useCommonStore.getState().loadRedisInfoResponse({ response })
try { localStorage.setItem(settings.connectInfoStorageKey, JSON.stringify(cloned)) } catch {}
} catch (error) {
try { localStorage.removeItem(settings.connectInfoStorageKey) } catch {}
useRedisStateStore.setState({ connection: undefined, connectionState: 'none' })
useCommonStore.getState().generalHandleError(error)
} finally {
overlay.hide()
}
},
selectDatabase: async (dbIndex: number) => {
const state = useRedisStateStore.getState()
const settings = useSettingsStore.getState()
useRedisStateStore.setState({ currentDatabase: dbIndex, page: 1, redisChanged: true })
try {
await request({ action: 'redis/console', payload: { command: `select ${dbIndex}` } })
const strings = useI18nStore.getState().strings
const fn = strings?.status?.dbChanged
const msg = typeof fn === 'function' ? fn({ db: dbIndex }) : ''
if (msg) useCommonStore.getState().toast(msg)
// Persist
const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id)
if (storageKey) try { localStorage.setItem(storageKey, String(dbIndex)) } catch {}
await useMainCommandStore.getState().refresh({ force: true })
} catch (e) {
useCommonStore.getState().generalHandleError(e)
}
},
save: async () => {
try {
const response = await request({ action: 'redis/save' })
const info = parseRedisInfo(response.info)
useRedisStateStore.setState({ info })
const strings = useI18nStore.getState().strings
useCommonStore.getState().toast(strings?.status?.savedRedis)
} catch (e) {
useCommonStore.getState().generalHandleError(e)
}
},
statistics: async () => {
try {
navigateTo('database.statistics')
await useMainCommandStore.getState().refresh({ force: true })
} catch (e) {
useCommonStore.getState().generalHandleError(e)
}
},
refresh: async (options: { withoutParent?: boolean; force?: boolean } = {}) => {
const now = Date.now()
if (!options.force && now - lastRefreshAt < 2000) return
lastRefreshAt = now
try {
const state = useRedisStateStore.getState()
const settings = useSettingsStore.getState()
const payload: any = {}
const searchValue = state.search
if (!settings.searchClientSide && typeof searchValue === 'string' && searchValue.length > 0) {
payload.match = settings.searchStartsWith ? searchValue + '*' : '*' + searchValue + '*'
}
const response = await request({ action: 'redis/refresh', payload })
useRedisStateStore.setState({ dbsize: response.dbsize, redisChanged: true })
useCommonStore.getState().loadRedisInfoResponse({ response })
emitCommand('tree-refresh')
if (!options.withoutParent) emitCommand('refresh-key')
} catch (e) {
useCommonStore.getState().generalHandleError(e)
}
},
disconnect: async () => {
const state = useRedisStateStore.getState()
const settings = useSettingsStore.getState()
try { localStorage.removeItem(settings.connectInfoStorageKey) } catch {}
useRedisStateStore.setState({
connection: undefined,
connectionState: 'none',
redisConnections: {},
monitor: false,
})
try {
await request({
action: 'connection/disconnect',
payload: { connectionId: state.connection?.id },
})
} catch {}
navigateTo('settings')
},
addKey: (options: { event: Event; node?: any }) => {
options.event.stopPropagation()
emitCommand('key-new', options)
},
getCurrentDatabase: () => {
const state = useRedisStateStore.getState()
const settings = useSettingsStore.getState()
let db: any = state.currentDatabase
if (db === undefined) {
const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id)
if (storageKey) try { db = localStorage.getItem(storageKey) } catch {}
}
return Number(db ?? 0)
},
setCurrentDatabase: (value: number) => {
const state = useRedisStateStore.getState()
const settings = useSettingsStore.getState()
useRedisStateStore.setState({ currentDatabase: value })
const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id)
if (storageKey) try { localStorage.setItem(storageKey, String(value)) } catch {}
},
}))