文件预览

idb_node.js

查看 RanchiMall Messenger 技能包中的文件内容。

文件内容

idb_node.js

/**
 * Node.js Messenger CLI — Local Key-Value Storage (IDB-compatible)
 *
 * Because IndexedDB is a browser API unavailable in Node.js, this script provides
 * the same logical operations (read, write, remove, list, clear) backed by a local
 * JSON file — mirroring the compactIDB interface used in the browser.
 *
 * Usage:
 *   node idb_node.js --action list   --db <DB> --store <STORE>
 *   node idb_node.js --action read   --db <DB> --store <STORE> --key <KEY>
 *   node idb_node.js --action write  --db <DB> --store <STORE> --key <KEY> --value <JSON>
 *   node idb_node.js --action remove --db <DB> --store <STORE> --key <KEY>
 *   node idb_node.js --action clear  --db <DB> --store <STORE>
 *   node idb_node.js --action dbs                              (list all local databases)
 *   node idb_node.js --action stores --db <DB>                (list stores in a database)
 *   node idb_node.js --action delete-db --db <DB>             (delete an entire database file)
 *
 * Storage: Each <db> maps to a JSON file at  ./idb_data/<db>.json
 *          Structure: { "<store>": { "<key>": <value>, ... }, ... }
 *
 * Note: No FLO_PRIVATE_KEY required. No network activity.
 */

'use strict';

const fs   = require('fs');
const path = require('path');

// ── Storage backend ──

const DATA_DIR = path.join(__dirname, 'idb_data');

function dbFile(db) {
    return path.join(DATA_DIR, `${db}.json`);
}

function ensureDataDir() {
    if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
}

function loadDB(db) {
    const file = dbFile(db);
    if (!fs.existsSync(file)) return {};
    try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
    catch (e) { throw new Error(`Corrupted database file: ${file}`); }
}

function saveDB(db, data) {
    ensureDataDir();
    fs.writeFileSync(dbFile(db), JSON.stringify(data, null, 2), 'utf8');
}

function getStore(data, store) {
    if (!(store in data)) throw new Error(`Store '${store}' not found in database. Did you initialise it?`);
    return data[store];
}

// ── Actions ──

/**
 * List all keys (and values) in a store.
 */
function listStore(db, store) {
    if (!db)    throw new Error('--db is required.');
    if (!store) throw new Error('--store is required.');
    const data = loadDB(db);
    const storeData = getStore(data, store);
    const keys = Object.keys(storeData);

    console.log(`\n${'='.repeat(65)}`);
    console.log(`  DB: ${db}  /  STORE: ${store}  (${keys.length} record${keys.length !== 1 ? 's' : ''})`);
    console.log('='.repeat(65));

    if (keys.length === 0) {
        console.log('  (empty)\n');
        return;
    }
    for (const key of keys) {
        const val = storeData[key];
        const display = typeof val === 'object' ? JSON.stringify(val) : String(val);
        console.log(`  ${key.padEnd(30)}  ${display.length > 80 ? display.slice(0, 80) + '...' : display}`);
    }
    console.log();
}

/**
 * Read a single record by key.
 */
function readRecord(db, store, key) {
    if (!db)    throw new Error('--db is required.');
    if (!store) throw new Error('--store is required.');
    if (!key)   throw new Error('--key is required.');
    const data = loadDB(db);
    const storeData = getStore(data, store);

    if (!(key in storeData)) {
        console.log(`\n  [idb] Key '${key}' not found in ${db}/${store}\n`);
        return;
    }
    const val = storeData[key];
    console.log(`\n  Key   : ${key}`);
    console.log(`  Value : ${typeof val === 'object' ? JSON.stringify(val, null, 2) : val}\n`);
}

/**
 * Add (insert) a record.
 */
function addRecord(db, store, key, valueStr) {
    if (!db)       throw new Error('--db is required.');
    if (!store)    throw new Error('--store is required.');
    if (valueStr === undefined || valueStr === null) throw new Error('--value is required.');

    let value;
    try { value = JSON.parse(valueStr); } catch { value = valueStr; }

    const data = loadDB(db);
    if (!(store in data)) data[store] = {};
    
    // Auto-generate key if not provided
    if (!key) key = Date.now().toString();

    if (data[store][key] !== undefined) {
        throw new Error(`Key '${key}' already exists in store '${store}'. Use 'write' to overwrite.`);
    }

    data[store][key] = value;
    saveDB(db, data);
    console.log(`\n  [idb] Added to [${db} > ${store} > ${key}]\n`);
}

/**
 * Write (upsert) a record.
 */
function writeRecord(db, store, key, valueStr) {
    if (!db)       throw new Error('--db is required.');
    if (!store)    throw new Error('--store is required.');
    if (!key)      throw new Error('--key is required.');
    if (valueStr === undefined || valueStr === null) throw new Error('--value is required.');

    // Try to parse JSON; fall back to raw string
    let value;
    try { value = JSON.parse(valueStr); } catch { value = valueStr; }

    const data = loadDB(db);
    if (!(store in data)) data[store] = {};
    data[store][key] = value;
    saveDB(db, data);
    console.log(`\n  [idb] Written: ${db}/${store}/${key}\n`);
}

/**
 * Remove a record by key.
 */
function removeRecord(db, store, key) {
    if (!db)    throw new Error('--db is required.');
    if (!store) throw new Error('--store is required.');
    if (!key)   throw new Error('--key is required.');
    const data = loadDB(db);
    const storeData = getStore(data, store);
    if (!(key in storeData)) {
        console.log(`\n  [idb] Key '${key}' not found — nothing removed.\n`);
        return;
    }
    delete storeData[key];
    saveDB(db, data);
    console.log(`\n  [idb] Removed: ${db}/${store}/${key}\n`);
}

