moleculer-repl
Version:
REPL module for Moleculer
160 lines (140 loc) • 4.06 kB
JavaScript
;
const _ = require("lodash");
/**
* Returns the list of versioned services
*
* @param {import("moleculer").ServiceBroker} broker
* @returns {Array<String>}
*/
function versionedServicesAutocomplete(broker) {
let services = broker.registry.getServiceList({
onlyLocal: true,
onlyAvailable: true,
skipInternal: true,
withActions: true,
withEvents: true
});
// Return only the names
return services.map(service => service.fullName);
}
/**
* Returns the list of nodes and their actions in the following format "<nodeID> <actionName>"
*
* @param {import("moleculer").ServiceBroker} broker
* @returns {string[]}
*/
function nodeIdActionNameAutocomplete(broker) {
/** @type {string[][]} */
const nested = _.compact(
broker.registry.getNodeList({ onlyAvailable: true, withServices: true }).map(node => {
// Get actions of the node
let actionList = node.services.map(svc => {
return Object.values(svc.actions).map(action => {
return action.name;
});
});
actionList = _.flatten(actionList);
// Create "<nodeID> <action name> command for autocomplete
return actionList.map(actionName => `${node.id} ${actionName}`);
})
);
return _.uniq(_.flatten(nested));
}
/**
* Returns the list of available actions
*
* @param {import("moleculer").ServiceBroker} broker
* @returns {Array<String>}
*/
function actionNameAutocomplete(broker) {
return _.uniq(
_.compact(
broker.registry
.getActionList({})
.map(item => (item && item.action ? item.action.name : null))
)
);
}
/**
* Returns the list of available events
*
* @param {import("moleculer").ServiceBroker} broker
* @returns {Array<String>}
*/
function eventNameAutocomplete(broker) {
return _.uniq(
_.compact(
broker.registry
.getEventList({})
.map(item => (item && item.event ? item.event.name : null))
)
);
}
/**
* Returns list of available commands
* @param {import('commander').Command} program
* @returns {String[]} Available commands
*/
function getAvailableCommands(program) {
const nested = program.commands.map(entry => [entry.name(), ...entry.aliases()]);
return /** @type {string[]} */ (_.flatten(nested));
}
/**
* Given a line from terminal generates a list of suggestions
*
* @param {String} line
* @param {import("moleculer").ServiceBroker} broker
* @param {import('commander').Command} program
* @returns {[String[], String]} List of suggestions. More info: https://nodejs.org/api/readline.html#use-of-the-completer-function
*/
function autocompleteHandler(line, broker, program) {
let [command, ...rest] = line.split(" ");
const availableCommands = getAvailableCommands(program);
// Empty line. Show all available commands
if (!command) {
return [availableCommands, line];
}
// Check if command value is complete
if (!availableCommands.includes(command)) {
// Unknown or incomplete command. Try to provide a suggestion
const hits = availableCommands.filter(c => c.startsWith(command));
// Show all completions if none found
return [hits.length ? hits : availableCommands, line];
}
// From here we already know what command will be executed //
let completions = [];
switch (command) {
case "cache": {
completions = ["keys", "clear"];
break;
}
case "listener": {
completions = ["add", "remove", "list"];
break;
}
case "bench":
case "call": {
completions = actionNameAutocomplete(broker);
break;
}
case "broadcast":
case "broadcastLocal":
case "emit": {
completions = eventNameAutocomplete(broker);
break;
}
case "dcall": {
completions = nodeIdActionNameAutocomplete(broker);
break;
}
case "destroy": {
completions = versionedServicesAutocomplete(broker);
break;
}
}
completions = completions.map(entry => `${command} ${entry}`);
// Match the command + action/event name against partial "line" value
let hits = completions.filter(c => c.startsWith(line));
return [hits.length ? hits : completions, line];
}
module.exports = { autocompleteHandler, getAvailableCommands };