npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ai-relational-memory

v0.2.0

Published

Механика реляционной памяти ИИ-сотрудника: доступ к PostgreSQL драйвером JavaScript, миграции с учётом применённого и принятием живой базы — CLI и программный API; внешний клиент psql не нужен

Readme

ai-relational-memory

Механика реляционной памяти ИИ-сотрудника: доступ к PostgreSQL и миграции с учётом применённого. Пакет намеренно ничего не знает о предметной области — ни про людей, ни про проекты, ни про журнал. Схема данных, файлы миграций, сид и команды в терминах домена живут у потребителя; здесь — то, на чём они стоят: доступ к базе, две оси потолка, классификация отказов, порядок миграций, контрольные суммы, транзакции, принятие уже живой базы.

Node.js ≥ 18. Единственная зависимость — драйвер pg; он приезжает вместе с пакетом, и внешний клиент psql не нужен. До версии 0.2.0 было наоборот: пакет не тянул за собой ничего, но требовал установленного postgresql-client. Требование оказалось непроходимым там, где нет системных прав, — а это ровно те машины, на которых работают ИИ-сотрудники в песочнице.

npm install -g ai-relational-memory        # или npx ai-relational-memory --help
export RELMEM_DATABASE_URL=postgres://…    # строка подключения — из окружения; файлов с секретами пакет не читает
export RELMEM_MIGRATIONS_DIR=./migrations  # каталог ВАШИХ файлов миграций
relmem ping
relmem status
relmem apply --dry-run

Настройка

Приоритет: явные параметры программного вызова → переменные окружения → умолчания. Значение строки подключения пакет не печатает никогда — ни в выводе, ни в текстах отказа: если оно попало в текст ошибки сервера, оно оттуда вырезается. В аргументы дочернего процесса строка подключения тоже не попадает (уходит стандартным вводом) — значит, не видна и в списке процессов машины.

| Параметр | Переменная | Умолчание | Что это | |---|---|---|---| | url | RELMEM_DATABASE_URL | — | строка подключения | | timeoutSec | RELMEM_TIMEOUT | 15 | потолок всего вызова: соединение плюс запрос | | connectTimeoutSec | RELMEM_CONNECT_TIMEOUT | 5 | отдельный потолок установления соединения | | maxBufferMb | RELMEM_MAX_BUFFER_MB | 64 | потолок РАЗМЕРА ответа | | timeZone | RELMEM_TZ | пояс машины | пояс сеанса (PGTZ): от него зависит, как рендерится timestamptz | | dir | RELMEM_MIGRATIONS_DIR | — | каталог файлов миграций | | table | RELMEM_TABLE | schema_migration | таблица учёта применённого | | appliedBy | RELMEM_APPLIED_BY | — | подпись автора применения; умолчания у механизма нет |

Потолок времени и потолок размера — разные оси, и путать их нельзя. Node гасит обращение одним и тем же сигналом в обоих случаях, поэтому «ответ не влез» разбирается по коду ошибки, а не по сигналу: иначе переполнение буфера выдаётся за недоступность базы, и чинят не то.

Поверхность пакета синхронна: query возвращает результат, а не обещание. Драйвер асинхронен, поэтому обращение уходит в короткий дочерний процесс, которого вызывающий ждёт синхронно. Это осознанный выбор, а не недосмотр: синхронного драйвера PostgreSQL на чистом JavaScript не бывает (сокеты в Node асинхронны по устройству), а асинхронная поверхность потребовала бы переписать всех потребителей. Процесс на запрос здесь был и раньше — им был сам psql.

Команды

relmem ping                            # связь с базой: печатает версию сервера
relmem status [--json]                 # применено, к применению, принято, bootstrap, дрейф
relmem apply [--dry-run]               # применить недостающие по порядку
relmem adopt [--through 0014_x.sql]    # принять применённое в учёт, НИ ОДНОГО файла не выполняя
relmem verify                          # дрейф, пропавшие файлы, нарушенный порядок
relmem query --sql 'SELECT 1' [--json|--tuples] [--var имя=значение]

Флаги, снимающие защиту, — каждый по своему поводу и только явным вызовом: --allow-nonempty (непустая схема без учёта), --allow-drift (применённый файл изменился), --allow-out-of-order (файл с номером ниже максимального применённого).

Коды возврата: 0 выполнено · 1 отказ механизма · 2 база недоступна · 3 ошибка вызова. Разделены они не для красоты: «база не отвечает» и «миграции разъехались» лечатся разными людьми.

Механизм миграций

Файлы берутся из вашего каталога по маске NNNN_*.sql и сортируются по числовому префиксу, затем по имени. Всё остальное в каталоге (README, сид, что угодно) механизм не замечает.

