letopis 0.5.0 → 0.16.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 +269 -0
- package/README.md +2472 -240
- 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 +81 -28
- package/dist/chain.js +312 -72
- package/dist/index.d.ts +10 -3
- package/dist/index.js +26 -3
- package/dist/schema.js +107 -4
- package/dist/sessions.d.ts +32 -0
- package/dist/sessions.js +48 -0
- package/dist/sql.d.ts +43 -1
- package/dist/sql.js +238 -79
- package/dist/tx.d.ts +1 -1
- package/dist/tx.js +16 -1
- package/dist/types.d.ts +81 -2
- package/dist/up.d.ts +48 -0
- package/dist/up.js +209 -0
- package/dist/uuid.d.ts +6 -0
- package/dist/uuid.js +32 -0
- package/dist/write.d.ts +29 -16
- package/dist/write.js +427 -98
- package/docker/Dockerfile +17 -0
- package/docker/start.sh +4 -0
- package/package.json +16 -2
- package/sql/ddl.sql +404 -0
- package/sql/seed.auth.sql +46 -0
- package/sql/seed.booking.sql +69 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,275 @@
|
|
|
2
2
|
|
|
3
3
|
Формат: [Keep a Changelog](https://keepachangelog.com/), версии — semver.
|
|
4
4
|
|
|
5
|
+
## [0.16.0] — 2026-07-12
|
|
6
|
+
|
|
7
|
+
### Changed — BREAKING: глаголы записи create/update, слот unset, id считает Schema
|
|
8
|
+
|
|
9
|
+
- **`set()` разбит на `create()` / `update()`** — мина «`Класс()` → INSERT,
|
|
10
|
+
`Класс({})` → UPDATE всех» мертва, намерение всегда явное:
|
|
11
|
+
- **`create(data?)`** — INSERT; известный id (шаг `Класс(id)`, `data.id` или
|
|
12
|
+
вычисленный v5) уже существует → новая версия (идемпотентный create,
|
|
13
|
+
REST-PUT семантика). Фильтр-объект перед create — ошибка ПОСТРОЕНИЯ
|
|
14
|
+
(`create() takes no filter`); create на pivot-шаге — ошибка.
|
|
15
|
+
- **`update(data?)`** — новая версия КАЖДОГО найденного путём;
|
|
16
|
+
`Класс()` ≡ `Класс({})` — «все в границах контекста»; не найдено → `[]`,
|
|
17
|
+
update НИКОГДА не создаёт.
|
|
18
|
+
- `set()` бросает подсказку `set() split into create()/update() (0.16.0)`.
|
|
19
|
+
- **Слот-снятие переименовано: `.Класс.delete()` → `.Класс.unset()`** — развели
|
|
20
|
+
«удалить сущность» (`delete()`) и «снять конец связи» (`unset()`); старое имя
|
|
21
|
+
бросает подсказку. Слот теперь требует глагол записи ПЕРЕД собой:
|
|
22
|
+
`…create(…).Класс.set(x)` / `…update(…).Класс.unset()`; слот без операции —
|
|
23
|
+
ошибка `link slot needs a write` (раньше слот молча создавал `set({})`).
|
|
24
|
+
- **Генерация id по Schema** — `attributes.id`:
|
|
25
|
+
- `"uuid"` / `{type:'uuid'}` → **v4** (random, как раньше — дефолт);
|
|
26
|
+
- `{type:'uuid', generate: 7}` → **v7** (unix-время в старших битах — вставки
|
|
27
|
+
ложатся в хвост btree-индекса);
|
|
28
|
+
- `{type:'uuid', generate: 5, from:[…]}` → **v5, детерминированный**:
|
|
29
|
+
`uuidv5("pgSchema:partition:класс:значения from")`; `from` — имена
|
|
30
|
+
ОБЯЗАТЕЛЬНЫХ концов `Schema.links` (класс или полное имя союза
|
|
31
|
+
`'Service|Complex'`; значение — id конца) и/или скалярных полей `data`.
|
|
32
|
+
Явный id у v5-класса запрещён (`computes id`). Свойства: **create
|
|
33
|
+
идемпотентен** (та же комбинация → та же сущность, дубль невозможен даже в
|
|
34
|
+
гонке — двойная бронь мертва на уровне схемы) и **id известен ДО создания**
|
|
35
|
+
(формула открыта; namespace letopis `c7a2f9d4-3b61-4e8a-9f05-8d2c1e6b7a90`).
|
|
36
|
+
- Батч: multi-VALUES-склейка для v5-классов отключена (id нужны концы) —
|
|
37
|
+
такие планы исполняются поштучно, семантика та же.
|
|
38
|
+
- **Наследование attributes по иерархии Schema**: при загрузке реестра attributes
|
|
39
|
+
класса собираются по цепочке `ancestor` — потомок ПОВЕРХ предка, переопределение
|
|
40
|
+
поля = замена правила целиком (не слияние). Правило `id` (генерация) наследуется
|
|
41
|
+
так же; `links` НЕ наследуются (концы объявляются каждым классом). Валидатор и
|
|
42
|
+
fieldTypes компилируются из слитых attributes.
|
|
43
|
+
- **Демо-схема booking целиком на generate** — с наследованием: `Entity` и `link`
|
|
44
|
+
объявляют дефолт `{generate: 7}`, v7-классы (Org/Staff/Folder/Customer/Schedule/
|
|
45
|
+
Booking/item) наследуют его без собственного правила; v5-классы переопределяют:
|
|
46
|
+
`busy ← v5(Slot, Staff)`; `skill ← v5(Staff, Service|Complex)`;
|
|
47
|
+
`shift ← v5(Schedule, Staff)`; `compo ← v5(Complex, Service)`;
|
|
48
|
+
`Slot ← v5(Schedule, data.start)`; `Service`/`Complex` ← `v5(Org, data.name)`
|
|
49
|
+
(имя уникально в организации). Полигоны `v1.salondemo`/`v1.article` обновлены
|
|
50
|
+
на месте (UPDATE Schema; данные нетронуты).
|
|
51
|
+
- Тесты мигрированы (все `set`→`create`/`update`, слот `.delete`→`.unset`,
|
|
52
|
+
фикс-id v5-классов → вычисляемые); новый **`test/idgen.test.ts`** — генерация
|
|
53
|
+
id на боевых классах Schema (v4/v7/v5 из поля и концов, гонка без локов,
|
|
54
|
+
наследование attributes/id-правила, схема-гарды). SALON.md и демо-полигон
|
|
55
|
+
перегнаны на 0.16.0. Итого **165 тестов**.
|
|
56
|
+
|
|
57
|
+
## [0.15.0] — 2026-07-12
|
|
58
|
+
|
|
59
|
+
### Changed — BREAKING: единый закон пути, слоты связей, pivot, entity()
|
|
60
|
+
- **Один закон навигации для чтения И записи**: цепочка всегда подчиняется переходам
|
|
61
|
+
`Schema.links`; недопустимый переход — ошибка СИНХРОННО при построении (не в терминале).
|
|
62
|
+
Мультиродительский «контекст-сбор» через невалидные HUB→HUB прыжки удалён.
|
|
63
|
+
- **Слоты связей** вместо `.link()`: конец связки задаётся `.Класс.set(target)` ПОСЛЕ
|
|
64
|
+
`.set(данные)` — свойство-класс БЕЗ вызова. Владелец создаваемой связки — из валидного
|
|
65
|
+
пути, прочие концы — слотами. `target` = id | Row | **вложенная цепочка** (исполняется в
|
|
66
|
+
той же транзакции, обязана дать ровно одну сущность класса конца — «создать И привязать»).
|
|
67
|
+
`.Класс.delete()` снимает optional-конец; союз-конец `[A|B]` замещается слотом целиком.
|
|
68
|
+
**`.link()` УДАЛЁН** (бросает подсказку).
|
|
69
|
+
- **pivot**: повтор LINK-класса в пути — возврат к тому же узлу (ветвление к другому концу
|
|
70
|
+
+ дофильтровка AND). `db.Запись(з).позиция().Услуга(у).позиция().Сотрудник(вася)` —
|
|
71
|
+
«позиции записи з с услугой у у мастера вася» одной цепочкой.
|
|
72
|
+
- **`entity(x)`** (Chain и db): вставить узел-переменную/паттерн (`db.позиция()`) или Row в
|
|
73
|
+
путь; та же переменная повторно — явный pivot; `db.entity(row).…` — старт с готовой строки.
|
|
74
|
+
- Тесты мигрированы на слоты; **`test/salon.test.ts`** — тест-план «работа салона»
|
|
75
|
+
(21 акт), покрывающий ВСЕ 145 публичных точек API + матрица покрытия. Итого 153 теста.
|
|
76
|
+
|
|
77
|
+
## [0.14.0] — 2026-07-12
|
|
78
|
+
|
|
79
|
+
### Changed — Schema.links v2: концы связей объектами
|
|
80
|
+
- **Конец связи — объект**: `{"class":"Org","cardinality":1}` /
|
|
81
|
+
`{"classes":["Service","Complex"],"cardinality":1}` (союз ролей) /
|
|
82
|
+
`{"class":"Staff","optional":true,…}`. `'Entity'`-полиморф в новых схемах не
|
|
83
|
+
используется — классы концов называются явно. **Колонка `Schema.links` — `jsonb`**
|
|
84
|
+
(настоящий массив объектов; в новых схемах). Старые схемы с `text[]` читаются тем же
|
|
85
|
+
кодом по прежним правилам (legacy-строки `'Org'`, `'Entity'`-полиморф).
|
|
86
|
+
- **Валидация строгая на двух уровнях** (либа `write.ts` + БД-триггер
|
|
87
|
+
`entity_check`): жадный матчинг ключей `Entity.links` по порядку объявления
|
|
88
|
+
концов; обязательный конец без ключа → `requires end "Service|Complex"`;
|
|
89
|
+
связь вне концов → `has stray link(s)` (раньше лишние молчали). Голый SQL
|
|
90
|
+
мимо либы ловится триггером так же.
|
|
91
|
+
- **Демо-домен уточнён**: `позиция = [Запись, {Услуга|Комплекс}, Сотрудник?]` —
|
|
92
|
+
три конца, владелец строго Запись; состав комплекса — новый LINK-класс
|
|
93
|
+
**`compo`/«состав» = [Комплекс, Услуга]** (раньше ездил на позиции);
|
|
94
|
+
`навык = [Сотрудник, {Услуга|Комплекс}]`; `занятость = [Сотрудник, Окно, Запись?]`
|
|
95
|
+
(третий конец легализован — раньше жил контрабандой); `Запись = [Клиент?]`
|
|
96
|
+
(ручная бронь без клиента). `cardinality` — зарезервировано, пока не проверяется.
|
|
97
|
+
- Полигон `v1.article` мигрирован НА МЕСТЕ (ALTER links → jsonb + apply поверх):
|
|
98
|
+
данные Entity нетронуты (1 112 952), схема идентична booking.
|
|
99
|
+
- `lib/scripts/gen-seed.mjs` — сид Schema теперь реально генерится из
|
|
100
|
+
`schema.booking.v2.json` (комментарий «файл сгенерирован» стал правдой);
|
|
101
|
+
`schema-sync` сравнивает концы канонически (объект ⇔ JSON-текст в text[]).
|
|
102
|
+
|
|
103
|
+
## [0.13.0] — 2026-07-11
|
|
104
|
+
|
|
105
|
+
### Added — `up()`: одна точка входа
|
|
106
|
+
- **`up(opts): Promise<EntityDb>`** — от пустой машины до готового `db` одной функцией:
|
|
107
|
+
probe `dsn` (живой postgres — docker пропускается: CI-сервисы, внешние БД) → docker
|
|
108
|
+
ensure через CLI (`inspect`/`build` из пакованного Dockerfile/`run`/`start`) → ожидание
|
|
109
|
+
готовности PG + Redis → автосоздание базы из `dsn` (3D000) → версионная схема
|
|
110
|
+
`"v<version>.<schema>"` из шаблонов пакета (маркер `"<SCHEMA-NAME>"`) → сквозной
|
|
111
|
+
`connect()`. Идемпотентно на каждом шаге; `fresh: true` — дроп схемы и накат заново;
|
|
112
|
+
`seeds: string[] | false` — свои сиды вместо демо. Живые тайминги: с нуля
|
|
113
|
+
(build+initdb+apply) 7.4 s, `docker start` 1.2 s, всё готово ~110 ms.
|
|
114
|
+
- **Данные PG — на хосте**: контейнер поднимается с named volume **`letopis-pgdata`**
|
|
115
|
+
(кроссплатформенно, переживает пересоздание контейнера; физически — внутри
|
|
116
|
+
docker-диска WSL2/ext4), `dataDir: '/path'` — bind mount папки (на Windows/NTFS —
|
|
117
|
+
на свой риск). Redis без персиста — сессии эфемерны.
|
|
118
|
+
- SQL-шаблоны и Dockerfile теперь **пакуются в npm** (`lib/sql/`, `lib/docker/`,
|
|
119
|
+
`files += sql, docker`) — `up()` работает у любого потребителя пакета из коробки.
|
|
120
|
+
|
|
121
|
+
### Changed
|
|
122
|
+
- Dev-контейнер переименован: образ `clockz-db` → **`letopis-db`**, контейнер
|
|
123
|
+
`clockz-timescale` → **`letopis-timescale`** (данные перенесены дампом, полигон
|
|
124
|
+
`v1.article` 1 112 952 строк цел).
|
|
125
|
+
- Шаблоны переехали `db/*.sql` → `lib/sql/`, докер-файлы `db/docker/` → `lib/docker/`;
|
|
126
|
+
`db/apply.mjs` остался CLI-обёрткой и читает шаблоны из `lib/sql/`.
|
|
127
|
+
|
|
128
|
+
## [0.12.0] — 2026-07-11
|
|
129
|
+
|
|
130
|
+
### Changed — BREAKING: шаблон SQL + версия движка в имени схемы
|
|
131
|
+
- **SQL-шаблоны (`db/ddl.sql`, сиды) держат маркер `"<SCHEMA-NAME>"`** вместо реального
|
|
132
|
+
имени: подстановка тотальная и безопасна by construction — jsonb-литералы (`'booking'`
|
|
133
|
+
в enum, ключи settings) совпасть с маркером не могут (старый text-replace по живому
|
|
134
|
+
имени уже кусался).
|
|
135
|
+
- **`db/apply.mjs`: обязательный `--version=N`** (целое ≥ 1, версия ДВИЖКА — бамп руками
|
|
136
|
+
при breaking-изменении DDL); итоговая PG-схема — **`vN.<имя>`**: `--schema=booking
|
|
137
|
+
--version=1` → `"v1.booking"` (точка в имени — валидный идентификатор в кавычках;
|
|
138
|
+
v1 и v2 живут в БД рядом). `--schema` принимает базовое имя без точек.
|
|
139
|
+
- **`connect({ schema })` принимает ПОЛНОЕ имя** (`'v1.booking'`) — либа префикс не
|
|
140
|
+
достраивает и о версиях не знает. Канал `pg_notify`/LISTEN = полное имя схемы
|
|
141
|
+
(`pg_notify('v1.booking', …)` — строковый литерал; LISTEN-идентификатор postgres.js
|
|
142
|
+
кавычит сам).
|
|
143
|
+
- Все тестовые/бенч-схемы переехали: `v1.booking`, `v1.article`, `v1.acl`, `v1.depth`,
|
|
144
|
+
`v1.plan`, `v1.wave4`, `v1.bench`; безверсионные дропнуты.
|
|
145
|
+
|
|
146
|
+
## [0.11.0] — 2026-07-11
|
|
147
|
+
|
|
148
|
+
### Changed — BREAKING: цепочка = план, операции = звенья
|
|
149
|
+
- **`.set(data?)` / `.delete(opts?)` / `.anonymize(fields)` возвращают ЦЕПОЧКУ** (Chain),
|
|
150
|
+
а не исполняются сами: операция применяется к шагу, к которому приклеена точкой;
|
|
151
|
+
**исполняет терминал** (`rows/first/ids/count/run/versions/агрегации`) — весь план
|
|
152
|
+
**одной транзакцией** (deny/валидация любого сегмента откатывает всё). Голый
|
|
153
|
+
`await …set(…)` без терминала больше НЕ пишет (цепочка не thenable). Миграция:
|
|
154
|
+
`await db.X(id).set({…})` → `await db.X(id).set({…}).rows()`.
|
|
155
|
+
- **Продолжение цепочки — от результата операции** (fan-out по строкам):
|
|
156
|
+
`db.Клиент({vip: true}).set({bonus: 500}).Запись().delete({confirm: true}).rows()` —
|
|
157
|
+
обновить всех vip и снести записи каждого. Повторная операция без шага — к тем же
|
|
158
|
+
строкам: `X(id).set({a}).set({b}).rows()` — две версии подряд.
|
|
159
|
+
- **`execute()` → `run()`** (пути; то же у батча: `batch.execute()` → `batch.run()`);
|
|
160
|
+
старые имена бросают понятную ошибку.
|
|
161
|
+
- **`delete({ confirm: true })` — удалить; БЕЗ confirm — превью**: терминал возвращает
|
|
162
|
+
кандидатов (цели + каскад), БД не тронута.
|
|
163
|
+
- **Пост-довесы связей удалены** (`set({}).Окно(w)`, z-форма, тип `SetChain`): имя класса
|
|
164
|
+
после операции — переход-шаг. Связи — контекст-шагами до операции; для значения связи
|
|
165
|
+
БЕЗ участия в фильтре целей — новый модификатор **`.link(Класс, target)`**:
|
|
166
|
+
`tr.Запись(b).позиция({}).link('Сотрудник', новый).set({}).rows()`.
|
|
167
|
+
- Ошибки записи (ValidationError, abstract, context step, acl) летят из терминала (async).
|
|
168
|
+
- В батче `await …set(…)` больше не возвращает номер очереди (план в очереди; Chain).
|
|
169
|
+
- Модификатор шага сразу после операции — ошибка `step modifier after set()`.
|
|
170
|
+
|
|
171
|
+
## [0.10.0] — 2026-07-11
|
|
172
|
+
|
|
173
|
+
### Added
|
|
174
|
+
- **ACL по Resource/Rule** (формат согласован повторно, закрыт последний гэп RESEARCH.md):
|
|
175
|
+
- `db.acl.check(account, endpoint)` — legacy-семантика 1:1: субъект-группы по
|
|
176
|
+
`Account.categories` (`{A,B}` / `!{A,B}` / NULL), маски эндпоинтов (`*`, `{a,b}`),
|
|
177
|
+
победа ровно одного правила (max weight, при равенстве deny), без правил — deny.
|
|
178
|
+
- **Операции над данными = категории ресурсов `READ`/`WRITE`/`DELETE`**; pattern —
|
|
179
|
+
**шаблон строки Entity**: реальные колонки (`class`, `owner`, `account`, `tags`,
|
|
180
|
+
`data`, `links`…) с литералами или `"$account"` (подставляется динамически);
|
|
181
|
+
`class`-маска действует на потомков (lineage). Никаких выдуманных полей.
|
|
182
|
+
- `db.acl.checkData(account, class, op)` → `{allow, filter?}` — filter = остаточный
|
|
183
|
+
шаблон строк победившего правила с подставленным `$account`.
|
|
184
|
+
- **`connect({ account, enforceAcl: true })`**: READ на каждый шаг цепочки, WRITE на
|
|
185
|
+
`set()`/anonymize/батчи, DELETE на цели и все классы каскадного замыкания (deny
|
|
186
|
+
откатывает транзакцию). **Предикат вливается в SQL до сортировки/лимита** — пагинация,
|
|
187
|
+
count, keyset честные. INSERT пришпиливает `$account`-колонки; upsert по явному id
|
|
188
|
+
НЕ перехватывает существующую недоступную сущность. `watch()` отдаёт события только
|
|
189
|
+
безусловных allow-классов. Deny-by-default; правила фиксируются на connect.
|
|
190
|
+
- `bench/acl.bench.mjs` (1.1M): безусловное правило — бесплатно; предикат в worst-case
|
|
191
|
+
(100% строк) +33–99% на full-scan; в реальности предикат сужает выборку — дешевле базы.
|
|
192
|
+
- `test/acl.test.ts` — 9 сцен; суммарно 114 тестов. Сид не менялся.
|
|
193
|
+
|
|
194
|
+
## [0.9.0] — 2026-07-11
|
|
195
|
+
|
|
196
|
+
### Added
|
|
197
|
+
- **OTP** в `db.auth` (node:crypto, без зависимостей):
|
|
198
|
+
- **TOTP** (RFC 6238, authenticator-приложения): `enrollTotp` (секрет base32 + otpauth-URI
|
|
199
|
+
для QR; re-enroll сбрасывает), `verifyTotp` (окно ±1 шаг, replay принятого шага отбит
|
|
200
|
+
через `meta.lastStep`, первая успешная проверка активирует фактор), `totpEnabled`;
|
|
201
|
+
хелпер `totpCode(secret, atMs?)` экспортирован (тесты/серверная генерация).
|
|
202
|
+
- **Одноразовые коды** (email/SMS/сброс — доставка на приложении): `issueOtp` (в БД
|
|
203
|
+
только sha256 + expires + attempts; повторный выпуск затирает старый код),
|
|
204
|
+
`verifyOtp` (успех сжигает код; 5 неудач сжигают; истёкший TTL сжигает).
|
|
205
|
+
- `test/auth.test.ts`: +2 сцены (TOTP: enroll/verify/replay/окно/re-enroll; OTP:
|
|
206
|
+
одноразовость/лимит/TTL/перевыпуск); суммарно 105 тестов.
|
|
207
|
+
|
|
208
|
+
## [0.8.0] — 2026-07-11
|
|
209
|
+
|
|
210
|
+
### Added
|
|
211
|
+
- **Устойчивость к сбоям** (§ 10.12 README):
|
|
212
|
+
- transient-ошибки PG (deadlock `40P01`, serialization `40001`) ретраятся там, где
|
|
213
|
+
повтор безопасен: чтения в автокоммите (до 3 попыток, backoff 40–160 ms), `insertOne`
|
|
214
|
+
(плюс прежний 23505), внутренние транзакции delete-каскада и батчей — повтор целиком;
|
|
215
|
+
- внутри `db.begin()` повтор невозможен (транзакция aborted) — ошибка уходит сразу
|
|
216
|
+
с подсказкой `retry the whole db.begin() block`; `tr.lock()` при deadlock — та же подсказка;
|
|
217
|
+
- ретрай 23505/transient в `insertOne` больше не срабатывает внутри транзакции
|
|
218
|
+
(раньше повтор в aborted-транзакции маскировал исходную ошибку кодом 25P02).
|
|
219
|
+
- **`watch(…, { onReconnect })`** — LISTEN-соединение и так переживает обрывы
|
|
220
|
+
(postgres.js: reconnect + повторный LISTEN), но NOTIFY за время разрыва потеряны;
|
|
221
|
+
`onReconnect` зовётся после каждого восстановления — точка дочитать пропущенное.
|
|
222
|
+
Типы `WatchEvent`/`WatchOpts` экспортированы.
|
|
223
|
+
- `test/resilience.test.ts` — юнит-ретраи, настоящий deadlock двух транзакций
|
|
224
|
+
(встречные `lock()`), обрыв LISTEN через `pg_terminate_backend` (reconnect ~0.4 s);
|
|
225
|
+
суммарно 103 теста.
|
|
226
|
+
|
|
227
|
+
## [0.7.0] — 2026-07-10
|
|
228
|
+
|
|
229
|
+
### Added
|
|
230
|
+
- **`db.auth`** — вход по таблице Credential, на аккаунт много способов:
|
|
231
|
+
- пароль (`setPassword`/`verifyPassword`) — scrypt из node:crypto (без зависимостей),
|
|
232
|
+
формат `scrypt$N$r$p$salt$hash`; время ответа выровнено dummy-verify;
|
|
233
|
+
- api-ключ (`issueApiKey`/`verifyApiKey`) — `lts_<48hex>` показывается один раз,
|
|
234
|
+
в БД только sha256 (legacy хранил открыто — исправлено);
|
|
235
|
+
- ключ-секрет (`issueKeySecret`/`verifyKeySecret`) — key открытый id, secret хэшем;
|
|
236
|
+
- внешние identity (`link`/`lookup`) — oauth/sso/telegram: токен проверяет приложение;
|
|
237
|
+
- все verify: кред жив + `confirmed` (отключаемо `requireConfirmed:false`) + аккаунт `enabled`.
|
|
238
|
+
- **Сессии в Redis**: `db.auth.sessions(store)` → `start/check/revoke/revokeAll`;
|
|
239
|
+
клиент инжектируется (интерфейс `SessionStore`, ioredis подходит как есть; в prod-зависимости
|
|
240
|
+
не входит); в Redis — sha256 токена, не сам токен.
|
|
241
|
+
- `credential_identity_udx` — один живой кред на `(category, identifier)` во всей схеме:
|
|
242
|
+
вход по identifier однозначен; мягко удалённый identifier освобождается.
|
|
243
|
+
- Dev-контейнер `db/docker/Dockerfile`: TimescaleDB + Redis в одном контейнере
|
|
244
|
+
(порты 15432/16379); CI — сервис redis:7.
|
|
245
|
+
- `test/auth.test.ts` — 7 сцен, включая живой Redis (expire/revoke); суммарно 98 тестов.
|
|
246
|
+
|
|
247
|
+
### Fixed
|
|
248
|
+
- CI: артефакт пакета собирался по паттерну `entity-chain-*.tgz` и был пуст после
|
|
249
|
+
переименования пакета — теперь `letopis-*.tgz`.
|
|
250
|
+
- `bench/article-demo.mjs`: битый относительный импорт `./src/index.js` → `../src/index.js`.
|
|
251
|
+
|
|
252
|
+
## [0.6.0] — 2026-07-10
|
|
253
|
+
|
|
254
|
+
### Added
|
|
255
|
+
- **data-пути любой глубины** во всём API: containment-фильтры, операторы (включая
|
|
256
|
+
`exists`/`has*` на вложенных ключах), `.sort()`, `.after()`/`cursorOf()`,
|
|
257
|
+
`.sum/.avg/.min/.max/.countBy`. Тип листа (и SQL-каст) берётся из вложенных
|
|
258
|
+
`{type:'object', props:{…}}` схемы — новый `FieldType 'object'`.
|
|
259
|
+
- Пример в booking-схеме: `Org.settings.booking.deposit.{amount,currency}` (лист глубины 4),
|
|
260
|
+
строгая валидация и default-ы на каждом уровне; deep-merge обновляет один вложенный лист,
|
|
261
|
+
не трогая соседей (покрыто тестом).
|
|
262
|
+
- Наследование классов 3+ уровней покрыто тестом lineage (`Vehicle → Car → SportsCar`).
|
|
263
|
+
- `gen-types`: `{type:'object', props}` → вложенные TS-литералы.
|
|
264
|
+
- `test/depth.test.ts` — 5 сцен глубины; суммарно 91 тест.
|
|
265
|
+
|
|
266
|
+
## [0.5.1] — 2026-07-10
|
|
267
|
+
|
|
268
|
+
### Fixed
|
|
269
|
+
- `.sort('data.price.RUB')` — сортировка по вложенному record-пути молча давала ORDER BY NULL
|
|
270
|
+
(ключ с точкой вместо пути); теперь строится `data->'price'->>'RUB'` с кастом по типу листа.
|
|
271
|
+
Тот же путь понимают `.after()` и `cursorOf()`. Дефект пойман живым прогоном примеров статьи.
|
|
272
|
+
- `.min()/.max()` возвращали numeric строкой (`"800"`); для number-полей по Schema — число.
|
|
273
|
+
|
|
5
274
|
## [0.5.0] — 2026-07-10
|
|
6
275
|
|
|
7
276
|
### Changed
|