Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи? Написать
Дайджесты новостей
Иллюстрация к статье: модульная архитектура адаптеров баз данных на TypeScript с динамической изоляцией драйверов и песочницей запросов

Архитектура мультибазового SPI в TypeScript: опыт проектирования платформы LibreDB Studio

В зрелых средах вроде Java или платформы .NET работа с базами данных опирается на устоявшиеся стандарты. Спецификации JDBC и ADO.NET десятилетиями задают строгий контракт: вендор СУБД поставляет драйвер, реализующий базовый набор интерфейсов для соединений, транзакций и метаданных. Разработчику десктопного клиента или аналитической панели достаточно использовать общие абстракции, не вникая в сетевые протоколы конкретной базы.

В экосистеме JavaScript и TypeScript системного стандарта драйверов уровня языка нет. Среда npm наполнена независимыми библиотеками: для PostgreSQL популярен pg, для MySQL используется mysql2, базы SQLite работают через бинарные биндинги better-sqlite3 или встроенные модули, а в NoSQL доминируют ioredis и MongoDB. Каждая библиотека использует собственные соглашения, форматы конфигураций, структуры ошибок и модели пула соединений.

Создание универсальной платформы LibreDB Studio, поддерживающей более 15 СУБД, потребовало проектирования собственного интерфейса поставщика услуг — Service Provider Interface (SPI). Это архитектурный контракт для сменных модулей, позволяющий приложению единообразно взаимодействовать с реляционными, документоориентированными, колоночными и встраиваемыми хранилищами.

Проблема экосистемы: почему в TypeScript нет своего JDBC

Отсутствие единого стандарта ставит перед архитекторами три ключевые проблемы:

  1. Несовместимость протоколов и моделей. Реляционная база оперирует каталогами, схемами и внешними ключами. Redis работает с плоским пространством ключей, а MongoDB — с коллекциями документов. Единый слой абстракции не должен разрушать специфику движков.
  2. Фрагментация ошибок. Нарушение уникальности в PostgreSQL возвращает строковый код ошибки протокола, в MySQL — числовой код, а в SQLite — текстовое сообщение. Пользовательский интерфейс не должен содержать сотен проверок под каждый драйвер.
  3. Раздувание памяти. При монолитном импорте драйверов всех СУБД с бинарными C++ аддонами объем резидентной памяти процесса — Resident Set Size (RSS) — превышает 250 мегабайт еще до настройки первого подключения.

Динамический импорт: борьба с раздуванием памяти

Ключевым решением LibreDB Studio стал отказ от статической загрузки модулей. Ядро системы использует паттерн фабрики с асинхронной подгрузкой через await import().

Архитектура слоя доступа опирается на базовый класс BaseDatabaseProvider. Он декларирует методы управления жизненным циклом (connect, disconnect, ping), выполнения запросов и сбора телеметрии. Адаптеры для каждой базы наследуют этот контракт и оборачивают нативные npm-драйверы.

Когда пользователь настраивает подключение к MySQL, фабрика инициирует загрузку модуля mysql2 ровно в момент соединения. Если библиотека отсутствует в окружении, приложение выводит понятное уведомление с предложением установить драйвер. В итоге процесс платформы стартует с минимальным объемом памяти около 45–50 МБ, подгружая бинарные модули только по необходимости.

Нормализация метаданных через Object Surface API

Второй вызов — построение дерева объектов в интерфейсе. Пользователю нужен доступ к сущностям в едином боковом меню, но внутренняя структура каталогов у разных СУБД не совпадает.

Концепция Object Surface API нормализует системные метаданные в единую древовидную структуру ObjectDetail:

  • В PostgreSQL и MySQL адаптер опрашивает представления information_schema, преобразуя трехуровневую иерархию (база — схема — таблица) в стандартные пути сущностей.
  • В SQLite адаптер выполняет команды PRAGMA (table_info, foreign_key_list), формируя виртуальную схему main со списком таблиц и индексов.
  • В Redis провайдер сканирует пространство ключей по двоеточиям, синтезируя виртуальные папки для группировки записей.

Нормализованный объект содержит путь, список колонок с типами данных, ключи и индексы. Интерфейс вызывает методы listContainers, listObjects и describeObject, оставаясь изолированным от диалектов СУБД. При этом специфические типы данных и параметры TTL ключей не скрываются.

Сравнение платформенных моделей драйверов