Применяется то, чего нет в таблице учёта, — повторный запуск не выполняет ничего. Это идемпотентность механизма; идемпотентность содержимого файла (IF NOT EXISTS) остаётся вашей заботой и от механизма не требуется: применённое второй раз не запускается.

Каждый файл — в своей транзакции вместе с записью в таблицу учёта, поэтому применённое и отметка о применении не могут разъехаться. Разбор останавливается на первом же отказе: операторы уходят на сервер по одному, и недосланный COMMIT означает откат — иначе прогон продолжался бы после ошибки и отмечал успех на половине файла.

Файл с номером ниже максимального применённого (появился в ветке, пока в основной уже ушёл более поздний) — отказ, а не тихое применение вне порядка. Порядок и есть то, ради чего механизм существует; снимается флагом.

Таблица учёта

| Колонка | Зачем | |---|---| | name (PK) | имя файла целиком — сортировка и идентичность в одном значении | | checksum | sha256 содержимого: правка применённого файла обязана быть видна | | applied_at | когда | | applied_by | кто (значение даёте вы) | | duration_ms | сколько шло — на живой базе это единственный след стоимости | | adopted | принято, а не выполнено: строка появилась из adopt, файл не запускался | | bootstrap | файл помечен как выполняемый вне механизма |

adopted — не украшение: без него журнал утверждает, что механизм выполнил файлы, которых он не выполнял, и через полгода по такому журналу нельзя понять, проверялась ли миграция вообще.

Директивы файла

Читаются из шапки — строк-комментариев до первой строки с кодом; форма -- relmem: <директива>. Директива живёт рядом с файлом, а не в отдельном манифесте: разъехаться нечему. Неизвестная директива — отказ, а не тихое игнорирование (опечатка в защите хуже отсутствия защиты).

  • bootstrap — механизм не выполняет файл никогда. Это про создание роли и базы: другая база, суперпользователь, разовый ручной запуск. Учитывается отдельно, чтобы status не показывал такой файл вечно недостающим.
  • no-transaction — файл выполняется вне транзакции (CREATE DATABASE, CREATE INDEX CONCURRENTLY, ALTER TYPE … ADD VALUE в транзакции жить не могут). Отметка о применении тогда ставится отдельным запросом после успеха, и это более слабая гарантия: обрыв ровно между выполнением и отметкой оставит файл выполненным, но неучтённым.

Непустая база без учёта не мигрируется

Инвариант, ради которого механизм и написан:

Таблицы учёта нет, а в схеме уже есть отношения — применять нельзя.

apply в этом состоянии отказывается с ненулевым кодом и называет команду принятия. Переустановить схему поверх живых данных перестаёт быть возможным по построению, а не по внимательности того, кто набирает команду. Если база и правда должна заполняться с нуля — это говорится явным флагом --allow-nonempty.

Принятие живой базы

relmem adopt --through 0014_reviewer_condition.sql
relmem status

adopt записывает файлы в таблицу учёта как применённые, не выполняя ни одного, и помечает строки признаком adopted. Границу задаёт --through <файл> (включительно); без него принимаются все найденные. Кроме создания самой таблицы учёта никаких изменений схемы и данных не происходит, уже учтённые строки не переписываются, а повторный запуск — пустая операция.

Это штатный способ перевести под управление механизмом базу, к которой миграции применялись руками.

Программный API

CLI и API — одна и та же механика; API нужен обёртке, у которой поверх механики стоит предметная область.

import { createDb, createMigrator } from 'ai-relational-memory';

const db = createDb({
    url: process.env.MY_DATABASE_URL,
    timeoutSec: 15,
    // Имена, которыми настройки называются в текстах отказа: человек должен увидеть СВОЮ переменную.
    labels: { url: 'MY_DATABASE_URL' },
    toolName: 'my-memory',
});

const rows = db.queryJson('SELECT name FROM person WHERE name ILIKE :\'q\'', { q: '%иван%' }, { soft: true });

const migrator = createMigrator({ db, dir: './migrations', appliedBy: 'my-memory' });
console.log(migrator.status());

Библиотека не завершает процесс. Отказ — это исключение с полем класса (DbError.kind: no-url, no-driver, timeout, too-big, sql, bad-json; MigrateError.kind: not-empty, drift, out-of-order, no-dir, bad-table, bad-directive, verify), а решение «упасть или деградировать» принимает вызывающий: у него для этого больше сведений. Для второго варианта есть мягкий режим { soft: true } — вместо исключения null и названная причина в onSoftError.

Самопроверка

node selftest.mjs                                   # герметичная половина: базы не нужно
RELMEM_SELFTEST_DATABASE_URL=postgres://… node selftest.mjs   # плюс живая половина

Живая половина работает на своей временной схеме, создаёт и удаляет её сама и чужой схемы не касается. Без переменной она пропускается с названной причиной, а не молча.

Лицензия

MIT.