@sidekick-coder/db
Version:
Cli Tool to manipulate data from diferent sources
657 lines (645 loc) • 17.5 kB
JavaScript
;
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;