@elcrm/db
v0.1.0
Published
Bun SQL MySQL/Postgres + bun:sqlite for elCRM
Maintainers
Readme
@elcrm/db
Библиотека для работы с MySQL, PostgreSQL и SQLite через Bun. Один и тот же async API (db.query / insert / get / transaction / ensureColumn): MySQL/Postgres — Bun SQL, SQLite — bun:sqlite с bind-параметрами.
Для поэтапной миграции с «голого» bun:sqlite есть sync escape hatch: getSqlite().
📖 Полная техническая документация API доступна в API.md
Установка
npm install @elcrm/db
# или
bun add @elcrm/dbНастройка
Библиотека автоматически использует переменные окружения для подключения к базе данных. Вы можете настроить их следующими способами:
Переменные окружения
Библиотека автоматически определяет тип базы данных (MySQL или PostgreSQL) на основе переменных окружения или конфигурации.
Для MySQL:
# Вариант 1: Стандартные переменные
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306
DB_BASE=name_base
DB_USER=root
DB_PASS=password
# Вариант 2: MYSQL_ префикс
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=name_base
MYSQL_USER=root
MYSQL_PASSWORD=passwordДля PostgreSQL:
# Вариант 1: Стандартные переменные
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_BASE=name_base
DB_USER=postgres
DB_PASS=password
# Вариант 2: POSTGRES_ или PG_ префикс
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DATABASE=name_base
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
# Или с PG_ префиксом
PG_HOST=localhost
PG_PORT=5432
PG_DATABASE=name_base
PG_USER=postgres
PG_PASSWORD=passwordДля SQLite:
DB_TYPE=sqlite
DB_BASE=/path/to/data.db
# или
SQLITE_PATH=/path/to/data.dbimport { db, initDB, getSqlite } from "@elcrm/db";
await initDB({ type: "sqlite", database: "./data.db" });
// или :memory: / sqlite:///abs/path.db / file:./data.db
await db.ensureColumn("users", "avatar", "TEXT NOT NULL DEFAULT ''");
const raw = getSqlite(); // bun:sqlite DatabaseПримечание: Если DB_TYPE не указан, по умолчанию используется MySQL.
Использование
Базовое подключение
import { db } from "@elcrm/db";
// Проверка подключения
const isConnected = await db.ping();
if (!isConnected) {
console.error("Не удалось подключиться к базе данных");
}API
Основные методы
query<T>(sql: string, params?: any[]): Promise<T[]>
Выполняет произвольный SQL-запрос с параметрами.
// Простой запрос
const users = await db.query("SELECT * FROM users");
// С параметрами
const user = await db.query<User>(
"SELECT * FROM users WHERE id = ? AND status = ?",
[1, "active"]
);
// Типизированный результат
interface User {
id: number;
name: string;
email: string;
}
const typedUsers = await db.query<User>("SELECT * FROM users");insert(table: string, data: RecordData): Promise<InsertResult>
Вставляет новую запись в таблицу.
const result = await db.insert("users", {
name: "Иван",
email: "[email protected]",
age: 25,
});
console.log(result.last_insert_id); // ID вставленной записиselectWhere<T>(table: string, columns: string[], where?: WhereCondition, options?: SelectOptions): Promise<T[]>
Выполняет SELECT-запрос с условиями WHERE.
// Простой запрос
const users = await db.selectWhere("users", ["id", "name", "email"]);
// С условиями WHERE
const activeUsers = await db.selectWhere<User>(
"users",
["id", "name", "email"],
{ status: "active", age: 25 }
);
// С опциями сортировки и лимита
const topUsers = await db.selectWhere<User>(
"users",
["id", "name", "email"],
{ status: "active" },
{ orderBy: "created_at DESC", limit: 10 }
);update(table: string, data: RecordData, where: WhereCondition): Promise<QueryResult[]>
Обновляет записи в таблице.
// Обновление одной записи
await db.update(
"users",
{ name: "Новое имя", email: "[email protected]" },
{ id: 1 }
);
// Обновление нескольких записей
await db.update(
"users",
{ status: "inactive" },
{ age: 18, status: "pending" }
);select<T>(table: string, columns: string[], whereClause?: string): Promise<T[]>
Выполняет SELECT-запрос с произвольным WHERE условием.
// Без условий
const allUsers = await db.select("users", ["id", "name"]);
// С произвольным WHERE
const users = await db.select<User>(
"users",
["id", "name", "email"],
"age > 18 AND status = 'active'"
);find<T>(table: string, column: string, value: any): Promise<T[]>
Находит записи по значению столбца.
// Поиск по ID
const user = await db.find<User>("users", "id", 1);
// Поиск по email
const users = await db.find<User>("users", "email", "[email protected]");exists(table: string, column: string, value: any): Promise<{ exists: number }[]>
Проверяет существование записи.
const result = await db.exists("users", "email", "[email protected]");
if (result[0].exists === 1) {
console.log("Пользователь существует");
}get<T>(sql: string, params?: any[]): Promise<T | null>
Получает одну запись из результата запроса.
const user = await db.get<User>("SELECT * FROM users WHERE id = ?", [1]);
if (user) {
console.log(user.name);
}run(sql: string, params?: any[]): Promise<RunResult>
Выполняет SQL-запрос и возвращает метаданные.
const result = await db.run("UPDATE users SET status = ? WHERE id = ?", [
"active",
1,
]);
console.log(result.affectedRows); // Количество затронутых строк
console.log(result.insertId); // ID вставки (если был INSERT)lastInsertId(): Promise<InsertResult[]>
Получает последний вставленный ID.
const result = await db.lastInsertId();
console.log(result[0].last_insert_id);Транзакции
transaction<T>(callback: (db: Database) => Promise<T>): Promise<T>
Выполняет операции в транзакции.
try {
const result = await db.transaction(async (db) => {
// Все операции выполняются в одной транзакции
await db.insert("users", { name: "Иван", email: "[email protected]" });
await db.insert("profiles", { user_id: 1, bio: "Описание" });
return { success: true };
});
console.log("Транзакция выполнена успешно");
} catch (error) {
console.error("Ошибка транзакции:", error);
// Автоматический ROLLBACK выполнен
}Утилиты
ping(): Promise<boolean>
Проверяет подключение к базе данных.
const isConnected = await db.ping();
if (isConnected) {
console.log("База данных доступна");
}getTableStructure(table: string): Promise<TableColumnInfo[]>
Получает структуру таблицы из базы данных.
const structure = await db.getTableStructure("users");
structure.forEach((column) => {
console.log(`${column.Field}: ${column.Type}`);
});checkTableStructure(table: string, typeStructure: TypeStructure): Promise<StructureCheckResult>
Проверяет соответствие структуры таблицы TypeScript типу.
import { db, createTypeStructure } from "@elcrm/db";
const structure = createTypeStructure({
id: "number",
name: "string",
email: "string",
});
const result = await db.checkTableStructure("users", structure);
if (result.isValid) {
console.log("✅ Структура соответствует!");
} else {
console.log("❌ Обнаружены несоответствия:", result);
}createTableFromType(table: string, typeStructure: TypeStructure, options?: CreateTableOptions): Promise<QueryResult[]>
Создает таблицу в базе данных на основе TypeScript типа.
import { db, createTypeStructure } from "@elcrm/db";
const structure = createTypeStructure({
id: "number",
name: "string",
email: "string",
});
await db.createTableFromType("users", structure, {
timestamps: true,
});Date(date?: string | number): string
Форматирует дату для базы данных (MySQL/PostgreSQL).
// Текущая дата
const now = db.Date(); // "2024-01-15 12:30:45"
// Конкретная дата
const date = db.Date("2024-01-15T12:30:45Z"); // "2024-01-15 12:30:45"type: DatabaseType
Получает тип подключенной базы данных.
console.log(db.type); // "mysql" или "postgres"parserCell
Утилиты для работы с ячейками специального формата.
toBase(arr: number[]): string
Преобразует массив чисел в строку формата .1.2.3.
const str = db.parserCell.toBase([1, 2, 3, 4, 5]);
console.log(str); // ".1.2.3.4.5."toArray(str: string): number[]
Преобразует строку формата .1.2.3. в массив чисел.
const arr = db.parserCell.toArray(".1.2.3.4.5.");
console.log(arr); // [1, 2, 3, 4, 5]Создание таблиц из SQL файлов
Библиотека предоставляет удобные функции для создания таблиц из SQL файлов с автоматическим преобразованием MySQL синтаксиса в PostgreSQL при необходимости.
ensureTableFromSql(tableName: string, sqlFilePath: string, onStatus?: (status: string) => void): Promise<void>
Создает таблицу из SQL файла, если её нет. Автоматически определяет тип БД и преобразует MySQL SQL в PostgreSQL синтаксис при необходимости.
import { ensureTableFromSql } from "@elcrm/db";
import { join } from "path";
// Создание таблицы из SQL файла
await ensureTableFromSql(
"users",
join(process.cwd(), "db", "users.sql"),
(status) => console.log(status) // Опциональный колбэк для статуса
);Особенности:
- Автоматически проверяет существование таблицы перед созданием
- Преобразует MySQL синтаксис в PostgreSQL при необходимости
- Поддерживает колбэк для отслеживания статуса выполнения
- Удаляет
DROP TABLE IF EXISTSиз SQL файла (используется проверка существования)
createTableSql(name: string, onStatus?: (status: string) => void): Promise<void>
Простейший фасад для создания таблицы из файла в папке db. Ищет файл db/<name>.sql и создает таблицу с именем name.
import { createTableSql } from "@elcrm/db";
// Создает таблицу users из файла db/users.sql
await createTableSql("users", (status) => console.log(status));Пример структуры проекта:
project/
├── db/
│ ├── users.sql
│ ├── products.sql
│ └── orders.sql
└── src/
└── index.tsПример SQL файла (MySQL синтаксис):
CREATE TABLE IF NOT EXISTS users (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Библиотека автоматически преобразует этот SQL в PostgreSQL синтаксис при работе с PostgreSQL:
CREATE TABLE IF NOT EXISTS public.users (
id SERIAL NOT NULL,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);Преобразования MySQL → PostgreSQL:
INT(11) AUTO_INCREMENT→SERIALTINYINT(1)→BOOLEANDATETIME→TIMESTAMP- Обратные кавычки
`→ двойные кавычки"или удаляются ENGINE,CHARSET,COLLATE→ удаляютсяON UPDATE CURRENT_TIMESTAMP→ удаляется (PostgreSQL использует триггеры)UNIQUE KEY→UNIQUEKEY(индексы) → удаляются (создаются отдельно в PostgreSQL)
Типы
Интерфейсы
// Конфигурация подключения
interface DBConfig {
type?: DatabaseType; // "mysql" или "postgres" (по умолчанию "mysql")
host?: string;
port?: number;
database?: string;
user?: string;
password?: string;
}
// Тип базы данных
type DatabaseType = "mysql" | "postgres";
// Результат запроса
interface QueryResult {
[key: string]: any;
}
// Результат вставки
interface InsertResult {
last_insert_id: number;
}
// Результат выполнения
interface RunResult {
affectedRows: number;
insertId: number | null;
}
// Опции для SELECT
interface SelectOptions {
orderBy?: string;
limit?: number;
}
// Данные для вставки/обновления
type RecordData = Record<string, any>;
// Условия WHERE
type WhereCondition = Record<string, any>;
// Информация о колонке таблицы
interface TableColumnInfo {
Field: string;
Type: string;
Null: "YES" | "NO";
Key: string;
Default: string | null;
Extra: string;
}
// Структура типа для проверки
type TypeStructure = Record<string, string | string[]>;
// Результат проверки структуры таблицы
interface StructureCheckResult {
isValid: boolean;
missingInDB: string[]; // Поля, которые есть в типе, но отсутствуют в БД
missingInType: string[]; // Поля, которые есть в БД, но отсутствуют в типе
typeMismatches: Array<{
field: string;
dbType: string;
expectedType: string | string[];
}>;
}
// Опции для создания таблицы
interface CreateTableOptions {
primaryKey?: string; // Название поля для первичного ключа (по умолчанию "id")
engine?: string; // Движок таблицы (по умолчанию "InnoDB")
charset?: string; // Кодировка (по умолчанию "utf8mb4")
collate?: string; // Сравнение (по умолчанию "utf8mb4_unicode_ci")
ifNotExists?: boolean; // Использовать IF NOT EXISTS (по умолчанию true)
timestamps?: boolean; // Автоматически добавлять created_at и updated_at (по умолчанию false)
}
// Описание колонки для создания таблицы
interface ColumnDefinition {
name: string;
type: string | string[]; // TypeScript тип
nullable?: boolean; // Может быть null
defaultValue?: any; // Значение по умолчанию
autoIncrement?: boolean; // Автоинкремент
primaryKey?: boolean; // Первичный ключ
unique?: boolean; // Уникальное значение
length?: number; // Длина для строковых типов
}Примеры использования
Поддержка MySQL и PostgreSQL
Библиотека автоматически определяет тип базы данных и адаптирует запросы:
import { db } from "@elcrm/db";
// Автоматическое определение типа БД
console.log(db.type); // "mysql" или "postgres"
// Все методы работают одинаково для обеих БД
const users = await db.query("SELECT * FROM users");
// INSERT автоматически использует правильный синтаксис
// MySQL: SELECT LAST_INSERT_ID()
// PostgreSQL: RETURNING id
const result = await db.insert("users", {
name: "Иван",
email: "[email protected]",
});Создание пользователя
import { db } from "@elcrm/db";
interface User {
id: number;
name: string;
email: string;
created_at: string;
}
// Создание
const result = await db.insert("users", {
name: "Иван Иванов",
email: "[email protected]",
});
const userId = result.last_insert_id;
// Получение созданного пользователя
const user = await db.get<User>("SELECT * FROM users WHERE id = ?", [userId]);Поиск и фильтрация
// Поиск активных пользователей старше 18 лет
const activeUsers = await db.selectWhere<User>(
"users",
["id", "name", "email"],
{ status: "active", age: 18 },
{ orderBy: "created_at DESC", limit: 20 }
);
// Проверка существования
const exists = await db.exists("users", "email", "[email protected]");
if (exists[0].exists === 1) {
console.log("Пользователь уже существует");
}Обновление данных
// Обновление профиля пользователя
await db.update(
"users",
{
name: "Новое имя",
updated_at: db.Date(),
},
{ id: 1 }
);Работа с транзакциями
// Создание пользователя с профилем в транзакции
try {
const result = await db.transaction(async (db) => {
// Создаем пользователя
const userResult = await db.insert("users", {
name: "Иван",
email: "[email protected]",
});
// Создаем профиль
await db.insert("profiles", {
user_id: userResult.last_insert_id,
bio: "Описание профиля",
});
return userResult.last_insert_id;
});
console.log(`Пользователь создан с ID: ${result}`);
} catch (error) {
console.error("Ошибка при создании пользователя:", error);
}Использование с TypeScript
import { db, QueryResult } from "@elcrm/db";
// Определяем интерфейс для типизации
interface Product {
id: number;
name: string;
price: number;
category_id: number;
}
// Типизированные запросы
const products = await db.selectWhere<Product>(
"products",
["id", "name", "price"],
{ category_id: 1 },
{ orderBy: "price ASC", limit: 10 }
);
// products имеет тип Product[]
products.forEach((product) => {
console.log(product.name, product.price);
});Создание таблиц из SQL файлов
import { createTableSql, ensureTableFromSql } from "@elcrm/db";
// Простой способ - из папки db/
await createTableSql("users", (status) => {
console.log(status); // Логирование статуса
});
// Продвинутый способ - с указанием полного пути
await ensureTableFromSql("products", "/path/to/products.sql", (status) =>
console.log(status)
);Проверка соответствия структуры таблицы TypeScript типу
Библиотека предоставляет возможность проверить, соответствует ли структура таблицы в базе данных вашему TypeScript типу.
import { db, createTypeStructure } from "@elcrm/db";
// Ваш TypeScript тип
export type TUsers = {
id: number;
tid: number;
username: string;
version: string;
online: Date | string;
lang: string;
first_name: string;
last_name: string;
premium: number;
notice: number;
agent: string;
avatar: string;
created_at: Date | string;
birthday: Date | string;
sex: number;
country_id: number;
region_id: number;
city_id: number;
about: string;
active: number;
maths: number;
subscription: number;
search: number;
updated_at: Date | string;
agreement: string;
phone: string;
age: number;
photos: string;
current_id: number;
height: number;
alcohol: number;
smoke: number;
children: number;
education: number;
zodiac: number;
interests: number;
work: number;
pets: number;
ip: string;
};
// Создаем структуру для проверки
const usersStructure = createTypeStructure<TUsers>({
id: "number",
tid: "number",
username: "string",
version: "string",
online: ["Date", "string"],
lang: "string",
first_name: "string",
last_name: "string",
premium: "number",
notice: "number",
agent: "string",
avatar: "string",
created_at: ["Date", "string"],
birthday: ["Date", "string"],
sex: "number",
country_id: "number",
region_id: "number",
city_id: "number",
about: "string",
active: "number",
maths: "number",
subscription: "number",
search: "number",
updated_at: ["Date", "string"],
agreement: "string",
phone: "string",
age: "number",
photos: "string",
current_id: "number",
height: "number",
alcohol: "number",
smoke: "number",
children: "number",
education: "number",
zodiac: "number",
interests: "number",
work: "number",
pets: "number",
ip: "string",
});
// Проверяем соответствие
const result = await db.checkTableStructure("users", usersStructure);
if (result.isValid) {
console.log("✅ Структура таблицы соответствует типу!");
} else {
console.log("❌ Обнаружены несоответствия:");
if (result.missingInDB.length > 0) {
console.log("Поля, отсутствующие в БД:", result.missingInDB);
}
if (result.missingInType.length > 0) {
console.log("Поля, отсутствующие в типе:", result.missingInType);
}
if (result.typeMismatches.length > 0) {
console.log("Несоответствия типов:");
result.typeMismatches.forEach((mismatch) => {
console.log(
` - ${mismatch.field}: БД имеет тип "${mismatch.dbType}", ожидается "${mismatch.expectedType}"`
);
});
}
}Получение структуры таблицы
Вы также можете получить структуру таблицы напрямую:
import { db, TableColumnInfo } from "@elcrm/db";
const structure = await db.getTableStructure("users");
structure.forEach((column) => {
console.log(
`${column.Field}: ${column.Type} (${
column.Null === "YES" ? "nullable" : "not null"
})`
);
});Создание таблицы из TypeScript типа
Библиотека может автоматически создавать таблицы в базе данных на основе вашего TypeScript типа:
import { db, createTypeStructure, CreateTableOptions } from "@elcrm/db";
// Ваш TypeScript тип
export type TUsers = {
id: number;
tid: number;
username: string;
email: string;
first_name: string;
last_name: string;
age: number;
active: number;
created_at: Date | string;
updated_at: Date | string;
};
// Создаем структуру для таблицы
const usersStructure = createTypeStructure<TUsers>({
id: "number",
tid: "number",
username: "string",
email: "string",
first_name: "string",
last_name: "string",
age: "number",
active: "number",
created_at: ["Date", "string"],
updated_at: ["Date", "string"],
});
// Опции создания таблицы
const options: CreateTableOptions = {
primaryKey: "id", // Поле первичного ключа (по умолчанию "id")
engine: "InnoDB", // Движок таблицы (по умолчанию "InnoDB")
charset: "utf8mb4", // Кодировка (по умолчанию "utf8mb4")
collate: "utf8mb4_unicode_ci", // Сравнение (по умолчанию "utf8mb4_unicode_ci")
ifNotExists: true, // Использовать IF NOT EXISTS (по умолчанию true)
timestamps: false, // Автоматически добавлять created_at и updated_at (по умолчанию false)
};
// Создаем таблицу
await db.createTableFromType("users", usersStructure, options);
console.log("✅ Таблица users создана успешно!");Особенности автоматического создания:
- Первичный ключ: Поле с названием, указанным в
primaryKey(по умолчанию "id"), автоматически становится первичным ключом с AUTO_INCREMENT - Типы данных: Автоматически определяются на основе TypeScript типов:
number→INTstring→VARCHAR(255)илиTEXT(в зависимости от названия поля)Date | string→TIMESTAMPилиDATEboolean→TINYINT(1)
- Умное определение типов: Библиотека анализирует названия полей:
- Поля с
email,url,link→VARCHAR(255) - Поля с
phone,tel→VARCHAR(20) - Поля с
description,content,text,about→TEXT - Поля с
time,at→TIMESTAMP
- Поля с
- Timestamps: Если
timestamps: true, автоматически добавляются поляcreated_atиupdated_atс соответствующими значениями по умолчанию - Nullable: Поля с типом
| nullили| undefinedмогут быть NULL
Пример с автоматическими timestamps:
const simpleStructure = createTypeStructure({
id: "number",
name: "string",
email: "string",
});
// Автоматически добавит created_at и updated_at
await db.createTableFromType("users", simpleStructure, {
timestamps: true,
});Продвинутое использование
Инициализация с конфигурацией
import { initDB } from "@elcrm/db";
// Инициализация с явной конфигурацией
await initDB({
type: "postgres", // или "mysql"
host: "localhost",
port: 5432,
database: "mydb",
user: "postgres",
password: "password",
});Прямой доступ к подключению
import { getConnection } from "@elcrm/db";
const connection = getConnection();
// Используйте connection для прямых операций с SQLОпределение типа базы данных
import { db } from "@elcrm/db";
// Получить тип подключенной БД
console.log(db.type); // "mysql" или "postgres"
// Использовать в условной логике
if (db.type === "postgres") {
// PostgreSQL-специфичная логика
} else {
// MySQL-специфичная логика
}Обработка ошибок
Все методы библиотеки могут выбрасывать исключения. Рекомендуется использовать try-catch:
try {
const users = await db.query("SELECT * FROM users");
} catch (error) {
console.error("Ошибка выполнения запроса:", error);
}Лицензия
MIT
Автор
MaSkal [email protected]
