UNPKG

@sidekick-coder/db

Version:

Cli Tool to manipulate data from diferent sources

429 lines (422 loc) 11.4 kB
import fs, { promises } from 'fs'; import * as valibot from 'valibot'; import path, { dirname, resolve } from 'path'; import { pathToFileURL } from 'url'; import { parse as parse$2, stringify as stringify$2 } from 'yaml'; import qs, { stringify as stringify$1, parse as parse$1 } from 'qs'; import * as inquirer from '@inquirer/prompts'; import { mergeWith, merge, omit } from 'lodash-es'; import fg from 'fast-glob'; import minimist from 'minimist'; // src/utils/vars.ts // src/utils/tryCatch.ts async function tryCatch(tryer) { try { const result = await tryer(); return [result, null]; } catch (error) { return [null, error]; } } tryCatch.sync = function(tryer) { try { const result = tryer(); return [result, null]; } catch (error) { return [null, error]; } }; var parse = parse$2; var stringify = stringify$2; var YAML = { parse, stringify }; function readFileSync(path4) { const [content, error] = tryCatch.sync(() => fs.readFileSync(path4)); if (error) { return null; } return new Uint8Array(content); } readFileSync.text = function(filepath, defaultValue = "") { const content = readFileSync(filepath); if (!content) { return defaultValue; } return new TextDecoder().decode(content); }; readFileSync.json = function(path4, options) { const content = readFileSync.text(path4); if (!content) { return (options == null ? void 0 : options.default) || null; } const [json, error] = tryCatch.sync(() => JSON.parse(content, options == null ? void 0 : options.reviver)); return error ? (options == null ? void 0 : options.default) || null : json; }; readFileSync.yaml = function(path4, options) { const content = readFileSync.text(path4); if (!content) { return (options == null ? void 0 : options.default) || null; } const [yml, error] = tryCatch.sync(() => YAML.parse(content, options == null ? void 0 : options.parseOptions)); return error ? (options == null ? void 0 : options.default) || null : yml; }; var filesystem = { readSync: readFileSync}; function createReviver(folder) { return (_, value) => { if (typeof value == "string" && value.startsWith("./")) { return resolve(dirname(folder), value); } return value; }; } var schema = valibot.optional( valibot.pipe( valibot.any(), valibot.transform((value) => { if (typeof value == "object") { return value; } if (/\.yml$/.test(value)) { const file = value.replace(/^@/, ""); const folder = dirname(file); return filesystem.readSync.yaml(value.replace(/^@/, ""), { reviver: createReviver(folder) }); } if (typeof value == "string" && value.includes("=")) { const result = qs.parse(value, { allowEmptyArrays: true }); return result; } if (typeof value == "string" && value.startsWith("{")) { return JSON.parse(value); } if (typeof value == "string" && value.startsWith("[")) { return JSON.parse(value); } return value; }), valibot.record(valibot.string(), valibot.any()) ) ); function createPathNode() { return { resolve: (...args) => path.resolve(...args), join: (...args) => path.join(...args), dirname: (args) => path.dirname(args), basename: (args) => path.basename(args) }; } // src/core/validator/valibot.ts var stringList = valibot.pipe( valibot.any(), valibot.transform((value) => { if (typeof value === "string") { return value.split(","); } if (Array.isArray(value)) { return value; } }), valibot.array(valibot.string()) ); function array2(s) { return valibot.pipe( v2.union([v2.array(s), s]), valibot.transform((value) => Array.isArray(value) ? value : [value]), valibot.array(s) ); } function path3(dirname2, path4 = createPathNode()) { return valibot.pipe( valibot.string(), valibot.transform((value) => path4.resolve(dirname2, value)) ); } function uint8() { return valibot.pipe( valibot.any(), valibot.check((value) => value instanceof Uint8Array), valibot.transform((value) => value) ); } var prompts = { password: (options) => valibot.optionalAsync(valibot.string(), () => { return inquirer.password({ message: "Enter password", ...options }); }) }; var extras = { array: array2, vars: schema, stringList, path: path3, uint8, number: valibot.pipe( valibot.any(), valibot.transform(Number), valibot.check((n) => !isNaN(n)), valibot.number() ) }; var vWithExtras = { ...valibot, extras, prompts }; var v2 = vWithExtras; // src/core/validator/validate.ts function validate(cb, payload) { const schema3 = typeof cb === "function" ? cb(v2) : cb; const { output, issues, success } = v2.safeParse(schema3, payload); if (!success) { const flatten = v2.flatten(issues); const messages = []; if (flatten.root) { messages.push(...flatten.root); } if (flatten.nested) { Object.entries(flatten.nested).forEach((entry) => { const [key, value] = entry; messages.push(...value.map((v3) => `${key}: ${v3}`)); }); } const message = messages.length ? messages.join(", ") : "Validation failed"; const error = new Error(message); error.name = "ValidationError"; Object.assign(error, { messages }); throw error; } return output; } validate.async = async function(cb, payload) { let schema3; if (typeof cb === "function") { schema3 = cb(v2); } else { schema3 = cb; } const { output, issues, success } = await v2.safeParseAsync(schema3, payload); if (!success) { const error = new Error("Validation failed"); const flatten = v2.flatten(issues); const details = { ...flatten.root, ...flatten.nested }; Object.assign(error, { details }); throw error; } return output; }; // src/core/database/where.ts function parseCondition(condition) { if (condition.value === "$true") { return { or: [], and: [ { field: condition.field, operator: condition.operator, value: true } ] }; } if (condition.value === "$false") { return { or: [], and: [ { field: condition.field, operator: condition.operator, value: false } ] }; } if (condition.value === "$exists") { return { or: [], and: [ { field: condition.field, operator: "exists", value: true } ] }; } if (typeof condition.value == "string" && condition.value.startsWith("$in")) { const values = condition.value.slice(4, -1).split(","); return { or: [], and: [ { field: condition.field, operator: "in", value: values } ] }; } return { and: [condition], or: [] }; } function transformWhere(where) { const { and, or, ...rest } = where; const result = { and: [], or: [] }; if ((rest == null ? void 0 : rest.field) && (rest == null ? void 0 : rest.operator)) { return { field: rest.field, operator: rest.operator, value: rest.value }; } for (const [key, value] of Object.entries(rest)) { const { and: and2, or: or2 } = parseCondition({ field: (value == null ? void 0 : value.field) || key, operator: (value == null ? void 0 : value.operator) || "eq", value: (value == null ? void 0 : value.value) || value }); if (and2) { result.and.push(...and2); } if (or2) { result.or.push(...or2); } } if (and == null ? void 0 : and.length) { and.forEach((w) => { result.and.push(transformWhere(w)); }); } if (or == null ? void 0 : or.length) { or.forEach((w) => { result.or.push(transformWhere(w)); }); } if (!result.or.length) { delete result.or; } if (!result.and.length) { delete result.and; } return result; } var schema2 = v2.pipe(v2.any(), v2.transform(transformWhere)); function parseFile(filename) { const extensions = [".yml", ".yaml", ".json"]; if (extensions.some((ext) => filename.endsWith(`*${ext}`))) { const pattern = fg.convertPathToPattern(filename); const files = fg.sync(pattern); return files.map((file) => parseFile(file)); } const contents = fs.readFileSync(filename, "utf-8"); const reviver = (key, value) => { if (typeof value == "string" && extensions.some((ext) => value.endsWith(ext))) { return parseFile(value); } return value; }; if (filename.endsWith(".json")) { return JSON.parse(contents, reviver); } if (filename.endsWith(".yml") || filename.endsWith(".yaml")) { return YAML.parse(contents, reviver); } return contents; } function parseVars(payload) { const items = Array.isArray(payload) ? payload : [payload]; if (!items.length) { return {}; } let result = {}; items.forEach((item) => { const vars = validate((v3) => v3.extras.vars, item); result = mergeWith(result, vars, (objValue, srcValue) => { if (Array.isArray(objValue)) { return objValue.concat(srcValue); } }); }); return result; } function parseWhere(payload) { const record2 = parseVars(payload); return validate(() => schema2, record2); } var parse2 = parse$2; var stringify2 = stringify$2; var QS = { parse: parse$1, stringify: stringify$1 }; async function importAll(path4, options) { const files = await promises.readdir(path4); const result = {}; for await (const file of files) { if ((options == null ? void 0 : options.exclude) && options.exclude.includes(file)) { continue; } const url = pathToFileURL(resolve(path4, file)); const module = await import(url.href); result[file] = module; } return result; } function parseOptions(args, options) { var _a, _b, _c; const flags = minimist(args, { string: ["where", "view", "data", "render", "id"], alias: { where: "w", view: "v", data: "d", render: "r", render_options: "ro", sortBy: ["sort-by"], sortDesc: ["sort-desc"] } }); const result = validate( (v3) => { var _a2, _b2; return v3.objectWithRest( { view: v3.optional(v3.string(), (_b2 = (_a2 = options == null ? void 0 : options.databaseDefinition) == null ? void 0 : _a2.view) == null ? void 0 : _b2.default), render: v3.optional(v3.string()), data: v3.optional(v3.extras.vars), where: v3.optional(v3.extras.vars), include: v3.optional(v3.extras.stringList), exclude: v3.optional(v3.extras.stringList), render_options: v3.optional(v3.extras.vars, {}), sortBy: v3.optional(v3.extras.stringList), sortDesc: v3.optional(v3.extras.array(v3.boolean())) }, v3.any() ); }, flags ); if (result.view && (options == null ? void 0 : options.databaseDefinition)) { const view = (_c = (_b = (_a = options.databaseDefinition) == null ? void 0 : _a.view) == null ? void 0 : _b.sources) == null ? void 0 : _c.find((v3) => v3.name === result.view); merge(result, omit(view, "name")); } return result; } export { QS, importAll, parse2 as parse, parseFile, parseOptions, parseVars, parseWhere, stringify2 as stringify };