RSS Git Download  Clone
Raw Blame History 3kB 84 lines
export default async (options) => {
    const { socket } = options
    try {
        const redis = socket.p3xrs.ioredis
        if (!redis) {
            socket.emit(options.responseEvent, { status: 'error', error: 'Not connected to Redis' })
            return
        }

        let shards
        try {
            // Redis 7.0+ — flat array shard data, needs parsing
            const raw = await redis.call('CLUSTER', 'SHARDS')
            shards = parseClusterShards(raw)
        } catch {
            // Fallback to CLUSTER SLOTS (Redis 3.0+)
            const slots = await redis.call('CLUSTER', 'SLOTS')
            shards = parseClusterSlots(slots)
        }

        socket.emit(options.responseEvent, { status: 'ok', data: { shards } })
    } catch (e) {
        console.error('cluster/shards failed', e)
        socket.emit(options.responseEvent, { status: 'error', error: e.message })
    }
}

// Parse flat array from CLUSTER SHARDS (Redis 7+)
// Each shard: ["slots", [start, end], "nodes", [[flat node...], ...]]
function parseClusterShards(raw) {
    return raw.map(entry => {
        const map = {}
        for (let i = 0; i < entry.length; i += 2) {
            map[entry[i]] = entry[i + 1]
        }
        const slotArr = map.slots || []
        const slotRanges = []
        for (let i = 0; i < slotArr.length; i += 2) {
            slotRanges.push([slotArr[i], slotArr[i + 1]])
        }
        const nodes = (map.nodes || []).map(nodeArr => {
            const node = {}
            for (let i = 0; i < nodeArr.length; i += 2) {
                node[nodeArr[i]] = nodeArr[i + 1]
            }
            return { host: node.ip || node.endpoint, port: node.port, id: node.id, role: node.role }
        })
        const master = nodes.find(n => n.role === 'master') || nodes[0] || { host: '?', port: 0, id: '?' }
        const replicas = nodes.filter(n => n.role !== 'master')
        return { slotRanges, master, replicas }
    })
}

function parseClusterSlots(slots) {
    // CLUSTER SLOTS returns: [[startSlot, endSlot, [masterIP, port, id], [replicaIP, port, id], ...], ...]
    const shardMap = new Map()
    for (const entry of slots) {
        const start = entry[0]
        const end = entry[1]
        const masterInfo = entry[2]
        const masterId = masterInfo[2] || `${masterInfo[0]}:${masterInfo[1]}`

        if (!shardMap.has(masterId)) {
            shardMap.set(masterId, {
                slotRanges: [],
                master: { host: masterInfo[0], port: masterInfo[1], id: masterId },
                replicas: [],
            })
        }
        const shard = shardMap.get(masterId)
        shard.slotRanges.push([start, end])

        // Replicas are entries[3], entries[4], etc.
        for (let i = 3; i < entry.length; i++) {
            const rep = entry[i]
            const repId = rep[2] || `${rep[0]}:${rep[1]}`
            if (!shard.replicas.find(r => r.id === repId)) {
                shard.replicas.push({ host: rep[0], port: rep[1], id: repId })
            }
        }
    }
    return Array.from(shardMap.values())
}