import RocksDB from 'rocksdb-native'
import b4a from 'b4a'
const CORESTORE_CF = 'corestore'
// Key type labels based on hypercore-storage schema
const KEY_TYPES = {
0x00: 'seed',
0x01: 'core-key',
0x02: 'discovery-key',
0x03: 'core-info',
0x04: 'data-block'
}
export async function openDB (dbPath, opts = {}) {
const { readOnly = false } = opts
const db = new RocksDB(dbPath, {
readOnly,
columnFamilies: [CORESTORE_CF]
})
await db.ready()
return db
}
export async function scanColumnFamily (session, opts = {}) {
const { limit = 100000 } = opts
const keyTypes = new Map()
let totalKeys = 0
let totalBytes = 0
const readable = []
const iter = session.iterator({ limit })
return new Promise((resolve, reject) => {
iter.on('data', ({ key, value }) => {
totalKeys++
const valLen = value ? value.length : 0
totalBytes += valLen
const firstByte = key[0]
const info = keyTypes.get(firstByte) || { count: 0, bytes: 0 }
info.count++
info.bytes += valLen
keyTypes.set(firstByte, info)
// Collect readable strings
if (value && valLen > 2 && valLen < 2000) {
try {
const str = b4a.toString(value, 'utf8')
if (/^[\x20-\x7e\n\r\t]+$/.test(str) && readable.length < 100) {
readable.push({
key: b4a.toString(key, 'hex'),
type: firstByte,
value: str.slice(0, 300)
})
}
} catch {}
}
})
iter.on('end', () => resolve({ totalKeys, totalBytes, keyTypes, readable }))
iter.on('error', reject)
})
}
export async function listApps (db) {
const defaultIter = db.iterator({ limit: 10000 })
const pearApps = []
const fileSources = []
return new Promise((resolve, reject) => {
defaultIter.on('data', ({ key, value }) => {
if (key.length < 2) return
// Extract readable string from key (skip prefix byte)
let keyStr = ''
for (let i = 1; i < key.length; i++) {
if (key[i] >= 0x20 && key[i] <= 0x7e) {
keyStr += String.fromCharCode(key[i])
}
}
// Extract readable string from value
let valStr = ''
if (value && value.length < 500) {
for (let i = 0; i < value.length; i++) {
if (value[i] >= 0x20 && value[i] <= 0x7e) {
valStr += String.fromCharCode(value[i])
}
}
}
const prefix = key[0]
if (keyStr.startsWith('pear://')) {
pearApps.push({ prefix, url: keyStr, storage: valStr, valLen: value ? value.length : 0 })
} else if (keyStr.startsWith('file://')) {
fileSources.push({ prefix, path: keyStr, storage: valStr, valLen: value ? value.length : 0 })
}
})
defaultIter.on('end', () => resolve({ pearApps, fileSources }))
defaultIter.on('error', reject)
})
}
export async function getCorestoreStats (db) {
const cs = db.columnFamily(CORESTORE_CF)
const stats = await scanColumnFamily(cs)
return {
...stats,
typeBreakdown: [...stats.keyTypes.entries()].map(([byte, info]) => ({
type: KEY_TYPES[byte] || `unknown-0x${byte.toString(16)}`,
byte,
count: info.count,
bytes: info.bytes,
mb: (info.bytes / 1024 / 1024).toFixed(2)
})).sort((a, b) => b.bytes - a.bytes)
}
}
export function formatBytes (bytes) {
if (bytes >= 1024 * 1024 * 1024) return (bytes / 1024 / 1024 / 1024).toFixed(2) + ' GB'
if (bytes >= 1024 * 1024) return (bytes / 1024 / 1024).toFixed(2) + ' MB'
if (bytes >= 1024) return (bytes / 1024).toFixed(1) + ' KB'
return bytes + ' B'
}