wise-json-db
Version:
Blazing fast, crash-proof embedded JSON database for Node.js with batch operations, TTL, indexes, and segmented checkpointing.
211 lines (179 loc) • 9.09 kB
JavaScript
// cli/actions.js
const fs = require('fs/promises');
const path = require('path');
const { flattenDocToCsv } = require('../wise-json/collection/utils.js');
const { confirmAction, prettyError } = require('./utils.js');
// --- Утилита для проверки существования коллекции ---
async function assertCollectionExists(db, collectionName) {
const names = await db.getCollectionNames();
if (!names.includes(collectionName)) {
prettyError(`Collection "${collectionName}" does not exist.`);
}
}
// =============================
// --- Read-Only Actions ---
// =============================
async function listCollectionsAction(db) {
const collections = await db.getCollectionNames();
const result = await Promise.all(collections.map(async (name) => {
const col = await db.collection(name);
await col.initPromise;
return { name, count: await col.count() };
}));
if (result.length === 0) {
console.log('No collections found.');
return;
}
console.table(result);
}
async function showCollectionAction(db, [collectionName], options) {
if (!collectionName) prettyError('Usage: show-collection <collection> [options]');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
const limit = parseInt(options.limit || '10', 10);
const offset = parseInt(options.offset || '0', 10);
const sortField = options.sort;
const sortOrder = options.order || 'asc';
const output = options.output || 'json';
let filter = {};
if (options.filter) {
try {
filter = JSON.parse(options.filter);
} catch (e) {
prettyError(`Invalid JSON in --filter option: ${e.message}`);
}
}
let docs = await col.find(filter);
if (sortField) {
docs.sort((a, b) => {
if (a[sortField] === undefined) return 1;
if (b[sortField] === undefined) return -1;
if (a[sortField] < b[sortField]) return sortOrder === 'asc' ? -1 : 1;
if (a[sortField] > b[sortField]) return sortOrder === 'asc' ? 1 : -1;
return 0;
});
}
docs = docs.slice(offset, offset + limit);
if (output === 'csv') console.log(flattenDocToCsv(docs));
else if (output === 'table') console.table(docs);
else console.log(JSON.stringify(docs, null, 2));
}
async function listIndexesAction(db, [collectionName]) {
if (!collectionName) prettyError('Usage: list-indexes <collection>');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
console.log(JSON.stringify(await col.getIndexes(), null, 2));
}
async function getDocumentAction(db, [collectionName, docId]) {
if (!collectionName || !docId) prettyError('Usage: get-document <collection> <docId>');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
const doc = await col.getById(docId);
if (!doc) {
prettyError(`Document with ID "${docId}" not found in collection "${collectionName}".`);
}
console.log(JSON.stringify(doc, null, 2));
}
async function exportCollectionAction(db, [collectionName, filePath], options) {
if (!collectionName || !filePath) prettyError('Usage: export-collection <collection> <file>');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
const outputFormat = options.output || 'json';
const absoluteFilePath = path.resolve(process.cwd(), filePath);
try {
if (outputFormat === 'csv') {
await col.exportCsv(absoluteFilePath);
} else {
await col.exportJson(absoluteFilePath);
}
console.log(`Collection "${collectionName}" exported to ${absoluteFilePath} as ${outputFormat}.`);
} catch (e) {
prettyError(`Failed to export to file: ${e.message}`);
}
}
// =============================
// --- Write Actions ---
// =============================
async function createIndexAction(db, [collectionName, fieldName], options) {
if (!collectionName || !fieldName) prettyError('Usage: create-index <collection> <field> [--unique]');
const col = await db.collection(collectionName);
await col.initPromise;
await col.createIndex(fieldName, { unique: !!options.unique });
console.log(`Index on "${fieldName}" created successfully in collection "${collectionName}".`);
}
async function dropIndexAction(db, [collectionName, fieldName]) {
if (!collectionName || !fieldName) prettyError('Usage: drop-index <collection> <field>');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
await col.dropIndex(fieldName);
console.log(`Index on "${fieldName}" dropped from collection "${collectionName}".`);
}
async function importCollectionAction(db, [collectionName, filePath], options) {
if (!collectionName || !filePath) prettyError('Usage: import-collection <collection> <file.json> [--mode=replace|append]');
const col = await db.collection(collectionName);
await col.initPromise;
const mode = options.mode || 'append';
const absoluteFilePath = path.resolve(process.cwd(), filePath);
try {
await col.importJson(absoluteFilePath, { mode });
console.log(`Import to "${collectionName}" from ${absoluteFilePath} completed (mode: ${mode}).`);
} catch(e) {
prettyError(`Failed to import from file: ${e.message}`);
}
}
async function dropCollectionAction(db, [collectionName], options) {
if (!collectionName) prettyError('Usage: collection-drop <collection>');
await assertCollectionExists(db, collectionName);
const confirmed = await confirmAction(`Are you sure you want to PERMANENTLY delete the collection "${collectionName}"?`, options);
if (confirmed) {
const collectionPath = path.join(db.dbRootPath, collectionName);
await fs.rm(collectionPath, { recursive: true, force: true });
console.log(`Collection "${collectionName}" dropped successfully.`);
} else {
console.log('Operation cancelled.');
}
}
async function insertDocumentAction(db, [collectionName, jsonString]) {
if (!collectionName || !jsonString) prettyError('Usage: doc-insert <collection> <json_string>');
const col = await db.collection(collectionName);
await col.initPromise;
try {
const doc = JSON.parse(jsonString);
const inserted = await col.insert(doc);
console.log(JSON.stringify(inserted, null, 2));
} catch (e) {
prettyError(`Failed to insert document. Invalid JSON or db error: ${e.message}`);
}
}
async function removeDocumentAction(db, [collectionName, docId]) {
if (!collectionName || !docId) prettyError('Usage: doc-remove <collection> <docId>');
await assertCollectionExists(db, collectionName);
const col = await db.collection(collectionName);
await col.initPromise;
const success = await col.remove(docId);
if (!success) {
prettyError(`Document with ID "${docId}" not found for removal.`);
}
console.log(`Document "${docId}" removed from "${collectionName}".`);
}
// --- Реестр команд ---
module.exports = {
// Read-only
'list-collections': { handler: listCollectionsAction, isWrite: false, description: 'Lists all collections and their document counts.' },
'show-collection': { handler: showCollectionAction, isWrite: false, description: 'Shows documents in a collection with pagination and filtering.' },
'list-indexes': { handler: listIndexesAction, isWrite: false, description: 'Lists indexes for a collection.'},
'get-document': { handler: getDocumentAction, isWrite: false, description: 'Gets a single document by its ID.'},
'export-collection':{ handler: exportCollectionAction,isWrite: false, description: 'Exports a collection to a file. Use --output=csv for CSV.' },
// Write-enabled
'create-index': { handler: createIndexAction, isWrite: true, description: 'Creates an index on a field. Use --unique for a unique index.' },
'drop-index': { handler: dropIndexAction, isWrite: true, description: 'Drops an index from a collection.' },
'import-collection':{ handler: importCollectionAction,isWrite: true, description: 'Imports documents from a JSON file. Use --mode=replace to clear first.' },
'collection-drop': { handler: dropCollectionAction, isWrite: true, description: 'Permanently deletes an entire collection. Use with caution.' },
'doc-insert': { handler: insertDocumentAction, isWrite: true, description: 'Inserts a single document from a JSON string.' },
'doc-remove': { handler: removeDocumentAction, isWrite: true, description: 'Removes a single document by its ID.' },
};