letopis 0.20.3 → 1.0.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.
Files changed (89) hide show
  1. package/AGENT-CHEATSHEET.en.md +368 -0
  2. package/AGENT-CHEATSHEET.md +354 -0
  3. package/CHANGELOG.md +348 -0
  4. package/MIGRATION.md +190 -0
  5. package/README.en.md +1937 -0
  6. package/README.md +1493 -3466
  7. package/dist/acl.d.ts +26 -50
  8. package/dist/acl.js +22 -267
  9. package/dist/admin.d.ts +138 -0
  10. package/dist/admin.js +170 -0
  11. package/dist/auth.d.ts +120 -73
  12. package/dist/auth.js +121 -306
  13. package/dist/cache.d.ts +73 -0
  14. package/dist/cache.js +148 -0
  15. package/dist/chain.d.ts +124 -191
  16. package/dist/chain.js +369 -551
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +164 -0
  19. package/dist/demo/booking.d.ts +289 -0
  20. package/dist/demo/booking.js +159 -0
  21. package/dist/errors.d.ts +29 -0
  22. package/dist/errors.js +70 -0
  23. package/dist/import.d.ts +179 -0
  24. package/dist/import.js +792 -0
  25. package/dist/index.d.ts +172 -26
  26. package/dist/index.js +304 -178
  27. package/dist/jsonschema.d.ts +22 -0
  28. package/dist/jsonschema.js +167 -0
  29. package/dist/load.d.ts +76 -0
  30. package/dist/load.js +884 -0
  31. package/dist/model.d.ts +166 -0
  32. package/dist/model.js +224 -0
  33. package/dist/ops.d.ts +7 -6
  34. package/dist/ops.js +7 -51
  35. package/dist/pglite.d.ts +22 -0
  36. package/dist/pglite.js +45 -0
  37. package/dist/registry.d.ts +57 -0
  38. package/dist/registry.js +82 -0
  39. package/dist/sql.d.ts +59 -142
  40. package/dist/sql.js +568 -654
  41. package/dist/sync.d.ts +31 -0
  42. package/dist/sync.js +108 -0
  43. package/dist/tx.d.ts +129 -8
  44. package/dist/tx.js +300 -73
  45. package/dist/typed.d.ts +97 -0
  46. package/dist/typed.js +1 -0
  47. package/dist/types.d.ts +71 -250
  48. package/dist/types.js +27 -108
  49. package/dist/up.d.ts +140 -47
  50. package/dist/up.js +339 -267
  51. package/dist/uuid.d.ts +21 -6
  52. package/dist/uuid.js +48 -64
  53. package/dist/validate.d.ts +24 -0
  54. package/dist/validate.js +251 -0
  55. package/dist/watch.d.ts +62 -0
  56. package/dist/watch.js +168 -0
  57. package/dist/write.d.ts +117 -74
  58. package/dist/write.js +658 -720
  59. package/llms.txt +26 -0
  60. package/package.json +49 -19
  61. package/sql/10-core.sql +136 -0
  62. package/sql/15-errors.sql +60 -0
  63. package/sql/20-context.sql +153 -0
  64. package/sql/30-validate.sql +423 -0
  65. package/sql/40-class.sql +259 -0
  66. package/sql/50-acl.sql +539 -0
  67. package/sql/60-write.sql +1369 -0
  68. package/sql/70-read.sql +245 -0
  69. package/sql/80-auth.sql +827 -0
  70. package/sql/90-time.sql +957 -0
  71. package/sql/95-seed.system.sql +178 -0
  72. package/sql/99-revision.sql +3 -0
  73. package/sql/README.md +56 -0
  74. package/sql/seed.booking.sql +39 -112
  75. package/dist/schema.d.ts +0 -15
  76. package/dist/schema.js +0 -351
  77. package/dist/sessions.d.ts +0 -32
  78. package/dist/sessions.js +0 -114
  79. package/dist/tables.d.ts +0 -105
  80. package/dist/tables.js +0 -248
  81. package/docker/Dockerfile +0 -40
  82. package/docker/start.sh +0 -18
  83. package/scripts/check-docs.mjs +0 -375
  84. package/scripts/gen-api-contract.mjs +0 -226
  85. package/scripts/gen-types.mjs +0 -350
  86. package/scripts/release-notes.mjs +0 -76
  87. package/scripts/schema-sync.mjs +0 -185
  88. package/sql/ddl.sql +0 -600
  89. package/sql/seed.auth.sql +0 -73
