UNPKG

@sidekick-coder/db

Version:

Cli Tool to manipulate data from diferent sources

657 lines (645 loc) 17.5 kB
'use strict'; var valibot = require('valibot'); var path = require('path'); var fs = require('fs'); require('url'); var yaml = require('yaml'); var qs = require('qs'); var inquirer = require('@inquirer/prompts'); var omit = require('lodash-es/omit.js'); var pick = require('lodash-es/pick.js'); var lodashEs = require('lodash-es'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var valibot__namespace = /*#__PURE__*/_interopNamespace(valibot); var path__default = /*#__PURE__*/_interopDefault(path); var fs__default = /*#__PURE__*/_interopDefault(fs); var qs__default = /*#__PURE__*/_interopDefault(qs); var inquirer__namespace = /*#__PURE__*/_interopNamespace(inquirer); var omit__default = /*#__PURE__*/_interopDefault(omit); var pick__default = /*#__PURE__*/_interopDefault(pick); // src/core/provider/defineProvider.ts function defineProvider(payload) { return payload; } // 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 = yaml.parse; var stringify = yaml.stringify; var YAML = { parse, stringify }; function readFileSync(path4) { const [content, error] = tryCatch.sync(() => fs__default.default.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 path.resolve(path.dirname(folder), value); } return value; }; } var schema = valibot__namespace.optional( valibot__namespace.pipe( valibot__namespace.any(), valibot__namespace.transform((value) => { if (typeof value == "object") { return value; } if (/\.yml$/.test(value)) { const file = value.replace(/^@/, ""); const folder = path.dirname(file); return filesystem.readSync.yaml(value.replace(/^@/, ""), { reviver: createReviver(folder) }); } if (typeof value == "string" && value.includes("=")) { const result = qs__default.default.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__namespace.record(valibot__namespace.string(), valibot__namespace.any()) ) ); function createPathNode() { return { resolve: (...args) => path__default.default.resolve(...args), join: (...args) => path__default.default.join(...args), dirname: (args) => path__default.default.dirname(args), basename: (args) => path__default.default.basename(args) }; } // src/core/validator/valibot.ts var stringList = valibot__namespace.pipe( valibot__namespace.any(), valibot__namespace.transform((value) => { if (typeof value === "string") { return value.split(","); } if (Array.isArray(value)) { return value; } }), valibot__namespace.array(valibot__namespace.string()) ); function array2(s) { return valibot__namespace.pipe( v2.union([v2.array(s), s]), valibot__namespace.transform((value) => Array.isArray(value) ? value : [value]), valibot__namespace.array(s) ); } function path3(dirname2, path4 = createPathNode()) { return valibot__namespace.pipe( valibot__namespace.string(), valibot__namespace.transform((value) => path4.resolve(dirname2, value)) ); } function uint8() { return valibot__namespace.pipe( valibot__namespace.any(), valibot__namespace.check((value) => value instanceof Uint8Array), valibot__namespace.transform((value) => value) ); } var prompts = { password: (options) => valibot__namespace.optionalAsync(valibot__namespace.string(), () => { return inquirer__namespace.password({ message: "Enter password", ...options }); }) }; var extras = { array: array2, vars: schema, stringList, path: path3, uint8, number: valibot__namespace.pipe( valibot__namespace.any(), valibot__namespace.transform(Number), valibot__namespace.check((n) => !isNaN(n)), valibot__namespace.number() ) }; var vWithExtras = { ...valibot__namespace, extras, prompts }; var v2 = vWithExtras; // src/core/validator/validate.ts function validate(cb, payload) { const schema2 = typeof cb === "function" ? cb(v2) : cb; const { output, issues, success } = v2.safeParse(schema2, 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 schema2; if (typeof cb === "function") { schema2 = cb(v2); } else { schema2 = cb; } const { output, issues, success } = await v2.safeParseAsync(schema2, 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; }; function getOne(value, keys) { for (const key of keys) { if (lodashEs.has(value, key)) { return lodashEs.get(value, key); } } } function toDataItem(notionObject) { const result = {}; const entries = Object.entries(notionObject.properties); for (const [key, value] of entries) { if (value.id === "title") { result[key] = value.title.map((t) => t.plain_text).join(""); continue; } if (value.type === "status") { result[key] = lodashEs.get(value, "status.name"); continue; } if (value.type === "rich_text") { const text = value.rich_text.map((t) => t.plain_text).join(""); result[key] = text; continue; } if (value.type === "number") { result[key] = value.number; continue; } if (value.type === "formula") { result[key] = getOne(value.formula, ["number", "string"]); continue; } if (value.type === "select") { result[key] = lodashEs.get(value, "select.name"); continue; } if (value.type === "multi_select") { result[key] = value.multi_select.map((s) => s == null ? void 0 : s.name); continue; } if (value.type === "url") { result[key] = value.url; continue; } if (value.type === "files") { result[key] = value.files; continue; } if (value.type === "last_edited_time") { result[key] = value.last_edited_time; continue; } if (value.type === "created_time") { result[key] = value.created_time; continue; } } return result; } function toNotionObject(itemData, properties) { const result = { properties: {} }; for (const [key, value] of Object.entries(itemData)) { const property = properties[key]; if (!property) continue; if (property.type === "title") { result.properties[key] = { title: [ { type: "text", text: { content: value } } ] }; continue; } if (property.type === "rich_text") { result.properties[key] = { rich_text: [ { type: "text", text: { content: String(value) } } ] }; continue; } if (property.type === "status") { const options = lodashEs.get(property, "status.options", []); const status = options.find((o) => { if (o.name === value) return o; if (o.id === value) return o; }); if (!status) continue; result.properties[key] = { status }; continue; } if (property.type === "multi_select") { const options = lodashEs.get(property, "multi_select.options", []); const multiSelect = value.map((v3) => { const option = options.find((o) => { if (o.name === v3) return o; if (o.id === v3) return o; }); return option; }); result.properties[key] = { multi_select: multiSelect }; continue; } if (property.type === "number") { result.properties[key] = { number: Number(value) }; continue; } if (property.type === "formula") { result.properties[key] = { formula: value }; continue; } if (property.type === "select") { result.properties[key] = { select: { name: value } }; continue; } if (property.type === "multi_select") { result.properties[key] = { multi_select: value.map((s) => ({ name: s })) }; continue; } if (property.type === "url") { result.properties[key] = { url: value }; continue; } if (property.type === "files") { result.properties[key] = { files: value }; continue; } if (property.type === "last_edited_time") { result.properties[key] = { last_edited_time: value }; continue; } if (property.type === "created_time") { result.properties[key] = { created_time: value }; continue; } if (property.type === "date") { result.properties[key] = { date: value }; continue; } } return result; } // src/providers/notion/parseWhere.ts var dateOperatorsMap = { eq: "equals", ne: "does_not_equal", gt: "after", gte: "on_or_after", lt: "before", lte: "on_or_before" }; function parseWhere(where, properties) { const { and, or, ...rest } = where; const property = properties[rest.field]; const result = { and: [], or: [] }; if (rest.operator === "in") { const or2 = rest.value.map((v3) => ({ property: rest.field, [property.type]: { equals: v3 } })); return { or: or2 }; } if (rest.operator === "exists") { return { property: rest.field, [property.type]: { is_not_empty: rest.value ? true : void 0, is_empty: rest.value ? void 0 : true } }; } if ((property == null ? void 0 : property.type) === "formula") { return { property: rest.field, [property.type]: { string: { contains: rest.value } } }; } if (property && (rest == null ? void 0 : rest.field) && (rest == null ? void 0 : rest.operator)) { const notionOperator = dateOperatorsMap[rest.operator] || "equals"; return { property: rest.field, [property.type]: { [notionOperator]: rest.value } }; } if (and == null ? void 0 : and.length) { and.forEach((w) => { result.and.push(parseWhere(w, properties)); }); } if (or == null ? void 0 : or.length) { or.forEach((w) => { result.or.push(parseWhere(w, properties)); }); } if (result.and.length === 1 && !result.or.length) { return result.and[0]; } if (!result.or.length) { delete result.or; } if (!result.and.length) { delete result.and; } return result; } // src/providers/notion/provider.ts var provider = defineProvider((config) => { const schema2 = v2.object({ secret_token: v2.string(), database_id: v2.string() }); const { secret_token, database_id } = validate(schema2, config); const api = async (path4, options = {}) => { const defaultOptions = { headers: { "Authorization": `Bearer ${secret_token}`, "Notion-Version": "2022-06-28", "Content-Type": "application/json" } }; const [response, error] = await tryCatch( async () => fetch(`https://api.notion.com/v1/${path4}`, lodashEs.merge(defaultOptions, options)) ); if (error) { throw error; } const body = await response.json(); if (!response.ok) { console.error(body); throw new Error("Notion API error"); } if (body.object === "error") { console.error(body); throw new Error("Notion API error"); } return body; }; async function findProperties() { const response = await api(`databases/${database_id}`); return response.properties; } const list = async (options) => { const where = options == null ? void 0 : options.where; const exclude = options == null ? void 0 : options.exclude; const include = options == null ? void 0 : options.include; const limit = (options == null ? void 0 : options.limit) || 100; const cursor = options == null ? void 0 : options.cursor; const properties = await findProperties(); const body = { page_size: limit, start_cursor: cursor }; if (where && Object.keys(where).length) { body.filter = parseWhere(where, properties); } const { results, has_more, next_cursor, request_id } = await api( `databases/${database_id}/query`, { method: "POST", body: JSON.stringify(body) } ); let items = []; for (const notionObject of results) { const item = toDataItem(notionObject); item._raw = notionObject; item._id = notionObject.id; items.push(item); } if (include == null ? void 0 : include.length) { items = items.map((item) => pick__default.default(item, include)); } if ((exclude == null ? void 0 : exclude.length) && !(include == null ? void 0 : include.length)) { items = items.map((item) => omit__default.default(item, exclude)); } if (items.length && !include && !exclude) { const keys = Object.keys(items[0]).filter((k) => k !== "_id" && k.startsWith("_")); items = items.map((item) => omit__default.default(item, keys)); } return { meta: { has_more, next_cursor, request_id }, data: items }; }; const find = async (options) => { const { data: items } = await list({ ...options, limit: 1 }); return items[0] || null; }; const create = async (options) => { const { data } = options; const properties = await findProperties(); const notionObject = toNotionObject(data, properties); notionObject.parent = { database_id }; const response = await api(`pages`, { method: "POST", body: JSON.stringify(notionObject) }); return toDataItem(response); }; const update = async (options) => { const { data, where } = options; const page = await list({ where, exclude: [] }); const items = page.data; const properties = await findProperties(); let count = 0; for await (const item of items) { count++; const notionObject = toNotionObject(data, properties); await api(`pages/${item._id}`, { method: "PATCH", body: JSON.stringify(notionObject) }); } return { count }; }; const destroy = async (options) => { const { where } = options; const { data } = await list({ where, exclude: [] }); let count = 0; for await (const item of data) { count++; await api(`pages/${item._id}`, { method: "PATCH", body: JSON.stringify({ archived: true }) }); } return { count }; }; return { list, find, create, update, destroy, findProperties }; }); exports.provider = provider;