/**
 * Clear all records in a store.
 */
function clearStore(db, store) {
    if (!db)    throw new Error('--db is required.');
    if (!store) throw new Error('--store is required.');
    const data = loadDB(db);
    getStore(data, store); // validate store exists
    const count = Object.keys(data[store]).length;
    data[store] = {};
    saveDB(db, data);
    console.log(`\n  [idb] Cleared ${count} record${count !== 1 ? 's' : ''} from ${db}/${store}\n`);
}

/**
 * List all local database files.
 */
function listDatabases() {
    ensureDataDir();
    const files = fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.json'));
    console.log(`\n${'='.repeat(65)}`);
    console.log(`  LOCAL DATABASES  (${files.length})`);
    console.log('='.repeat(65));
    if (files.length === 0) {
        console.log('  (none yet)\n');
        return;
    }
    for (const f of files) {
        const db = f.replace('.json', '');
        let stores = [];
        try {
            const data = JSON.parse(fs.readFileSync(path.join(DATA_DIR, f), 'utf8'));
            stores = Object.keys(data);
        } catch {}
        console.log(`  ${db.padEnd(30)}  stores: [${stores.join(', ')}]`);
    }
    console.log();
}

/**
 * List stores in a database.
 */
function listStores(db) {
    if (!db) throw new Error('--db is required.');
    const data = loadDB(db);
    const stores = Object.keys(data);
    console.log(`\n  DB: ${db}`);
    console.log(`  Stores (${stores.length}): ${stores.length ? stores.join(', ') : '(none)'}\n`);
}

/**
 * Delete an entire database file.
 */
function deleteDatabase(db) {
    if (!db) throw new Error('--db is required.');
    const file = dbFile(db);
    if (!fs.existsSync(file)) {
        console.log(`\n  [idb] Database '${db}' does not exist.\n`);
        return;
    }
    fs.unlinkSync(file);
    console.log(`\n  [idb] Deleted database: ${db}\n`);
}

/**
 * Initialise a database with one or more stores (creates the file/stores if missing).
 */
function initDatabase(db, storesStr) {
    if (!db)       throw new Error('--db is required.');
    if (!storesStr) throw new Error('--stores "store1,store2,..." is required.');
    const storeNames = storesStr.split(',').map(s => s.trim()).filter(Boolean);
    const data = loadDB(db);
    let added = 0;
    for (const s of storeNames) {
        if (!(s in data)) { data[s] = {}; added++; }
    }
    saveDB(db, data);
    console.log(`\n  [idb] Database '${db}' ready. Added ${added} new store${added !== 1 ? 's' : ''}. Stores: [${Object.keys(data).join(', ')}]\n`);
}

// ── Parse CLI arguments ──

function parseArgs() {
    const args = process.argv.slice(2);
    const parsed = {};
    for (let i = 0; i < args.length; i++) {
        switch (args[i]) {
            case '--action': parsed.action = args[++i]; break;
            case '--db':     parsed.db     = args[++i]; break;
            case '--store':  parsed.store  = args[++i]; break;
            case '--stores': parsed.stores = args[++i]; break;
            case '--key':    parsed.key    = args[++i]; break;
            case '--value':  parsed.value  = args[++i]; break;
        }
    }
    return parsed;
}

// ── Main ──

function main() {
    try {
        const args = parseArgs();

        switch (args.action) {
            case 'list':      listStore(args.db, args.store); break;
            case 'read':      readRecord(args.db, args.store, args.key); break;
            case 'add':       addRecord(args.db, args.store, args.key, args.value); break;
            case 'write':     writeRecord(args.db, args.store, args.key, args.value); break;
            case 'remove':    removeRecord(args.db, args.store, args.key); break;
            case 'clear':     clearStore(args.db, args.store); break;
            case 'dbs':       listDatabases(); break;
            case 'stores':    listStores(args.db); break;
            case 'delete-db': deleteDatabase(args.db); break;
            case 'init':      initDatabase(args.db, args.stores); break;

            default:
                console.log(`
Messenger Local Storage (Node.js IDB-compatible)

Usage: node idb_node.js --action <action> [options]

Actions:
  dbs                                        List all local databases
  stores     --db <DB>                       List stores in a database
  init       --db <DB> --stores "s1,s2,..."  Create database / add stores
  list       --db <DB> --store <STORE>       List all records in a store
  read       --db <DB> --store <STORE> --key <KEY>
  add        --db <DB> --store <STORE> --value <JSON|TEXT> [--key <KEY>]
  write      --db <DB> --store <STORE> --key <KEY> --value <JSON|TEXT>
  remove     --db <DB> --store <STORE> --key <KEY>     Delete a record by key
  clear      --db <DB> --store <STORE>       Clear all records in a store
  delete-db  --db <DB>                       Delete an entire database file

Storage:
  Records are persisted to  ./idb_data/<db>.json
  This mirrors the browser compactIDB API used by the FLO DApp platform.

Examples:
  node idb_node.js --action dbs
  node idb_node.js --action init --db myApp --stores "contacts,settings,messages"
  node idb_node.js --action list --db myApp --store contacts
  node idb_node.js --action write --db myApp --store contacts --key "FBhHiN..." --value '"Alice"'
  node idb_node.js --action read  --db myApp --store contacts --key "FBhHiN..."
  node idb_node.js --action remove --db myApp --store contacts --key "FBhHiN..."
  node idb_node.js --action clear --db myApp --store messages
  node idb_node.js --action delete-db --db myApp

Note: No FLO_PRIVATE_KEY required. No network activity.
`);
        }

    } catch (error) {
        console.error('[error]', error.message || error);
        process.exitCode = 1;
    }
}

main();