UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

330 lines (327 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.cli = cli; const util_1 = require("util"); const node_fs_1 = __importDefault(require("node:fs")); const node_os_1 = __importDefault(require("node:os")); const promises_1 = require("node:readline/promises"); const node_process_1 = require("node:process"); const ajv_1 = __importDefault(require("ajv")); const ajv_formats_1 = __importDefault(require("ajv-formats")); const js_yaml_1 = __importDefault(require("js-yaml")); const shell_quote_1 = require("shell-quote"); const cloneDeep_1 = __importDefault(require("lodash/cloneDeep")); const uniq_1 = __importDefault(require("lodash/uniq")); const commander_1 = require("commander"); const services_1 = require("../services"); const sdk_1 = require("../sdk"); const runner_1 = __importDefault(require("../sdk/runner")); const node_assert_1 = require("node:assert"); // CLI sub commands const admin_1 = __importDefault(require("./admin")); const data_1 = __importDefault(require("./data")); const models_1 = __importDefault(require("./models")); const security_1 = __importDefault(require("./security")); const { version: CLI_VERSION } = require('../../package.json'); const program = new commander_1.Command('ds'); let isRun = false; program .storeOptionsAsProperties(false) .version(CLI_VERSION, '-v, --vers', 'output the current version') .description(`Datastore API client Gitlab: > https://gitlab.com/getanthill/datastore Documentation: > https://datastore.getanthill.org/docs/tutorials/using-the-cli Examples: ds models accounts find ds models accounts find --is_enabled true ds models accounts find --is_enabled true --sort updated_at --fields email created_at `); const datastoreConfigs = JSON.parse(process.env.DATASTORE_CONFIGS || JSON.stringify([ { name: 'default', config: { baseUrl: process.env.DATASTORE_API_URL || 'http://localhost:3001', token: process.env.DATASTORE_ACCESS_TOKEN || 'token', debug: false, }, }, ])); const services = (0, services_1.build)(); const datastores = new Map(datastoreConfigs.map((c) => { (0, node_assert_1.ok)(!!c.name, 'Missing Datastore configuration name'); return [ c.name, new sdk_1.Datastore({ ...c.config, telemetry: services.telemetry }), ]; })); services.datastores = datastores; const validator = new ajv_1.default({ useDefaults: false, coerceTypes: true, strict: false, }); // @ts-ignore (0, ajv_formats_1.default)(validator); let modelConfigs = {}; function log(obj, format = process.env.DATASTORE_CLI_FORMAT) { const _format = isRun === true ? 'cli' : format; if (_format === 'json') { console.log(JSON.stringify(obj)); return; } if (_format === 'yaml') { console.log(js_yaml_1.default.dump(obj)); return; } console.log((0, util_1.inspect)(obj, false, null, true)); } function addDatastoreOptions(h) { const dsNames = Array.from(datastores.keys()); h.addOption(new commander_1.Option('--ds, --datastore <datastore>', 'Datastore to use') .default(dsNames[0] || 'default') .choices(dsNames.length === 0 ? ['default'] : dsNames)); } function stream() { const h = program.command('stream <model> <source>'); addDatastoreOptions(h); const dsNames = Array.from(datastores.keys()); h.addOption(new commander_1.Option('--output <output>', 'Output') .default('entity') .choices(['entity', 'raw'])) .addOption(new commander_1.Option('--format <format>', 'Response format').choices([ 'json', 'yaml', ])) .addOption(new commander_1.Option('-c, --connector <connector>', 'Connector type').choices([ 'http', 'amqp', ])) .addOption(new commander_1.Option('-q, --queue <queue>', 'Queue name')) .addOption(new commander_1.Option('-s, --sync <datastore>', 'Sync event to a datastore').choices(dsNames.length === 0 ? ['default'] : dsNames)) .description('Stream entities changes or events'); h.action(async (model, source, cmd) => { if (!datastores) { return; } try { const datastore = datastores.get(cmd.datastore); if (!datastore) { return; } let streamHandler = datastore.streams.streamHTTP.bind(datastore.streams); if (cmd.connector === 'amqp') { streamHandler = datastore.streams.streamAMQP.bind(datastore.streams); } await streamHandler(async (e, _route, _headers, opts) => { log(e, cmd.format); if (source === 'events' && cmd.sync) { const _model = model === 'all' ? e.model : model; const event = model === 'all' ? e.entity : e; const modelConfig = modelConfigs[_model]; datastores .get(cmd.sync) ?.apply(e.model, event[modelConfig.correlation_field], event.type, event.v, event, { replay: 'true' }); } typeof opts?.ack === 'function' && (await opts.ack()); }, model, source); } catch (err) { if (err.response) { log(err.response.data, cmd.format); return; } log(err, cmd.format); } }); } function config() { const h = program .command('config') .addOption(new commander_1.Option('--format <format>', 'Response format').choices([ 'json', 'yaml', ])) .description('Show cli configuration'); h.action(async (cmd) => { try { log({ configs: datastoreConfigs, datastores: Array.from(datastores.keys()), }, cmd.format); } catch (err) { log(err, cmd.format); } }); } function run() { const h = program.command('run').description('CLI infinite command'); let cliName; const commands = new Map(); const defaultOptions = new Map(); function exitOverride(program, sub = '') { const name = ((sub ? sub + ' ' : '') + program.name()).replace(cliName + ' ', ''); if (sub === '') { cliName = name; } program.exitOverride(); commands.set(name, program); if (!defaultOptions.has(name)) { defaultOptions.set(name, { args: (0, cloneDeep_1.default)(program.args), // @ts-ignore _optionValues: (0, cloneDeep_1.default)(program._optionValues), // @ts-ignore _optionValueSources: (0, cloneDeep_1.default)(program._optionValueSources), }); } for (const prog of program.commands) { exitOverride(prog, name); } } function reset(program, sub = '') { const name = ((sub ? sub + ' ' : '') + program.name()).replace(cliName + ' ', ''); const c = defaultOptions.get(name); program.args = (0, cloneDeep_1.default)(c?.args ?? []); // @ts-ignore program._optionValues = (0, cloneDeep_1.default)(c?._optionValues ?? {}); // @ts-ignore program._optionValueSources = (0, cloneDeep_1.default)(c?._optionValueSources ?? {}); for (const prog of program.commands) { reset(prog, name); } } h.action(async (cmd) => { const history = []; const historyPath = node_os_1.default.homedir() + '/.ds_history'; process.on('beforeExit', () => { console.log('\nBye!'); }); try { await node_fs_1.default.promises .readFile(historyPath) .then((h) => { history.push(...h.toString().split('\n').reverse()); }) .catch(() => { // ... }); const writeStream = node_fs_1.default.createWriteStream(historyPath, { flags: 'a' }); isRun = true; exitOverride(program); const completer = (input) => { const completions = [...history, ...Array.from(commands.keys())]; const hits = (0, uniq_1.default)(completions .filter((c) => new RegExp('^' + input, 'i').test(c)) .map((h) => h.replace(new RegExp('^(' + input + '[^\\s]+).*', 'i'), '$1'))); return [hits.length ? hits : completions, input]; }; const readline = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout, history, completer }); let stdin; console.log(` Datastore cli@${CLI_VERSION}`); do { stdin = await readline.question('> '); history.push(stdin); writeStream.write(stdin + '\n'); if (stdin === 'exit') { break; } try { reset(program); console.log(''); program.showHelpAfterError(false); await program.parseAsync((0, shell_quote_1.parse)(stdin), { from: 'user', }); } catch (err2) { // ... } console.log(''); } while (stdin !== 'exit'); readline.close(); } catch (err) { log(err, cmd.format); } }); } async function cli(argv = process.argv) { const _argv = [...process.argv]; // clone argv. const cleanArgv = _argv.slice(2); // remove node path etc. const firstArg = cleanArgv // first command ignore lfags. .filter((v) => !/^--?/.test(v)) .shift(); const isRunCli = cleanArgv.length === 0; if (isRunCli === true) { argv.push('run'); } else if (firstArg !== 'run' && !program.commands.some((cmd) => cmd.name() === firstArg)) { _argv.splice(2, 0, 'help'); } program .command('heartbeat') .description('Check the availability of the service (/heartbeat)') .action(async (cmd) => { for (const [name, datastore] of datastores) { try { const { data } = await datastore.heartbeat(); log({ datastore: name, response: data }, cmd.format); } catch (err) { console.error('Datastore heartbeat failed', { datastore: name, config: datastore.config, }); } } }); for (const [name, datastore] of datastores) { try { const dsProgram = new commander_1.Command(name); dsProgram.summary(`datastore: ${datastore.config.baseUrl}`); const { data: _models } = await datastore.getModels(); const modelConfigs = _models; for (const model of Object.values(modelConfigs)) { try { model.datastore = name; dsProgram.addCommand((0, models_1.default)(services, model)); } catch (errCommand) { console.log('[Error] Sub command', { datastore: name, model: model.name, }); console.error(errCommand); } } program.addCommand(dsProgram); } catch (err) { // console.error(err); } } config(); program.addCommand((0, admin_1.default)(services)); program.addCommand((0, data_1.default)(services)); program.addCommand((0, security_1.default)(services)); isRunCli === false && program.addCommand((0, runner_1.default)()); isRunCli === false && stream(); if (isRunCli === true) { console.log(program.helpInformation()); run(); } return program.parse(argv); } if (!module.parent) { cli(); } //# sourceMappingURL=index.js.map