package/dist/types.d.ts CHANGED
@@ -1,290 +1,111 @@
1
1
  /**
2
- * letopis: типы.
2
+ * Общие типы letopis 1.0. Файл наполняется по этапам плана (reports/PLAN-LETOPIS-1.0.md).
3
3
  */
4
- /** Строка Entity (актуальная версия), как отдаёт либа. account/owner — NOT NULL. */
5
- export interface Row {
4
+ /** Ревизия движка в базе: штамп в lib/sql/99-revision.sql должен совпадать (этап 1). */
5
+ export declare const DDL_REVISION = 5;
6
+ /** Методы цепочки: терминалы, модификаторы, глаголы записи (этап 4), охранники снесённого API. */
7
+ export declare const CHAIN_METHODS: readonly ["then", "entity", "paths", "rows", "first", "ids", "count", "limit", "offset", "sort", "asOf", "withDeleted", "deep", "exact", "sum", "avg", "min", "max", "countBy", "after", "versions", "create", "update", "upsert", "set", "unset", "add", "remove", "delete", "anonymize", "reclass", "restore", "purge", "alias", "tags", "owner", "forUpdate", "cache", "rekey", "run", "execute", "account"];
8
+ /** Свойства корня db (и транзакции db.begin()). */
9
+ export declare const DB_METHODS: readonly ["as", "begin", "commit", "rollback", "lock", "batch", "watch", "close", "registry", "sql", "load", "verify", "maintain", "reset", "describe", "auth", "acl", "idOf", "refresh", "transaction", "flushSignals", "schema", "token", "tenant", "reloadSchema", "accounts", "credentials", "resources", "rules", "compact", "trimHistory", "rekeyClass", "cache"];
10
+ /** Свойства батча db.batch(name). */
11
+ export declare const BATCH_METHODS: readonly ["discard", "size"];
12
+ /**
13
+ * Зарезервированные имена: Proxy разбирает их раньше классов, поэтому класс с таким именем
14
+ * недостижим как шаг. Установщик подставляет список в функцию базы reserved_names(), которая
15
+ * отклоняет связи с такими именами; совпадение со switch Proxy проверяет check-docs.
16
+ */
17
+ export declare const RESERVED_CLASS_NAMES: readonly string[];
18
+ /** Строка ответа: текущая версия (или версия журнала для asOf, versions, withDeleted). */
19
+ export interface Row<D = Record<string, unknown>, L = Record<string, string | string[]>> {
6
20
  id: string;
7
21
  class: string;
8
- data: Record<string, unknown>;
9
- links: Record<string, string>;
10
- tags: string[];
11
- account: string;
22
+ /** Номер версии — для update(patch, { rev }) (этап 4). */
23
+ rev: number;
24
+ tenant: string;
12
25
  owner: string;
13
- updated: string;
14
- /** true у строк, возвращённых .delete() (цели + каскад) и tombstone-версий в .versions(). */
26
+ links: L;
27
+ data: D;
28
+ tags: string[];
29
+ /** Время версии (ISO, UTC, микросекунды). */
30
+ at: string;
31
+ author: string;
32
+ agent?: string;
33
+ /** Вид операции версии: create, update, delete, reclass, … */
34
+ op: string;
35
+ reason?: string;
36
+ /** Связь со старым или новым id при reclass и rekey. */
37
+ moved?: string;
38
+ /** Надгробие: удалённая сущность (withDeleted) или версия удаления (versions). */
15
39
  $deleted?: true;
16
- /** Глубина узла при .deep()-обходе (1 = прямой ребёнок). */
40
+ /** Глубина узла при deep() (1 — прямой потомок); в превью удаления — уровень каскада (0 — цель). */
17
41
  $depth?: number;
42
+ /** Что сделал upsert (этап 4). */
43
+ $upsert?: 'created' | 'updated' | 'unchanged';
44
+ /** Превью удаления (delete() без confirm): удалится, снимется конец или помешает (restrict). */
45
+ $action?: 'delete' | 'unset' | 'restrict';
46
+ /** purge(): число версий в журнале (превью) или стёртых. */
47
+ $versions?: number;
48
+ /** purge({ confirm: true }): история стёрта. */
49
+ $purged?: true;
18
50
  }
19
- /** Результат run(): вариант пути — узел на каждый шаг цепочки. */
51
+ /** Путь paths(): узел на каждый шаг цепочки. */
20
52
  export type Path = Record<string, Row>;
21
- /**
22
- * Конец связи класса (Schema.links v2). В БД элемент text[]: JSON-объект
23
- * `{"class":"Org","cardinality":1}` / `{"classes":["Service","Complex"],"cardinality":1}`
24
- * (союз ролей); legacy-строка 'Org' — сахар для {classes:['Org']} (старые схемы).
25
- */
26
- export interface LinkEnd {
27
- /** Допустимые классы конца; ровно один из них присутствует в Entity.links. */
28
- classes: string[];
29
- /** Конец может отсутствовать. Default false. */
30
- optional?: boolean;
31
- /** ЗАРЕЗЕРВИРОВАНО (не имплементировано): 0 — безлимит, N — точное число. Default 1. */
32
- cardinality?: number;
33
- }
34
- /**
35
- * Генерация id класса — из attributes.id:
36
- * "uuid" | {type:'uuid'} → v4 (random, дефолт);
37
- * {type:'uuid', generate: 7} → v7 (время в старших битах);
38
- * {type:'uuid', generate: 5, from: ['Slot','Staff']} → v5: детерминированный id из значений
39
- * from — имена ОБЯЗАТЕЛЬНЫХ концов Schema.links (класс или полное имя союза
40
- * 'Service|Complex') и/или скалярных полей data.
41
- */
42
- export interface IdGen {
43
- version: 4 | 5 | 7;
44
- /** Только v5: источники имени (порядок значим). */
45
- from?: string[];
46
- }
47
- /** Класс из таблицы Schema. */
48
- export interface ClassDef {
49
- id: string;
50
- alias: string;
51
- category: 'HUB' | 'LINK';
52
- ancestor: string | null;
53
- ancestors: string[];
54
- /** Все потомки (транзитивно) — считает триггер schema_lineage. */
55
- descendants: string[];
56
- /** Поля data с НАСЛЕДОВАНИЕМ по ancestor-цепочке (потомок поверх предка). */
57
- attributes: Record<string, unknown>;
58
- links: LinkEnd[];
59
- /**
60
- * true — хотя бы один конец объявлен объектом (схема v2): строгая валидация
61
- * (жадный матчинг по порядку, союзы, optional, лишние связи — ошибка).
62
- * false — все концы legacy-строками: старое поведение ('Entity' = полиморф, лишние молчат).
63
- */
64
- strictEnds: boolean;
65
- meta: Record<string, unknown>;
66
- abstract: boolean;
67
- order: number;
68
- /** Как генерить id новой сущности (attributes.id). */
69
- idGen: IdGen;
70
- /** Скомпилированный fastest-validator: true | ошибки. */
71
- check: (data: Record<string, unknown>) => true | {
72
- field: string;
73
- message?: string;
74
- }[];
75
- /** Тип каждого поля attributes — для SQL-кастов в фильтрах. */
76
- fieldTypes: Map<string, FieldType>;
77
- }
78
- export type FieldType = {
79
- kind: 'number';
80
- } | {
81
- kind: 'date';
82
- } | {
83
- kind: 'boolean';
84
- } | {
85
- kind: 'string';
86
- } | {
87
- kind: 'array';
88
- } | {
89
- kind: 'record';
90
- value: FieldType;
91
- } | {
92
- kind: 'object';
93
- props: Map<string, FieldType>;
94
- } | {
95
- kind: 'any';
96
- };
97
- /** Метка операторов фильтра (Symbol — не конфликтует с данными). */
53
+ /** Метка операторов фильтра (символ не пересекается с данными). */
98
54
  export declare const OP: unique symbol;
99
55
  export interface Op {
100
56
  [OP]: string;
101
57
  args: unknown[];
102
58
  }
103
59
  export type Scalar = string | number | boolean | null;
104
- /** Значение фильтра по полю: скаляр (eq), оператор или вложенный record-объект. */
60
+ /** Значение фильтра по полю: скаляр (равенство), список, оператор или вложенный объект. */
105
61
  export type FieldFilter = Scalar | Scalar[] | Op | {
106
- [key: string]: Scalar | Op;
62
+ [key: string]: FieldFilter | undefined;
63
+ } | {
64
+ id: string;
107
65
  };
108
66
  /**
109
- * Фильтр шага:
110
- * - строка / массив строк — id;
111
- * - объект: { поле data: значение (eq) | оператор | вложенный record-путь } + ключ id.
112
- * Колонки tags/account/owner фильтруются модификаторами цепочки .tags()/.account()/.owner().
67
+ * Фильтр шага: id или список id; строка ответа (берётся её id); or(…); объект — поля data
68
+ * любой глубины, роли концов (`{ Org: id }`) и `id`.
113
69
  */
114
70
  export type Filter = string | string[] | Op | {
71
+ id: string;
72
+ class: string;
73
+ } | {
115
74
  [field: string]: FieldFilter | undefined;
116
75
  };
117
- /** Курсор keyset-пагинации: значение поля сортировки + id последней строки страницы. */
76
+ /** Курсор keyset-пагинации: значение поля сортировки и id последней строки страницы. */
118
77
  export interface Cursor {
119
- v: string | number;
78
+ v: string | number | boolean | null;
120
79
  id: string;
121
80
  }
122
- /**
123
- * Операция записи, привязанная к шагу цепочки:
124
- * .create() / .update() / .delete() / .anonymize().
125
- * Терминал исполняет план (все операции + финальное чтение) одной транзакцией.
126
- */
127
- export interface PlanOp {
128
- kind: 'create' | 'update' | 'delete' | 'anonymize' | 'purge';
129
- /** create/update: данные новой версии (deep-merge листьев). */
130
- data?: Record<string, unknown>;
131
- /** anonymize: string-поля под '[erased]'. */
132
- fields?: string[];
133
- /** delete/purge: true — выполнить; без confirm — превью (вернуть кандидатов/замыкание, БД не трогать). */
134
- confirm?: boolean;
135
- /** Снапшот модификаторов на момент вызова операции (limit/sort для поиска целей). */
136
- mods: ChainMods;
137
- }
138
- /** Модификаторы выборки цепочки: .limit() / .offset() / .sort() / .asOf() / .after(). */
81
+ /** Модификаторы выборки цепочки. */
139
82
  export interface ChainMods {
140
83
  limit?: number;
141
84
  offset?: number;
142
- /** 'updated' | 'data.<поле>' (каст по типу поля последнего шага). */
85
+ /** 'at' | 'rev' | 'id' | 'data.<путь>'. */
143
86
  order?: string;
144
87
  desc?: boolean;
145
- /** Чтение «как было на момент T»: ISO-строка или Date. */
88
+ /** Срез «как было на момент T» (ISO). */
146
89
  asOf?: string;
147
- /** Keyset-пагинация от курсора (требует sort). */
148
90
  after?: Cursor;
149
- /** Внутреннее: агрегация терминалов .sum/.avg/.min/.max/.countBy. */
150
- aggFn?: 'sum' | 'avg' | 'min' | 'max' | 'countBy';
151
- aggField?: string;
152
- /** .withDeleted(): последний шаг включает удалённые (tombstone). Снимает ТОЛЬКО фильтр deleted; enforceAccount/enforceAcl действуют. */
91
+ /** Последний шаг включает удалённые. */
153
92
  withDeleted?: boolean;
93
+ /** Чтение с блокировкой строк последнего шага (только в db.begin()). */
94
+ forUpdate?: boolean;
95
+ /** versions({ follow: true }): история и через смену id (reclass, rekey) по колонке moved. */
96
+ follow?: boolean;
97
+ /** Кэш результатов (§2.12): .cache() — срок по умолчанию, .cache({ ttl }) — свой, секунды. */
98
+ cache?: {
99
+ ttl?: number;
100
+ };
154
101
  }
155
- export interface Account {
156
- id: string;
157
- categories: string[];
158
- data: Record<string, unknown>;
159
- meta: Record<string, unknown>;
160
- avatar: string;
161
- enabled: boolean;
162
- created: string;
163
- updated: string;
164
- }
165
- export interface Credential {
166
- id: string;
167
- account: string;
168
- category: string;
169
- identifier: string;
170
- meta: Record<string, unknown>;
171
- confirmed: boolean;
172
- created: string;
173
- updated: string;
174
- deleted: string | null;
175
- }
176
- export interface Resource {
177
- alias: string;
178
- category: string;
179
- pattern: Record<string, unknown> | null;
180
- meta: Record<string, unknown> | null;
181
- }
182
- export interface Rule {
183
- account: string;
184
- resource: string;
185
- permission: string;
186
- weight: number | null;
187
- meta: Record<string, unknown> | null;
188
- enabled: boolean;
189
- }
190
- /** Операция над данными = категория Resource: 'READ' | 'WRITE' | 'DELETE'. */
191
- export type AclOp = 'READ' | 'WRITE' | 'DELETE';
192
- /** Решение ACL: победившее правило (max weight; при равенстве deny; нет правил — deny). */
193
- export interface AclDecision {
194
- allow: boolean;
195
- /** Победившее правило (нет — deny «no matching rule»). */
196
- rule?: Rule;
197
- /**
198
- * Остаточный шаблон строки Entity из pattern победившего allow-ресурса
199
- * (ключи-колонки без class, "$account" уже подставлен) — вливается в SQL
200
- * до сортировки/лимита. Отсутствует = класс целиком.
201
- */
202
- filter?: Record<string, unknown>;
203
- /** Из meta deny-правила: код и сообщение для ответа клиенту. */
204
- code?: number;
205
- message?: string;
206
- }
207
- /** Событие хука onQuery: один SQL-запрос цепочки (чтение или запись). */
102
+ /** Событие хука onQuery: один запрос цепочки. */
208
103
  export interface QueryEvent {
209
- /** Режим чтения или write-операция. */
210
- mode: 'paths' | 'rows' | 'ids' | 'count' | 'versions' | 'agg' | 'insert' | 'delete';
211
- /** Классы шагов цепочки (id из Schema). */
104
+ mode: 'paths' | 'rows' | 'ids' | 'count' | 'versions' | 'agg';
105
+ /** Классы шагов цепочки. */
212
106
  classes: string[];
213
- /** Длительность, мс. */
214
107
  ms: number;
215
- /** Число строк результата. */
216
108
  rows: number;
217
- /** true при ms > slowMs (если slowMs задан). */
109
+ /** ms > slowMs. */
218
110
  slow: boolean;
219
111
  }
220
- export interface ConnectOpts {
221
- /** postgres://user:pass@host:port/db */
222
- dsn: string;
223
- /** PG-схема с таблицами Entity/Schema (напр. 'booking'). */
224
- schema: string;
225
- /** Партиция данных. Default 'entity'. */
226
- partition?: string;
227
- /** Размер пула соединений. Default 10. */
228
- max?: number;
229
- /**
230
- * Жёсткая изоляция арендатора: чтения фильтруются по account, записи пришпилены к нему.
231
- * **Default true** (с 0.20.0). Идентичность берётся у хендла `db.as(account)`; на
232
- * безличном (корневом) хендле чтение/запись при включённой изоляции — ошибка с подсказкой.
233
- * Явный `.account(чужой)` на scoped-хендле — тоже ошибка.
234
- * Выключать (`false`) осмысленно лишь для админских/сервисных подключений,
235
- * которым нужен доступ ко всем арендаторам.
236
- */
237
- enforceAccount?: boolean;
238
- /**
239
- * ACL по Resource/Rule: категории READ/WRITE/DELETE, pattern — шаблон строки Entity
240
- * (колонки + "$account"). Каждый шаг цепочки проверяется на READ, записи — WRITE,
241
- * delete — DELETE (включая классы каскада); предикат победившего правила вливается
242
- * в SQL до сортировки/лимита. Субъект даёт db.as() (энфорсер компилится на scope).
243
- * Deny-by-default.
244
- * **Default false** — остаётся opt-in: включённый по умолчанию deny-by-default без
245
- * настроенных Resource/Rule давал бы пустые выборки. Пока выключен, connect() один раз
246
- * на процесс печатает предупреждение.
247
- * Правила снимаются на connect и компилятся в memo-энфорсер; перечитать без
248
- * реконнекта — db.reloadSchema() (пересобирает и реестр, и ACL-резолвер).
249
- */
250
- enforceAcl?: boolean;
251
- /** Хук на каждый запрос цепочки (метрики, лог). */
252
- onQuery?: (e: QueryEvent) => void;
253
- /** Порог «медленного» запроса, мс: событие получает slow: true; без onQuery — console.warn. */
254
- slowMs?: number;
255
- }
256
- /**
257
- * Имена, которые Proxy цепочки/db разбирает ДО резолва класса, поэтому класс с таким
258
- * id/alias НЕДОСТИЖИМ как шаг под этим именем (`db.Клиент(c).link()` уйдёт в
259
- * migration-ошибку `.link() removed`, а не в класс `link`).
260
- *
261
- * Источник истины — `case`-метки switch'ей в chain.ts; совпадение с этим списком
262
- * проверяет scripts/check-docs.mjs. Используется как гвард: schema.define() отказывает,
263
- * loadRegistry предупреждает.
264
- */
265
- export declare const RESERVED_CLASS_NAMES: readonly string[];
266
- /** Зарезервированные имена класса среди {id, alias}; пусто — конфликта нет. */
267
- export declare function reservedNamesOf(def: {
268
- id: string;
269
- alias?: string;
270
- }): string[];
271
- /**
272
- * Ревизия движка (`lib/sql/ddl.sql`), которую ожидает ЭТА версия либы.
273
- *
274
- * Зачем: `ddl.sql` растёт аддитивно внутри одной версии движка (в 0.19.0, например,
275
- * добавились функции purge/purge_closure/purge_account). Схема, накатанная раньше, их
276
- * НЕ получает — приложение обновляет пакет и падает сырым `PostgresError: function
277
- * "v1.x".purge(...) does not exist` вместо внятного объяснения. `connect()` сравнивает
278
- * ожидаемую ревизию с меткой в схеме (`COMMENT ON SCHEMA` — её ставит последняя строка
279
- * ddl.sql) и один раз на процесс предупреждает, что и как обновить.
280
- *
281
- * Файл идемпотентен, поэтому аддитивные правки доезжают повторным накатом:
282
- * `up({ upgrade: true })` либо `node db/apply.mjs --upgrade` — данные целы.
283
- * Несовместимая правка СТРУКТУРЫ таблиц — это смена version в имени схемы (v1 → v2).
284
- *
285
- * Совпадение константы с маркером `-- DDL_REVISION:` в ddl.sql проверяет
286
- * `scripts/check-docs.mjs` — иначе одно уедет без другого.
287
- */
288
- export declare const DDL_REVISION = 1;
289
- /** Метка ревизии в комментарии схемы: 'letopis ddl_revision=N' → N; иначе null. */
290
- export declare function parseDdlRevision(comment: string | null | undefined): number | null;
package/dist/types.js CHANGED
@@ -1,113 +1,32 @@
1
1
  /**
2
- * letopis: типы.
2
+ * Общие типы letopis 1.0. Файл наполняется по этапам плана (reports/PLAN-LETOPIS-1.0.md).
3
3
  */
4
- //
5
- // FILE: lib/src/types.ts
6
- // VERSION: 1.3.0
7
- // START_MODULE_CONTRACT
8
- // PURPOSE: Общий словарь типов всей библиотеки letopis.
9
- // SCOPE: сущности, пути, определения классов, типы полей, фильтры и операторы, курсоры/план записи/модификаторы цепочки, записи auth/ACL, событие запроса и опции connect + символ OP.
10
- // DEPENDS: none
11
- // LINKS: M-TYPES, V-M-TYPES
12
- // ROLE: TYPES
13
- // MAP_MODE: EXPORTS
14
- // END_MODULE_CONTRACT
15
- //
16
- // START_MODULE_MAP
17
- // Row - строка Entity (актуальная версия): id/class/data/links/tags/account/owner/updated + служебные $deleted/$depth.
18
- // Path - вариант пути run(): по одному узлу Row на каждый шаг цепочки.
19
- // LinkEnd - конец связи класса (Schema.links v2): допустимые классы, optional, cardinality.
20
- // IdGen - способ генерации id класса (версия 4/5/7) + источники from для v5.
21
- // ClassDef - класс из таблицы Schema: категория, наследование, атрибуты, концы связей, idGen, валидатор, fieldTypes.
22
- // FieldType - тип поля attributes для SQL-кастов (number/date/boolean/string/array/record/object/any).
23
- // OP - символьная метка операторов фильтра (Symbol — не конфликтует с данными).
24
- // Op - узел оператора фильтра: { [OP]: имя, args }.
25
- // Scalar - скалярное значение фильтра: string | number | boolean | null.
26
- // FieldFilter - значение фильтра по полю: скаляр (eq) / массив / оператор Op / вложенный record.
27
- // Filter - фильтр шага: id-строка/массив, Op (or) или объект полей data.
28
- // Cursor - курсор keyset-пагинации: значение поля сортировки + id последней строки.
29
- // PlanOp - операция записи шага (create/update/delete/anonymize) + снапшот модификаторов.
30
- // ChainMods - модификаторы выборки цепочки (limit/offset/order/desc/asOf/after + внутренняя агрегация).
31
- // Account - запись аккаунта служебной таблицы auth.
32
- // Credential - учётные данные аккаунта (identifier/category/confirmed/…).
33
- // Resource - ресурс ACL: alias/category/pattern/meta.
34
- // Rule - правило ACL: account/resource/permission/weight/enabled.
35
- // AclOp - операция над данными = категория Resource: 'READ' | 'WRITE' | 'DELETE'.
36
- // AclDecision - решение ACL: allow + победившее правило + остаточный filter + code/message.
37
- // QueryEvent - событие хука onQuery: режим, классы, ms, rows, slow.
38
- // ConnectOpts - опции connect(): dsn/schema/partition/max/enforceAccount/enforceAcl/onQuery/slowMs (account/owner — не здесь, их даёт db.as()).
39
- // RESERVED_CLASS_NAMES - имена, перехватываемые Proxy до резолва класса (шаг недостижим под этим именем).
40
- // reservedNamesOf - зарезервированные имена среди {id, alias} определения класса.
41
- // END_MODULE_MAP
42
- //
43
- // START_CHANGE_SUMMARY
44
- // LAST_CHANGE: [v1.3.0 - BREAKING: из ConnectOpts удалены account/owner (арендатор — свойство
45
- // ВЫЗОВА: db.as(account, { owner })); 'as' добавлен в RESERVED_CLASS_NAMES → 52 имени.
46
- // Ранее: 'link' убран из RESERVED_CLASS_NAMES (охранник цепочки снят, имя снова
47
- // резолвится как класс); enforceAccount задокументирован как Default true]
48
- // END_CHANGE_SUMMARY
49
- //
50
- // START_BLOCK_ENTITY_TYPES
51
- // END_BLOCK_ENTITY_TYPES
52
- //
53
- // START_BLOCK_FILTER_TYPES
54
- /** Метка операторов фильтра (Symbol — не конфликтует с данными). */
55
- export const OP = Symbol('letopis.op');
56
- // END_BLOCK_CONNECT_TYPES
57
- // START_BLOCK_RESERVED_NAMES
58
- /**
59
- * Имена, которые Proxy цепочки/db разбирает ДО резолва класса, поэтому класс с таким
60
- * id/alias НЕДОСТИЖИМ как шаг под этим именем (`db.Клиент(c).link()` уйдёт в
61
- * migration-ошибку `.link() removed`, а не в класс `link`).
62
- *
63
- * Источник истины — `case`-метки switch'ей в chain.ts; совпадение с этим списком
64
- * проверяет scripts/check-docs.mjs. Используется как гвард: schema.define() отказывает,
65
- * loadRegistry предупреждает.
66
- */
67
- export const RESERVED_CLASS_NAMES = [
68
- // все три Proxy: then гасится, чтобы цепочка не выглядела thenable для await
69
- 'then',
70
- // chain-уровень (makeChain handler)
71
- 'entity', 'run', 'execute', 'rows', 'first', 'ids', 'count', 'limit', 'offset', 'sort',
72
- 'asOf', 'withDeleted', 'deep', 'exact', 'sum', 'avg', 'min', 'max', 'countBy', 'after', 'versions',
73
- 'create', 'update', 'set', 'delete', 'anonymize', 'purge', 'alias', 'tags', 'account',
74
- 'owner',
75
- // db-уровень (makeDb handler): switch …
76
- 'as', 'begin', 'commit', 'rollback', 'lock', 'batch', 'watch', 'close', 'reloadSchema',
77
- 'registry', 'sql',
78
- // … и фасады, которые разбираются if'ами ДО switch (служебные таблицы, auth, acl)
79
- 'accounts', 'credentials', 'resources', 'rules', 'schema', 'auth', 'acl',
80
- // batch-уровень (makeBatch handler)
81
- 'discard', 'size',
4
+ /** Ревизия движка в базе: штамп в lib/sql/99-revision.sql должен совпадать (этап 1). */
5
+ export const DDL_REVISION = 5;
6
+ // ---------------------------------------------------------------------------
7
+ // Методы цепочки — единственный источник зарезервированных имён (план, §2.14, этап 3, п. 10)
8
+ // ---------------------------------------------------------------------------
9
+ /** Методы цепочки: терминалы, модификаторы, глаголы записи (этап 4), охранники снесённого API. */
10
+ export const CHAIN_METHODS = [
11
+ 'then', 'entity', 'paths', 'rows', 'first', 'ids', 'count', 'limit', 'offset', 'sort', 'asOf', 'withDeleted',
12
+ 'deep', 'exact', 'sum', 'avg', 'min', 'max', 'countBy', 'after', 'versions', 'create', 'update', 'upsert', 'set',
13
+ 'unset', 'add', 'remove', 'delete', 'anonymize', 'reclass', 'restore', 'purge', 'alias', 'tags', 'owner',
14
+ 'forUpdate', 'cache', 'rekey', 'run', 'execute', 'account',
15
+ ];
16
+ /** Свойства корня db (и транзакции db.begin()). */
17
+ export const DB_METHODS = [
18
+ 'as', 'begin', 'commit', 'rollback', 'lock', 'batch', 'watch', 'close', 'registry', 'sql', 'load', 'verify',
19
+ 'maintain', 'reset', 'describe', 'auth', 'acl', 'idOf', 'refresh', 'transaction', 'flushSignals', 'schema',
20
+ 'token', 'tenant', 'reloadSchema', 'accounts', 'credentials', 'resources', 'rules', 'compact',
21
+ 'trimHistory', 'rekeyClass', 'cache',
82
22
  ];
83
- /** Зарезервированные имена класса среди {id, alias}; пусто — конфликта нет. */
84
- export function reservedNamesOf(def) {
85
- const set = new Set(RESERVED_CLASS_NAMES);
86
- return [def.id, def.alias].filter((n) => !!n && set.has(n));
87
- }
88
- // END_BLOCK_RESERVED_NAMES
89
- // START_BLOCK_DDL_REVISION
23
+ /** Свойства батча db.batch(name). */
24
+ export const BATCH_METHODS = ['discard', 'size'];
90
25
  /**
91
- * Ревизия движка (`lib/sql/ddl.sql`), которую ожидает ЭТА версия либы.
92
- *
93
- * Зачем: `ddl.sql` растёт аддитивно внутри одной версии движка (в 0.19.0, например,
94
- * добавились функции purge/purge_closure/purge_account). Схема, накатанная раньше, их
95
- * НЕ получает — приложение обновляет пакет и падает сырым `PostgresError: function
96
- * "v1.x".purge(...) does not exist` вместо внятного объяснения. `connect()` сравнивает
97
- * ожидаемую ревизию с меткой в схеме (`COMMENT ON SCHEMA` — её ставит последняя строка
98
- * ddl.sql) и один раз на процесс предупреждает, что и как обновить.
99
- *
100
- * Файл идемпотентен, поэтому аддитивные правки доезжают повторным накатом:
101
- * `up({ upgrade: true })` либо `node db/apply.mjs --upgrade` — данные целы.
102
- * Несовместимая правка СТРУКТУРЫ таблиц — это смена version в имени схемы (v1 → v2).
103
- *
104
- * Совпадение константы с маркером `-- DDL_REVISION:` в ddl.sql проверяет
105
- * `scripts/check-docs.mjs` — иначе одно уедет без другого.
26
+ * Зарезервированные имена: Proxy разбирает их раньше классов, поэтому класс с таким именем
27
+ * недостижим как шаг. Установщик подставляет список в функцию базы reserved_names(), которая
28
+ * отклоняет связи с такими именами; совпадение со switch Proxy проверяет check-docs.
106
29
  */
107
- export const DDL_REVISION = 1;
108
- /** Метка ревизии в комментарии схемы: 'letopis ddl_revision=N' → N; иначе null. */
109
- export function parseDdlRevision(comment) {
110
- const m = /letopis ddl_revision=(\d+)/.exec(comment ?? '');
111
- return m ? Number(m[1]) : null;
112
- }
113
- // END_BLOCK_DDL_REVISION
30
+ export const RESERVED_CLASS_NAMES = [...new Set([...CHAIN_METHODS, ...DB_METHODS, ...BATCH_METHODS])];
31
+ /** Метка операторов фильтра (символ не пересекается с данными). */
32
+ export const OP = Symbol.for('letopis.op');