Различие подходов между классическими средами и TypeScript наглядно отражает таблица:

Архитектурный аспектСпецификации JDBC и ADO.NETМодель SPI в LibreDB Studio (TypeScript)
Определение контрактаПлатформенный стандарт (Java JCP, Microsoft)Внутренний интерфейс (DatabaseProvider)
Поставка драйвераОфициальный пакет от вендора СУБДНезависимый npm-пакет, обернутый адаптером
Момент загрузки кодаСтатическая загрузка в рантаймОтложенная загрузка через dynamic import()
Интроспекция схемыСтандартный интерфейс DatabaseMetaDataСлой нормализации ObjectSurfaceAPI
Изоляция ИИ-агентовДелегируется системным правам пользователяРаздельные пулы сессий и нативный read-only

В экосистеме TypeScript ответственность за версионирование и тестирование контракта ложится на авторов прикладной системы.

Безопасность и изоляция ИИ-агентов: эшелонированная защита

Интеграция автономных ИИ-агентов для генерации запросов и анализа производительности создает риски случайной модификации данных. Ограничение модели только инструкцией в системном промпте не гарантирует безопасности. В LibreDB Studio реализована эшелонированная защита (defence in depth).

Агент никогда не получает соединение из общего пула интерактивного пользователя — для него выделяется отдельный профиль agent-read-only. Перед отправкой запрос разбирается в абстрактное синтаксическое дерево — Abstract Syntax Tree (AST). Если парсер находит узлы модификации данных (DML) или изменения схемы (DDL), запрос блокируется до отправки в сеть.

Провайдер также задействует нативные механизмы СУБД:

  • В сессиях PostgreSQL транзакция открывается в режиме READ ONLY под непривилегированной учетной записью.
  • В SQLite соединение конфигурируется директивой PRAGMA query_only = true, а значение прагмы повторно считывается перед выполнением каждого выражения.
  • На соединение накладывается таймаут выполнения (до 5 секунд) и лимит выборки (до 1000 строк).

Пароли и токены автоматически маскируются в строках подключения перед записью в системные логи или телеметрию.

Специфика встроенных баз и сетевых туннелей: SQLite и SSH

При работе с локальными файлами SQLite действует блокировка: пишущий процесс блокирует файл для остальных. Если пользователь редактирует запись, фоновая интроспекция схемы может вызвать ошибку блокировки. Фабрика провайдера находит путь к файлу и для чтения переиспользует уже открытый дескриптор в режиме read-only, выстраивая запись в очередь.

Для подключения к базам в закрытых контурах слой провайдера управляет туннелями SSH. Модуль на базе ssh2 поднимает защищенный канал к бастион-серверу и пробрасывает локальный порт на удаленный хост. Провайдер подключается к локальной точке входа, не зная о топологии сети. При разрыве сессии сетевой туннель корректно освобождает ресурсы вместе с пулом соединений.

Чеклист устойчивости при добавлении нового движка

Регламент регрессионного аудита нового провайдера включает следующие шаги:

  1. Изоляция сборки. Убедиться, что драйвер не попадает в стартовый бандл и его отсутствие не мешает запуску платформы.
  2. Очистка ресурсов. Проверить, что при обрыве связи или отмене запроса закрываются сокеты, освобождаются пулы, блокировки и туннели SSH.
  3. Стабильность метаданных. Проконтролировать, что нормализатор схем формирует детерминированные пути объектов при переключении между диалектами.
  4. Маскирование секретов. Проверить логи и интерфейс на отсутствие открытых паролей в строках подключения.
  5. Проверка read-only режима. Удостовериться, что в агентском профиле запись блокируется на уровне прав СУБД, а не только текстовым анализом запроса.

Архитектурный баланс: универсальность против возможностей движка

Опыт LibreDB Studio показывает, что универсальность слоя данных не должна приводить к поиску наименьшего общего знаменателя. Попытка свести все хранилища к плоским таблицам лишает разработчика преимуществ векторных индексов, временных рядов или бессхемных документов.

Жизнеспособный подход опирается на два уровня: стабильный контракт базовых операций (сессии, транзакции, интроспекция) дополняется декларативными флагами возможностей (capabilities). Если база поддерживает профилирование медленных запросов, провайдер реализует методы, а интерфейс активирует нужную панель. Грамотно выстроенный SPI позволяет подключать новые СУБД без переписывания интерфейса и потери производительности.