@sidekick-coder/db
Version:
Cli Tool to manipulate data from diferent sources
1,834 lines (1,804 loc) • 67.5 kB
JavaScript
import * as valibot from 'valibot';
import path, { dirname, resolve, join } from 'path';
import fs2 from 'fs';
import 'url';
import { parse as parse$1, stringify as stringify$1 } from 'yaml';
import qs from 'qs';
import * as inquirer from '@inquirer/prompts';
import { omit, merge, get, pick, orderBy, has } from 'lodash-es';
import sift from 'sift';
import cp from 'child_process';
import os from 'os';
import { format } from 'date-fns';
import crypto2 from 'crypto';
import omit4 from 'lodash-es/omit.js';
import pick2 from 'lodash-es/pick.js';
import ms from 'ms';
// src/core/validator/valibot.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$1;
var stringify = stringify$1;
var YAML = {
parse,
stringify
};
function readFileSync(path4) {
const [content, error] = tryCatch.sync(() => fs2.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 [json2, error] = tryCatch.sync(() => JSON.parse(content, options == null ? void 0 : options.reviver));
return error ? (options == null ? void 0 : options.default) || null : json2;
};
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 schema11 = typeof cb === "function" ? cb(v2) : cb;
const { output, issues, success } = v2.safeParse(schema11, 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 schema11;
if (typeof cb === "function") {
schema11 = cb(v2);
} else {
schema11 = cb;
}
const { output, issues, success } = await v2.safeParseAsync(schema11, 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));
// src/core/database/list.ts
var schema3 = vWithExtras.objectWithRest(
{
where: vWithExtras.optional(schema2),
limit: vWithExtras.optional(vWithExtras.number()),
page: vWithExtras.optional(vWithExtras.number()),
cursor: vWithExtras.optional(vWithExtras.string()),
include: vWithExtras.optional(vWithExtras.extras.stringList),
exclude: vWithExtras.optional(vWithExtras.extras.stringList),
sortBy: vWithExtras.optional(vWithExtras.extras.array(vWithExtras.string())),
sortDesc: vWithExtras.optional(vWithExtras.extras.array(vWithExtras.boolean()))
},
vWithExtras.any()
);
async function list(provider5, payload) {
const options = validate(schema3, payload);
if (!provider5.list) {
throw new Error(`Provider does not support listing`);
}
return await provider5.list(options);
}
// src/core/database/find.ts
var schema4 = vWithExtras.objectWithRest(
{
where: vWithExtras.optional(schema2),
include: vWithExtras.optional(vWithExtras.extras.stringList),
exclude: vWithExtras.optional(vWithExtras.extras.stringList)
},
vWithExtras.any()
);
async function find(provider5, payload) {
const options = validate(schema4, payload);
if (!provider5.find) {
throw new Error(`Provider does not support find`);
}
return await provider5.find(options);
}
// src/core/database/data.ts
var schema5 = () => v2.pipe(
v2.record(v2.string(), v2.any()),
v2.transform((value) => {
const result = {};
Object.entries(value).forEach(([k, v3]) => {
if (v3 === "$undefined") {
v3 = void 0;
}
result[k] = v3;
});
return result;
})
);
// src/core/database/create.ts
var schema6 = vWithExtras.objectWithRest(
{
data: schema5()
},
vWithExtras.any()
);
async function create(provider5, payload) {
if (!provider5.create) {
throw new Error(`Provider does not support creating`);
}
const options = validate(schema6, payload);
return provider5.create(options);
}
// src/core/database/update.ts
var schema7 = vWithExtras.objectWithRest(
{
data: schema5(),
where: vWithExtras.optional(schema2),
limit: vWithExtras.optional(vWithExtras.number())
},
vWithExtras.any()
);
async function update(provider5, payload) {
const options = validate(schema7, payload);
if (!provider5.update) {
throw new Error(`Provider does not support updating`);
}
return await provider5.update(options);
}
// src/core/database/destroy.ts
var schema8 = vWithExtras.object({
where: vWithExtras.optional(schema2),
limit: vWithExtras.optional(vWithExtras.number())
});
async function destroy(provider5, payload) {
if (!provider5.destroy) {
throw new Error(`Provider does not support destroying`);
}
const options = validate(schema8, payload);
return await provider5.destroy(options);
}
// src/core/provider/defineProvider.ts
function defineProvider(payload) {
return payload;
}
// src/core/parsers/markdown.ts
function parse2(contents) {
let body = contents;
const result = {};
if (contents.startsWith("---")) {
const [, frontmatter, rest] = contents.split("---");
Object.assign(result, YAML.parse(frontmatter));
body = rest.trim();
}
result["body"] = body || "";
return result;
}
function stringify2(data) {
const { body, ...properties } = data;
let result = "";
if (properties && Object.keys(properties).length) {
result += `---
${YAML.stringify(properties)}---
`;
}
if (body) {
result += body;
}
return result;
}
var MD = {
parse: parse2,
stringify: stringify2
};
// src/core/parsers/all.ts
var parsers = [];
var json = {
name: "json",
ext: "json",
parse: JSON.parse,
stringify: (contents) => JSON.stringify(contents, null, 4)
};
var markdown = {
name: "markdown",
ext: "md",
parse: MD.parse,
stringify: (contents) => MD.stringify(contents)
};
parsers.push(json, markdown);
parsers.push({
name: "yaml",
ext: "yaml",
parse: YAML.parse,
stringify: (contents) => YAML.stringify(contents, null, 4)
});
parsers.push({
name: "yml",
ext: "yml",
parse: YAML.parse,
stringify: (contents) => YAML.stringify(contents, null, 4)
});
var operatorMap = {
eq: "$eq",
ne: "$ne",
gt: "$gt",
gte: "$gte",
lt: "$lt",
lte: "$lte",
in: "$in",
exists: "$exists"
};
function parseCondition2(condition) {
const { field, operator, value } = condition;
if (!field || !operator) {
console.error("Invalid condition:", condition);
return {};
}
const siftOperator = operatorMap[operator];
if (!siftOperator) {
throw new Error(`Unsupported operator: ${operator}`);
}
return {
[field]: {
[siftOperator]: value
}
};
}
function parseGroup(group) {
const { and, or, ...rest } = group;
const parsed = {};
if ((rest == null ? void 0 : rest.field) && (rest == null ? void 0 : rest.operator)) {
return parseCondition2(rest);
}
if (and == null ? void 0 : and.length) {
parsed.$and = and.map(parseGroup);
}
if (or == null ? void 0 : or.length) {
parsed.$or = or.map(parseGroup);
}
return parsed;
}
function parseWhere(payload) {
var _a, _b;
if (!payload) return {};
const transformed = transformWhere(payload);
const parsed = parseGroup(transformed);
if (!((_a = parsed.$and) == null ? void 0 : _a.length)) {
delete parsed.$and;
}
if (!((_b = parsed.$or) == null ? void 0 : _b.length)) {
delete parsed.$or;
}
return parsed;
}
function query(data, options) {
var _a;
const { where, include, exclude } = options;
const limit = options.limit;
const offset = options.offset || 0;
const siftQuery = parseWhere(where);
let items = data.filter(sift(siftQuery));
if (include == null ? void 0 : include.length) {
items = items.map((item) => pick(item, options.include));
}
if ((exclude == null ? void 0 : exclude.length) && !(include == null ? void 0 : include.length)) {
items = items.map((item) => omit(item, exclude));
}
if ((_a = options.sortBy) == null ? void 0 : _a.length) {
const sort = options.sortBy.map((f, i) => ({
field: f,
order: options.sortDesc && options.sortDesc[i] ? "desc" : "asc"
}));
items = orderBy(
items,
sort.map((s) => s.field),
sort.map((s) => s.order)
);
}
items = items.slice(offset, limit ? offset + limit : void 0);
return items;
}
function count(data, options) {
const items = query(data, { where: options.where });
return items.length;
}
// src/providers/file/list.ts
async function list2(payload) {
const { filesystem: filesystem2, root, options, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
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;
const page = (options == null ? void 0 : options.page) || 1;
const sortBy = options == null ? void 0 : options.sortBy;
const sortDesc = options == null ? void 0 : options.sortDesc;
const files = filesystem2.readdirSync(root).filter((f) => ![".db"].includes(f));
const result = [];
for (const file of files) {
const filename = resolve3(file);
const content = filesystem2.readSync.text(filename);
const item = {
id: file.replace(`.${parser.ext}`, ""),
filename,
raw: content
};
Object.assign(item, parser.parse(content));
result.push(item);
}
const items = query(result, {
where,
exclude,
include,
limit,
offset: page > 1 ? (page - 1) * limit : 0,
sortBy,
sortDesc
});
const meta = {
total: count(result, { where }),
limit,
total_pages: limit ? Math.ceil(result.length / limit) : 1
};
return {
meta,
data: items
};
}
// src/providers/file/update.ts
async function update2(payload) {
const { filesystem: filesystem2, root, options, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
const data = options.data;
const { data: items } = await list2({
filesystem: filesystem2,
root,
parser,
options: {
where: options.where,
limit: options.limit
}
});
const hideKeys = ["id", "folder", "raw", "lock"];
for (const item of items) {
const filename = resolve3(`${item.id}.${parser.ext}`);
const properties = omit({ ...item, ...data }, hideKeys);
const content = parser.stringify(properties);
filesystem2.writeSync.text(filename, content);
}
return { count: items.length };
}
function createFsNode() {
const exists = async (path4) => {
const [, error] = await tryCatch(() => fs2.promises.access(path4));
return error ? false : true;
};
const existsSync = (path4) => {
const [, error] = tryCatch.sync(() => fs2.accessSync(path4));
return error ? false : true;
};
const read = async (path4) => {
const [content, error] = await tryCatch(() => fs2.promises.readFile(path4));
if (error) {
return null;
}
return new Uint8Array(content);
};
const readSync = (path4) => {
const [content, error] = tryCatch.sync(() => fs2.readFileSync(path4));
if (error) {
return null;
}
return new Uint8Array(content);
};
const readdir = async (path4) => {
const [files, error] = await tryCatch(() => fs2.promises.readdir(path4));
return error ? [] : files;
};
const readdirSync = (path4) => {
const [files, error] = tryCatch.sync(() => fs2.readdirSync(path4));
return error ? [] : files;
};
const write = async (path4, content) => {
const [, error] = await tryCatch(() => fs2.promises.writeFile(path4, content));
if (error) {
throw error;
}
};
const writeSync = (path4, content) => {
const [, error] = tryCatch.sync(() => fs2.writeFileSync(path4, content));
if (error) {
throw error;
}
};
const mkdir2 = async (path4) => {
const [, error] = await tryCatch(() => fs2.promises.mkdir(path4));
if (error) {
throw error;
}
};
const mkdirSync = (path4) => {
const [, error] = tryCatch.sync(() => fs2.mkdirSync(path4));
if (error) {
throw error;
}
};
const remove = async (path4) => {
const [, error] = await tryCatch(() => fs2.promises.rm(path4, { recursive: true }));
if (error) {
throw error;
}
};
const removeSync = (path4) => {
const [, error] = tryCatch.sync(() => fs2.rmSync(path4, { recursive: true }));
if (error) {
throw error;
}
};
const removeAt = async (path4, milliseconds) => {
if (os.platform() === "win32") {
const script = `
Set objShell = CreateObject("WScript.Shell")
objShell.Run "cmd /c timeout /t ${milliseconds / 1e3} && del /f /q ${path4}", 0, True
`;
const key = Math.random().toString(36).substring(7);
const tempScriptPath = join(os.tmpdir(), `db-delete-file-${key}.vbs`);
fs2.writeFileSync(tempScriptPath, script);
const child = cp.spawn("cscript.exe", [tempScriptPath], {
detached: true,
stdio: "ignore"
});
child.unref();
return true;
}
return false;
};
return {
exists,
existsSync,
read,
readSync,
readdir,
readdirSync,
write,
writeSync,
mkdir: mkdir2,
mkdirSync,
remove,
removeSync,
removeAt
};
}
// src/core/filesystem/createFilesystem.ts
function createFilesystem(options = {}) {
const fs3 = options.fs || createFsNode();
const path4 = options.path || createPathNode();
const locks2 = /* @__PURE__ */ new Set();
function awaitLock2(filepath, timeout = 1e3) {
return new Promise((resolve3, reject) => {
const interval = setInterval(() => {
if (!locks2.has(filepath)) {
clearInterval(interval);
resolve3();
}
}, 100);
setTimeout(() => {
clearInterval(interval);
reject(new Error("Timeout"));
}, timeout);
});
}
async function read(filepath) {
return fs3.read(filepath);
}
read.text = async function(filepath, options2) {
const content = await read(filepath);
if (!content) {
return (options2 == null ? void 0 : options2.default) || "";
}
return new TextDecoder().decode(content);
};
read.json = async function(filepath, options2) {
const content = await read.text(filepath);
if (!content) {
return (options2 == null ? void 0 : options2.default) || null;
}
const [json2, error] = await tryCatch(() => JSON.parse(content, options2 == null ? void 0 : options2.reviver));
if (options2 == null ? void 0 : options2.schema) {
return validate(options2.schema, json2);
}
return error ? (options2 == null ? void 0 : options2.default) || null : json2;
};
read.yaml = async function(filepath, options2) {
const content = await read.text(filepath);
if (!content) {
return (options2 == null ? void 0 : options2.default) || null;
}
const [yml, error] = await tryCatch(() => YAML.parse(content, options2 == null ? void 0 : options2.reviver));
return error ? (options2 == null ? void 0 : options2.default) || null : yml;
};
function readSync(filepath) {
return fs3.readSync(filepath);
}
readSync.text = function(filepath, defaultValue = "") {
const content = readSync(filepath);
if (!content) {
return defaultValue;
}
return new TextDecoder().decode(content);
};
readSync.json = function(filepath, options2) {
const content = readSync.text(filepath);
if (!content) {
return (options2 == null ? void 0 : options2.default) || null;
}
const [json2, error] = tryCatch.sync(() => JSON.parse(content, options2 == null ? void 0 : options2.reviver));
if (options2 == null ? void 0 : options2.schema) {
return validate(options2.schema, json2);
}
return error ? (options2 == null ? void 0 : options2.default) || null : json2;
};
readSync.yaml = function(filepath, options2) {
const content = readSync.text(filepath);
if (!content) {
return (options2 == null ? void 0 : options2.default) || null;
}
const [yml, error] = tryCatch.sync(() => YAML.parse(content, options2 == null ? void 0 : options2.parseOptions));
return error ? (options2 == null ? void 0 : options2.default) || null : yml;
};
async function readdir(filepath) {
return fs3.readdir(filepath);
}
function readdirSync(filepath) {
return fs3.readdirSync(filepath);
}
async function mkdir2(filepath, options2) {
if (await fs3.exists(filepath)) return;
if (options2 == null ? void 0 : options2.recursive) {
const parent = path4.dirname(filepath);
await mkdir2(parent, options2);
}
await fs3.mkdir(filepath);
}
function mkdirSync(filepath, options2) {
if (fs3.existsSync(filepath)) return;
if (options2 == null ? void 0 : options2.recursive) {
const parent = path4.dirname(filepath);
mkdirSync(parent, options2);
}
fs3.mkdirSync(filepath);
}
async function write(filename, content, options2) {
if (locks2.has(filename)) {
await awaitLock2(filename);
}
locks2.add(filename);
if (options2 == null ? void 0 : options2.recursive) {
const parent = path4.dirname(filename);
await mkdir2(parent, { recursive: true });
}
const [, error] = await tryCatch(() => fs3.write(filename, content));
locks2.delete(filename);
if (error) {
throw error;
}
}
write.text = async function(filename, content, options2) {
await write(filename, new TextEncoder().encode(content), options2);
};
write.json = async function(filename, content, options2) {
await write.text(filename, JSON.stringify(content, null, 2), options2);
};
function writeSync(filename, content, options2) {
if (options2 == null ? void 0 : options2.recursive) {
const parent = path4.dirname(filename);
mkdirSync(parent, { recursive: true });
}
fs3.writeSync(filename, content);
}
writeSync.text = function(filename, content, options2) {
writeSync(filename, new TextEncoder().encode(content), options2);
};
writeSync.json = function(filename, content, options2) {
writeSync.text(filename, JSON.stringify(content, null, 2), options2);
};
function remove(filepath) {
return fs3.remove(filepath);
}
function removeSync(filepath) {
return fs3.removeSync(filepath);
}
function removeAt(filepath, miliseconds) {
return fs3.removeAt(filepath, miliseconds);
}
return {
path: path4,
fs: fs3,
exists: fs3.exists,
existsSync: fs3.existsSync,
read,
readSync,
readdir,
readdirSync,
write,
writeSync,
mkdir: mkdir2,
mkdirSync,
remove,
removeSync,
removeAt
};
}
// src/providers/file/create.ts
async function create2(payload) {
const { filesystem: filesystem2, root, options, makeId, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
const data = options.data;
const id = data.id || await makeId();
const filename = resolve3(`${id}.${parser.ext}`);
if (filesystem2.existsSync(filename)) {
throw new Error(`Item with id "${id}" already exists`);
}
const raw = parser.stringify(data);
filesystem2.writeSync.text(filename, raw);
const item = {
id,
raw,
filename,
...data
};
return item;
}
// src/providers/file/find.ts
async function find2(payload) {
const { filesystem: filesystem2, options, root, parser } = payload;
const { data: items } = await list2({
filesystem: filesystem2,
root,
parser,
options: {
...options,
limit: 1
}
});
return items[0] || null;
}
// src/providers/file/destroy.ts
async function destroy2(payload) {
const { filesystem: filesystem2, root, parser, options } = payload;
const { data: items } = await list2({
filesystem: filesystem2,
root,
parser,
options: {
where: options.where,
limit: options.limit
}
});
for (const item of items) {
filesystem2.removeSync(item.filename);
}
return { count: items.length };
}
function createDate() {
return {
name: "date",
async create(config) {
const pattern = config.pattern || "yyyy-MM-dd";
return format(/* @__PURE__ */ new Date(), pattern);
}
};
}
// src/core/idStrategy/createIncremental.ts
function pad(value, size) {
return value.toString().padStart(size, "0");
}
function createIncremental(filesystem2, filename) {
async function create5(payload = {}) {
const options = validate(
(v3) => v3.object({
padding: v3.optional(v3.number(), 2)
}),
payload
);
if (!filesystem2.existsSync(filename)) {
filesystem2.writeSync.json(filename, { last_id: 0 }, { recursive: true });
}
const content = filesystem2.readSync.json(filename, {
schema: (v3) => v3.object({
last_id: v3.number()
})
});
const id = content.last_id + 1;
filesystem2.writeSync.json(filename, { last_id: id });
return pad(id, options.padding);
}
return {
name: "incremental",
create: create5
};
}
function createUuid() {
return {
name: "uuid",
async create() {
return crypto2.randomUUID();
}
};
}
// src/core/idStrategy/createStrategies.ts
function createStrategies(options) {
const { filesystem: filesystem2, root } = options;
return [
createIncremental(filesystem2, filesystem2.path.resolve(root, ".db", "incremental.json")),
createDate(),
createUuid()
];
}
var provider = defineProvider((config, instanceOptions) => {
const filesystem2 = createFilesystem({
fs: instanceOptions.fs,
path: instanceOptions.path
});
const schema11 = v2.object({
path: v2.extras.path(instanceOptions.root, filesystem2.path),
format: v2.optional(v2.picklist(["markdown", "json", "yaml"]), "markdown"),
id_strategy: v2.optional(
v2.object({
name: v2.string(),
options: v2.optional(v2.any())
}),
{ name: "incremental" }
)
});
const { path: path4, format: format2, id_strategy } = validate(schema11, config);
const root = path4;
const strategies = createStrategies({ filesystem: filesystem2, root });
const parser = parsers.find((p) => p.name === format2);
const strategy = strategies.find((s) => s.name === id_strategy.name);
if (!parser) {
throw new Error(`Parser for format "${format2}" not found`);
}
if (!strategy) {
throw new Error(`Strategy for id "${id_strategy}" not found`);
}
const makeId = () => strategy.create(id_strategy.options);
async function open(payload) {
const options = validate(
(v3) => v3.object({
where: v3.optional(schema2),
editor: v3.optional(v3.string())
}),
payload
);
const item = await find2({
root,
filesystem: filesystem2,
parser,
options
});
if (!item) {
throw new Error("Item not found");
}
let bin;
if (process.env.EDITOR) {
bin = process.env.EDITOR;
}
if (payload.editor) {
bin = payload.editor;
}
if (!bin) {
throw new Error(
"No editor found, please set the EDITOR environment variable or pass the editor option"
);
}
cp.spawn(bin, [item.filename], {
stdio: "inherit",
env: process.env
});
}
return {
open,
list: (options = {}) => list2({
root,
filesystem: filesystem2,
parser,
options
}),
find: (options) => find2({
root,
filesystem: filesystem2,
parser,
options
}),
create: (options) => create2({
root,
filesystem: filesystem2,
parser,
makeId,
options
}),
update: (options) => update2({
root,
filesystem: filesystem2,
options,
parser
}),
destroy: (options) => destroy2({
root,
filesystem: filesystem2,
parser,
options
})
};
});
// src/providers/folder/list.ts
async function list3(payload) {
const { filesystem: filesystem2, root, options, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
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;
const page = (options == null ? void 0 : options.page) || 1;
const sortBy = options == null ? void 0 : options.sortBy;
const sortDesc = options == null ? void 0 : options.sortDesc;
let files = filesystem2.readdirSync(root);
const excludeDirs = [".db"];
files = files.filter((file) => !excludeDirs.includes(file));
const result = [];
for (const id of files) {
const filename = resolve3(id, `index.${parser.ext}`);
const folder = resolve3(id);
if (!filesystem2.existsSync(filename)) {
throw new Error(`File "${filename}" not found`);
}
const raw = filesystem2.readSync.text(resolve3(id, `index.${parser.ext}`));
const item = {
id,
folder,
raw
};
Object.assign(item, parser.parse(raw));
result.push(item);
}
const items = query(result, {
where,
exclude,
include,
limit,
offset: page > 1 ? (page - 1) * limit : 0,
sortBy,
sortDesc
});
const meta = {
total: count(result, { where }),
limit,
total_pages: limit ? Math.ceil(result.length / limit) : 1
};
return {
meta,
data: items
};
}
// src/providers/folder/update.ts
async function update3(payload) {
const { filesystem: filesystem2, root, options, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
const data = options.data;
const { data: items } = await list3({
filesystem: filesystem2,
root,
parser,
options: {
where: options.where,
limit: options.limit
}
});
const hideKeys = ["id", "folder", "raw", "lock"];
for (const item of items) {
const filename = resolve3(item.id, `index.${parser.ext}`);
const properties = omit({ ...item, ...data }, hideKeys);
const content = parser.stringify(properties);
filesystem2.writeSync.text(filename, content);
}
return { count: items.length };
}
// src/providers/folder/create.ts
async function create3(payload) {
const { filesystem: filesystem2, root, options, makeId, parser } = payload;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
const data = options.data;
const id = data.id || await makeId();
const folder = resolve3(id);
const filename = resolve3(id, `index.${parser.ext}`);
if (filesystem2.existsSync(folder)) {
throw new Error(`Item with id "${id}" already exists`);
}
const raw = parser.stringify(data);
filesystem2.mkdirSync(resolve3(id));
filesystem2.writeSync.text(filename, raw);
const item = {
id,
raw,
folder,
...data
};
return item;
}
// src/providers/folder/find.ts
async function find3(payload) {
const { filesystem: filesystem2, options, root, parser } = payload;
const { data: items } = await list3({
filesystem: filesystem2,
root,
parser,
options: {
...options,
limit: 1
}
});
return items[0] || null;
}
// src/providers/folder/destroy.ts
async function destroy3(payload) {
const { filesystem: filesystem2, root, parser, options } = payload;
const { data: items } = await list3({
filesystem: filesystem2,
root,
parser,
options: {
where: options.where,
limit: options.limit
}
});
for (const item of items) {
filesystem2.removeSync(item.folder);
}
return { count: items.length };
}
var provider2 = defineProvider((config, instanceConfig) => {
const filesystem2 = createFilesystem({
fs: instanceConfig.fs,
path: instanceConfig.path
});
const schema11 = v2.object({
path: v2.extras.path(instanceConfig.root, filesystem2.path),
format: v2.optional(v2.picklist(["markdown", "json", "yaml"]), "markdown"),
id_strategy: v2.optional(
v2.object({
name: v2.string(),
options: v2.optional(v2.any())
}),
{ name: "incremental" }
)
});
const { path: path4, format: format2, id_strategy } = validate(schema11, config);
const root = path4;
const strategies = createStrategies({ filesystem: filesystem2, root });
const parser = parsers.find((p) => p.name === format2);
const strategy = strategies.find((s) => s.name === id_strategy.name);
if (!parser) {
throw new Error(`Parser for format "${format2}" not found`);
}
if (!strategy) {
throw new Error(`Strategy for id "${id_strategy}" not found`);
}
const makeId = () => strategy.create(id_strategy.options);
async function open(payload) {
const options = validate(
(v3) => v3.object({
where: v3.optional(schema2),
editor: v3.optional(v3.string())
}),
payload
);
const item = await find3({
root,
filesystem: filesystem2,
parser,
options
});
if (!item) {
throw new Error("Item not found");
}
let bin;
if (process.env.EDITOR) {
bin = process.env.EDITOR;
}
if (payload.editor) {
bin = payload.editor;
}
if (!bin) {
throw new Error(
"No editor found, please set the EDITOR environment variable or pass the editor option"
);
}
const filename = filesystem2.path.join(item.folder, `index.${parser.ext}`);
cp.spawn(bin, [filename], {
stdio: "inherit"
});
}
return {
open,
list: (options = {}) => list3({
root,
filesystem: filesystem2,
parser,
options
}),
find: (options) => find3({
root,
filesystem: filesystem2,
parser,
options
}),
create: (options) => create3({
root,
filesystem: filesystem2,
parser,
makeId,
options
}),
update: (options) => update3({
root,
filesystem: filesystem2,
options,
parser
}),
destroy: (options) => destroy3({
filesystem: filesystem2,
root,
parser,
options
})
};
});
function getOne(value, keys) {
for (const key of keys) {
if (has(value, key)) {
return 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] = 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] = 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 = 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 = 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 parseWhere2(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(parseWhere2(w, properties));
});
}
if (or == null ? void 0 : or.length) {
or.forEach((w) => {
result.or.push(parseWhere2(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 provider3 = defineProvider((config) => {
const schema11 = v2.object({
secret_token: v2.string(),
database_id: v2.string()
});
const { secret_token, database_id } = validate(schema11, 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}`, 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 list5 = 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 = parseWhere2(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) => pick2(item, include));
}
if ((exclude == null ? void 0 : exclude.length) && !(include == null ? void 0 : include.length)) {
items = items.map((item) => omit4(item, exclude));
}
if (items.length && !include && !exclude) {
const keys = Object.keys(items[0]).filter((k) => k !== "_id" && k.startsWith("_"));
items = items.map((item) => omit4(item, keys));
}
return {
meta: {
has_more,
next_cursor,
request_id
},
data: items
};
};
const find5 = async (options) => {
const { data: items } = await list5({
...options,
limit: 1
});
return items[0] || null;
};
const create5 = 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 update5 = async (options) => {
const { data, where } = options;
const page = await list5({ where, exclude: [] });
const items = page.data;
const properties = await findProperties();
let count2 = 0;
for await (const item of items) {
count2++;
const notionObject = toNotionObject(data, properties);
await api(`pages/${item._id}`, {
method: "PATCH",
body: JSON.stringify(notionObject)
});
}
return { count: count2 };
};
const destroy5 = async (options) => {
const { where } = options;
const { data } = await list5({ where, exclude: [] });
let count2 = 0;
for await (const item of data) {
count2++;
await api(`pages/${item._id}`, {
method: "PATCH",
body: JSON.stringify({
archived: true
})
});
}
return { count: count2 };
};
return {
list: list5,
find: find5,
create: create5,
update: update5,
destroy: destroy5,
findProperties
};
});
// src/providers/vault/config.ts
var schema9 = (dirname2, path4 = createPathNode()) => v2.object({
format: v2.optional(v2.string(), "markdown"),
path: v2.extras.path(dirname2, path4),
id_strategy: v2.optional(
v2.object({
name: v2.string(),
options: v2.optional(v2.any())
}),
{ name: "incremental" }
)
});
var schema10 = v2.object({
value: v2.union([v2.string(), v2.extras.uint8()]),
salt: v2.optional(v2.string(), crypto2.randomBytes(16).toString("hex")),
iv: v2.optional(v2.string(), crypto2.randomBytes(16).toString("hex")),
password: v2.string()
});
function encrypt(payload) {
const options = validate(schema10, payload);
const salt = Buffer.from(options.salt, "hex");
const iv = Buffer.from(options.iv, "hex");
const key = crypto2.scryptSync(options.password, salt, 32);
const cipher = crypto2.createCipheriv("aes-256-ctr", key, iv);
if (options.value instanceof Uint8Array) {
const buffer = Buffer.concat([cipher.update(options.value), cipher.final()]);
return new Uint8Array(buffer);
}
if (typeof options.value === "string") {
const encrypted = cipher.update(options.value, "utf8", "hex");
return encrypted + cipher.final("hex");
}
throw new Error("Invalid type");
}
function decrypt(payload) {
const options = validate(schema10, payload);
const salt = Buffer.from(options.salt, "hex");
const iv = Buffer.from(options.iv, "hex");
const key = crypto2.scryptSync(options.password, salt, 32);
const decipher = crypto2.createDecipheriv("aes-256-ctr", key, iv);
if (typeof options.value === "string") {
let decrypted = decipher.update(options.value, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
if (options.value instanceof Uint8Array) {
const buffer = Buffer.concat([decipher.update(options.value), decipher.final()]);
return new Uint8Array(buffer);
}
throw new Error("Invalid type");
}
function createEncryption(payload) {
const state = {
salt: (payload == null ? void 0 : payload.salt) || crypto2.randomBytes(16).toString("hex"),
iv: (payload == null ? void 0 : payload.iv) || crypto2.randomBytes(16).toString("hex"),
password: (payload == null ? void 0 : payload.password) || ""
};
const instance = {
state,
setSalt: function(salt) {
state.salt = salt;
return instance;
},
setIv: function(iv) {
state.iv = iv;
return instance;
},
setPassword: function(password2) {
state.password = password2;
return instance;
},
encrypt: function(value) {
return encrypt({ ...state, value });
},
decrypt: function(value) {
return decrypt({ ...state, value });
}
};
return instance;
}
function findMetadata(options) {
const { filesystem: filesystem2, root, id } = options;
const resolve3 = (...args) => filesystem2.path.resolve(root, ...args);
const filepath = resolve3(id, ".db", "metadata.json");
const json2 = filesystem2.readSync.json(filepath, {
default: {
salt: crypto2.randomBytes(16).toString("hex"),
iv: crypto2.randomBytes(16).toString("hex"),
files: []
}
});
const all = filesystem2.readdirSync(resolve3(id));
const files = [];