letopis 0.5.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +171 -0
- package/README.md +2280 -211
- package/dist/acl.d.ts +48 -0
- package/dist/acl.js +208 -0
- package/dist/auth.d.ts +116 -0
- package/dist/auth.js +263 -0
- package/dist/chain.d.ts +51 -26
- package/dist/chain.js +143 -74
- package/dist/index.d.ts +9 -3
- package/dist/index.js +24 -3
- package/dist/schema.js +11 -0
- package/dist/sessions.d.ts +32 -0
- package/dist/sessions.js +48 -0
- package/dist/sql.d.ts +23 -1
- package/dist/sql.js +167 -55
- package/dist/tx.d.ts +1 -1
- package/dist/tx.js +16 -1
- package/dist/types.d.ts +44 -1
- package/dist/up.d.ts +48 -0
- package/dist/up.js +209 -0
- package/dist/write.d.ts +18 -12
- package/dist/write.js +247 -36
- package/docker/Dockerfile +17 -0
- package/docker/start.sh +4 -0
- package/package.json +16 -2
- package/sql/ddl.sql +362 -0
- package/sql/seed.auth.sql +46 -0
- package/sql/seed.booking.sql +64 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,177 @@
|
|
|
2
2
|
|
|
3
3
|
Формат: [Keep a Changelog](https://keepachangelog.com/), версии — semver.
|
|
4
4
|
|
|
5
|
+
## [0.13.0] — 2026-07-11
|
|
6
|
+
|
|
7
|
+
### Added — `up()`: одна точка входа
|
|
8
|
+
- **`up(opts): Promise<EntityDb>`** — от пустой машины до готового `db` одной функцией:
|
|
9
|
+
probe `dsn` (живой postgres — docker пропускается: CI-сервисы, внешние БД) → docker
|
|
10
|
+
ensure через CLI (`inspect`/`build` из пакованного Dockerfile/`run`/`start`) → ожидание
|
|
11
|
+
готовности PG + Redis → автосоздание базы из `dsn` (3D000) → версионная схема
|
|
12
|
+
`"v<version>.<schema>"` из шаблонов пакета (маркер `"<SCHEMA-NAME>"`) → сквозной
|
|
13
|
+
`connect()`. Идемпотентно на каждом шаге; `fresh: true` — дроп схемы и накат заново;
|
|
14
|
+
`seeds: string[] | false` — свои сиды вместо демо. Живые тайминги: с нуля
|
|
15
|
+
(build+initdb+apply) 7.4 s, `docker start` 1.2 s, всё готово ~110 ms.
|
|
16
|
+
- **Данные PG — на хосте**: контейнер поднимается с named volume **`letopis-pgdata`**
|
|
17
|
+
(кроссплатформенно, переживает пересоздание контейнера; физически — внутри
|
|
18
|
+
docker-диска WSL2/ext4), `dataDir: '/path'` — bind mount папки (на Windows/NTFS —
|
|
19
|
+
на свой риск). Redis без персиста — сессии эфемерны.
|
|
20
|
+
- SQL-шаблоны и Dockerfile теперь **пакуются в npm** (`lib/sql/`, `lib/docker/`,
|
|
21
|
+
`files += sql, docker`) — `up()` работает у любого потребителя пакета из коробки.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Dev-контейнер переименован: образ `clockz-db` → **`letopis-db`**, контейнер
|
|
25
|
+
`clockz-timescale` → **`letopis-timescale`** (данные перенесены дампом, полигон
|
|
26
|
+
`v1.article` 1 112 952 строк цел).
|
|
27
|
+
- Шаблоны переехали `db/*.sql` → `lib/sql/`, докер-файлы `db/docker/` → `lib/docker/`;
|
|
28
|
+
`db/apply.mjs` остался CLI-обёрткой и читает шаблоны из `lib/sql/`.
|
|
29
|
+
|
|
30
|
+
## [0.12.0] — 2026-07-11
|
|
31
|
+
|
|
32
|
+
### Changed — BREAKING: шаблон SQL + версия движка в имени схемы
|
|
33
|
+
- **SQL-шаблоны (`db/ddl.sql`, сиды) держат маркер `"<SCHEMA-NAME>"`** вместо реального
|
|
34
|
+
имени: подстановка тотальная и безопасна by construction — jsonb-литералы (`'booking'`
|
|
35
|
+
в enum, ключи settings) совпасть с маркером не могут (старый text-replace по живому
|
|
36
|
+
имени уже кусался).
|
|
37
|
+
- **`db/apply.mjs`: обязательный `--version=N`** (целое ≥ 1, версия ДВИЖКА — бамп руками
|
|
38
|
+
при breaking-изменении DDL); итоговая PG-схема — **`vN.<имя>`**: `--schema=booking
|
|
39
|
+
--version=1` → `"v1.booking"` (точка в имени — валидный идентификатор в кавычках;
|
|
40
|
+
v1 и v2 живут в БД рядом). `--schema` принимает базовое имя без точек.
|
|
41
|
+
- **`connect({ schema })` принимает ПОЛНОЕ имя** (`'v1.booking'`) — либа префикс не
|
|
42
|
+
достраивает и о версиях не знает. Канал `pg_notify`/LISTEN = полное имя схемы
|
|
43
|
+
(`pg_notify('v1.booking', …)` — строковый литерал; LISTEN-идентификатор postgres.js
|
|
44
|
+
кавычит сам).
|
|
45
|
+
- Все тестовые/бенч-схемы переехали: `v1.booking`, `v1.article`, `v1.acl`, `v1.depth`,
|
|
46
|
+
`v1.plan`, `v1.wave4`, `v1.bench`; безверсионные дропнуты.
|
|
47
|
+
|
|
48
|
+
## [0.11.0] — 2026-07-11
|
|
49
|
+
|
|
50
|
+
### Changed — BREAKING: цепочка = план, операции = звенья
|
|
51
|
+
- **`.set(data?)` / `.delete(opts?)` / `.anonymize(fields)` возвращают ЦЕПОЧКУ** (Chain),
|
|
52
|
+
а не исполняются сами: операция применяется к шагу, к которому приклеена точкой;
|
|
53
|
+
**исполняет терминал** (`rows/first/ids/count/run/versions/агрегации`) — весь план
|
|
54
|
+
**одной транзакцией** (deny/валидация любого сегмента откатывает всё). Голый
|
|
55
|
+
`await …set(…)` без терминала больше НЕ пишет (цепочка не thenable). Миграция:
|
|
56
|
+
`await db.X(id).set({…})` → `await db.X(id).set({…}).rows()`.
|
|
57
|
+
- **Продолжение цепочки — от результата операции** (fan-out по строкам):
|
|
58
|
+
`db.Клиент({vip: true}).set({bonus: 500}).Запись().delete({confirm: true}).rows()` —
|
|
59
|
+
обновить всех vip и снести записи каждого. Повторная операция без шага — к тем же
|
|
60
|
+
строкам: `X(id).set({a}).set({b}).rows()` — две версии подряд.
|
|
61
|
+
- **`execute()` → `run()`** (пути; то же у батча: `batch.execute()` → `batch.run()`);
|
|
62
|
+
старые имена бросают понятную ошибку.
|
|
63
|
+
- **`delete({ confirm: true })` — удалить; БЕЗ confirm — превью**: терминал возвращает
|
|
64
|
+
кандидатов (цели + каскад), БД не тронута.
|
|
65
|
+
- **Пост-довесы связей удалены** (`set({}).Окно(w)`, z-форма, тип `SetChain`): имя класса
|
|
66
|
+
после операции — переход-шаг. Связи — контекст-шагами до операции; для значения связи
|
|
67
|
+
БЕЗ участия в фильтре целей — новый модификатор **`.link(Класс, target)`**:
|
|
68
|
+
`tr.Запись(b).позиция({}).link('Сотрудник', новый).set({}).rows()`.
|
|
69
|
+
- Ошибки записи (ValidationError, abstract, context step, acl) летят из терминала (async).
|
|
70
|
+
- В батче `await …set(…)` больше не возвращает номер очереди (план в очереди; Chain).
|
|
71
|
+
- Модификатор шага сразу после операции — ошибка `step modifier after set()`.
|
|
72
|
+
|
|
73
|
+
## [0.10.0] — 2026-07-11
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- **ACL по Resource/Rule** (формат согласован повторно, закрыт последний гэп RESEARCH.md):
|
|
77
|
+
- `db.acl.check(account, endpoint)` — legacy-семантика 1:1: субъект-группы по
|
|
78
|
+
`Account.categories` (`{A,B}` / `!{A,B}` / NULL), маски эндпоинтов (`*`, `{a,b}`),
|
|
79
|
+
победа ровно одного правила (max weight, при равенстве deny), без правил — deny.
|
|
80
|
+
- **Операции над данными = категории ресурсов `READ`/`WRITE`/`DELETE`**; pattern —
|
|
81
|
+
**шаблон строки Entity**: реальные колонки (`class`, `owner`, `account`, `tags`,
|
|
82
|
+
`data`, `links`…) с литералами или `"$account"` (подставляется динамически);
|
|
83
|
+
`class`-маска действует на потомков (lineage). Никаких выдуманных полей.
|
|
84
|
+
- `db.acl.checkData(account, class, op)` → `{allow, filter?}` — filter = остаточный
|
|
85
|
+
шаблон строк победившего правила с подставленным `$account`.
|
|
86
|
+
- **`connect({ account, enforceAcl: true })`**: READ на каждый шаг цепочки, WRITE на
|
|
87
|
+
`set()`/anonymize/батчи, DELETE на цели и все классы каскадного замыкания (deny
|
|
88
|
+
откатывает транзакцию). **Предикат вливается в SQL до сортировки/лимита** — пагинация,
|
|
89
|
+
count, keyset честные. INSERT пришпиливает `$account`-колонки; upsert по явному id
|
|
90
|
+
НЕ перехватывает существующую недоступную сущность. `watch()` отдаёт события только
|
|
91
|
+
безусловных allow-классов. Deny-by-default; правила фиксируются на connect.
|
|
92
|
+
- `bench/acl.bench.mjs` (1.1M): безусловное правило — бесплатно; предикат в worst-case
|
|
93
|
+
(100% строк) +33–99% на full-scan; в реальности предикат сужает выборку — дешевле базы.
|
|
94
|
+
- `test/acl.test.ts` — 9 сцен; суммарно 114 тестов. Сид не менялся.
|
|
95
|
+
|
|
96
|
+
## [0.9.0] — 2026-07-11
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- **OTP** в `db.auth` (node:crypto, без зависимостей):
|
|
100
|
+
- **TOTP** (RFC 6238, authenticator-приложения): `enrollTotp` (секрет base32 + otpauth-URI
|
|
101
|
+
для QR; re-enroll сбрасывает), `verifyTotp` (окно ±1 шаг, replay принятого шага отбит
|
|
102
|
+
через `meta.lastStep`, первая успешная проверка активирует фактор), `totpEnabled`;
|
|
103
|
+
хелпер `totpCode(secret, atMs?)` экспортирован (тесты/серверная генерация).
|
|
104
|
+
- **Одноразовые коды** (email/SMS/сброс — доставка на приложении): `issueOtp` (в БД
|
|
105
|
+
только sha256 + expires + attempts; повторный выпуск затирает старый код),
|
|
106
|
+
`verifyOtp` (успех сжигает код; 5 неудач сжигают; истёкший TTL сжигает).
|
|
107
|
+
- `test/auth.test.ts`: +2 сцены (TOTP: enroll/verify/replay/окно/re-enroll; OTP:
|
|
108
|
+
одноразовость/лимит/TTL/перевыпуск); суммарно 105 тестов.
|
|
109
|
+
|
|
110
|
+
## [0.8.0] — 2026-07-11
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
- **Устойчивость к сбоям** (§ 10.12 README):
|
|
114
|
+
- transient-ошибки PG (deadlock `40P01`, serialization `40001`) ретраятся там, где
|
|
115
|
+
повтор безопасен: чтения в автокоммите (до 3 попыток, backoff 40–160 ms), `insertOne`
|
|
116
|
+
(плюс прежний 23505), внутренние транзакции delete-каскада и батчей — повтор целиком;
|
|
117
|
+
- внутри `db.begin()` повтор невозможен (транзакция aborted) — ошибка уходит сразу
|
|
118
|
+
с подсказкой `retry the whole db.begin() block`; `tr.lock()` при deadlock — та же подсказка;
|
|
119
|
+
- ретрай 23505/transient в `insertOne` больше не срабатывает внутри транзакции
|
|
120
|
+
(раньше повтор в aborted-транзакции маскировал исходную ошибку кодом 25P02).
|
|
121
|
+
- **`watch(…, { onReconnect })`** — LISTEN-соединение и так переживает обрывы
|
|
122
|
+
(postgres.js: reconnect + повторный LISTEN), но NOTIFY за время разрыва потеряны;
|
|
123
|
+
`onReconnect` зовётся после каждого восстановления — точка дочитать пропущенное.
|
|
124
|
+
Типы `WatchEvent`/`WatchOpts` экспортированы.
|
|
125
|
+
- `test/resilience.test.ts` — юнит-ретраи, настоящий deadlock двух транзакций
|
|
126
|
+
(встречные `lock()`), обрыв LISTEN через `pg_terminate_backend` (reconnect ~0.4 s);
|
|
127
|
+
суммарно 103 теста.
|
|
128
|
+
|
|
129
|
+
## [0.7.0] — 2026-07-10
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
- **`db.auth`** — вход по таблице Credential, на аккаунт много способов:
|
|
133
|
+
- пароль (`setPassword`/`verifyPassword`) — scrypt из node:crypto (без зависимостей),
|
|
134
|
+
формат `scrypt$N$r$p$salt$hash`; время ответа выровнено dummy-verify;
|
|
135
|
+
- api-ключ (`issueApiKey`/`verifyApiKey`) — `lts_<48hex>` показывается один раз,
|
|
136
|
+
в БД только sha256 (legacy хранил открыто — исправлено);
|
|
137
|
+
- ключ-секрет (`issueKeySecret`/`verifyKeySecret`) — key открытый id, secret хэшем;
|
|
138
|
+
- внешние identity (`link`/`lookup`) — oauth/sso/telegram: токен проверяет приложение;
|
|
139
|
+
- все verify: кред жив + `confirmed` (отключаемо `requireConfirmed:false`) + аккаунт `enabled`.
|
|
140
|
+
- **Сессии в Redis**: `db.auth.sessions(store)` → `start/check/revoke/revokeAll`;
|
|
141
|
+
клиент инжектируется (интерфейс `SessionStore`, ioredis подходит как есть; в prod-зависимости
|
|
142
|
+
не входит); в Redis — sha256 токена, не сам токен.
|
|
143
|
+
- `credential_identity_udx` — один живой кред на `(category, identifier)` во всей схеме:
|
|
144
|
+
вход по identifier однозначен; мягко удалённый identifier освобождается.
|
|
145
|
+
- Dev-контейнер `db/docker/Dockerfile`: TimescaleDB + Redis в одном контейнере
|
|
146
|
+
(порты 15432/16379); CI — сервис redis:7.
|
|
147
|
+
- `test/auth.test.ts` — 7 сцен, включая живой Redis (expire/revoke); суммарно 98 тестов.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
- CI: артефакт пакета собирался по паттерну `entity-chain-*.tgz` и был пуст после
|
|
151
|
+
переименования пакета — теперь `letopis-*.tgz`.
|
|
152
|
+
- `bench/article-demo.mjs`: битый относительный импорт `./src/index.js` → `../src/index.js`.
|
|
153
|
+
|
|
154
|
+
## [0.6.0] — 2026-07-10
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
- **data-пути любой глубины** во всём API: containment-фильтры, операторы (включая
|
|
158
|
+
`exists`/`has*` на вложенных ключах), `.sort()`, `.after()`/`cursorOf()`,
|
|
159
|
+
`.sum/.avg/.min/.max/.countBy`. Тип листа (и SQL-каст) берётся из вложенных
|
|
160
|
+
`{type:'object', props:{…}}` схемы — новый `FieldType 'object'`.
|
|
161
|
+
- Пример в booking-схеме: `Org.settings.booking.deposit.{amount,currency}` (лист глубины 4),
|
|
162
|
+
строгая валидация и default-ы на каждом уровне; deep-merge обновляет один вложенный лист,
|
|
163
|
+
не трогая соседей (покрыто тестом).
|
|
164
|
+
- Наследование классов 3+ уровней покрыто тестом lineage (`Vehicle → Car → SportsCar`).
|
|
165
|
+
- `gen-types`: `{type:'object', props}` → вложенные TS-литералы.
|
|
166
|
+
- `test/depth.test.ts` — 5 сцен глубины; суммарно 91 тест.
|
|
167
|
+
|
|
168
|
+
## [0.5.1] — 2026-07-10
|
|
169
|
+
|
|
170
|
+
### Fixed
|
|
171
|
+
- `.sort('data.price.RUB')` — сортировка по вложенному record-пути молча давала ORDER BY NULL
|
|
172
|
+
(ключ с точкой вместо пути); теперь строится `data->'price'->>'RUB'` с кастом по типу листа.
|
|
173
|
+
Тот же путь понимают `.after()` и `cursorOf()`. Дефект пойман живым прогоном примеров статьи.
|
|
174
|
+
- `.min()/.max()` возвращали numeric строкой (`"800"`); для number-полей по Schema — число.
|
|
175
|
+
|
|
5
176
|
## [0.5.0] — 2026-07-10
|
|
6
177
|
|
|
7
178
|
### Changed
|