RSS Git Download  Clone
Raw Blame History 5kB 140 lines
import * as sharedIoRedis from '../../shared.mjs'

export default async (options) => {
    const {socket, payload} = options

    try {
        const redis = socket.p3xrs.ioredis
        const keys = payload.keys

        if (!Array.isArray(keys) || keys.length === 0) {
            socket.emit(options.responseEvent, {
                status: 'error',
                error: 'No keys specified for export',
            })
            return
        }

        // Get types for all keys
        const typePipeline = redis.pipeline()
        for (const key of keys) {
            typePipeline.type(key)
        }
        const typeResults = await typePipeline.exec()

        // Get TTLs for all keys
        const ttlPipeline = redis.pipeline()
        for (const key of keys) {
            ttlPipeline.pttl(key)
        }
        const ttlResults = await ttlPipeline.exec()

        // Read values based on type
        const exportedKeys = []
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            let type = typeResults[i][1]
            const pttl = ttlResults[i][1]

            // Normalize ReJSON-RL
            if (type === 'ReJSON-RL') {
                type = 'json'
            }

            if (type === 'none') {
                continue
            }

            let value
            try {
                switch (type) {
                    case 'string': {
                        const buf = await redis.getBuffer(key)
                        value = buf ? buf.toString('base64') : null
                        break
                    }
                    case 'list': {
                        const items = await redis.lrangeBuffer(key, 0, -1)
                        value = items.map(item => item.toString('base64'))
                        break
                    }
                    case 'set': {
                        const members = await redis.smembersBuffer(key)
                        value = members.map(m => m.toString('base64'))
                        break
                    }
                    case 'zset': {
                        // Returns [member, score, member, score, ...]
                        const raw = await redis.zrangebyscoreBuffer(key, '-inf', '+inf', 'WITHSCORES')
                        const entries = []
                        for (let j = 0; j < raw.length; j += 2) {
                            entries.push({
                                member: raw[j].toString('base64'),
                                score: parseFloat(raw[j + 1].toString()),
                            })
                        }
                        value = entries
                        break
                    }
                    case 'hash': {
                        const raw = await redis.hgetallBuffer(key)
                        const entries = {}
                        if (raw) {
                            for (const [field, val] of Object.entries(raw)) {
                                entries[field] = val.toString('base64')
                            }
                        }
                        value = entries
                        break
                    }
                    case 'stream': {
                        const entries = await redis.xrange(key, '-', '+')
                        value = entries.map(([id, fields]) => {
                            const obj = { id }
                            for (let j = 0; j < fields.length; j += 2) {
                                obj[fields[j]] = fields[j + 1]
                            }
                            return obj
                        })
                        break
                    }
                    case 'json': {
                        const jsonStr = await redis.call('JSON.GET', key)
                        value = jsonStr
                        break
                    }
                    default:
                        continue
                }
            } catch (e) {
                console.error(`key-export: failed to read key "${key}" (type=${type}):`, e.message)
                continue
            }

            exportedKeys.push({
                key,
                type,
                value,
                pttl: pttl > 0 ? pttl : -1,
            })
        }

        socket.emit(options.responseEvent, {
            status: 'ok',
            data: {
                version: 1,
                exportedAt: new Date().toISOString(),
                database: socket.p3xrs.currentDatabase || 0,
                keys: exportedKeys,
            },
        })

    } catch (e) {
        console.error(e)
        socket.emit(options.responseEvent, {
            status: 'error',
            error: e.message,
        })
    }
}