letopis 2.0.0 → 3.0.1

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 (83) hide show
  1. package/AGENT-CHEATSHEET.md +491 -414
  2. package/CHANGELOG.md +361 -0
  3. package/LICENSE +75 -21
  4. package/LICENSE-COMMERCIAL.md +49 -0
  5. package/README.md +969 -210
  6. package/api-contract.json +385 -0
  7. package/dist/acl.d.ts +7 -2
  8. package/dist/acl.js +19 -2
  9. package/dist/admin.d.ts +7 -1
  10. package/dist/admin.js +16 -9
  11. package/dist/auth.d.ts +47 -11
  12. package/dist/auth.js +129 -15
  13. package/dist/chain.d.ts +52 -25
  14. package/dist/chain.js +445 -64
  15. package/dist/cli.js +72 -20
  16. package/dist/errors.d.ts +23 -6
  17. package/dist/errors.js +52 -13
  18. package/dist/index.d.ts +32 -15
  19. package/dist/index.js +209 -68
  20. package/dist/internal.d.ts +2 -0
  21. package/dist/load.d.ts +10 -6
  22. package/dist/load.js +30 -11
  23. package/dist/log.d.ts +121 -0
  24. package/dist/log.js +654 -0
  25. package/dist/mcp.d.ts +1 -1
  26. package/dist/mcp.js +34 -16
  27. package/dist/messages/access.d.ts +10 -0
  28. package/dist/messages/access.js +13 -0
  29. package/dist/messages/api.d.ts +76 -0
  30. package/dist/messages/api.js +109 -7
  31. package/dist/messages/boot.d.ts +5 -0
  32. package/dist/messages/boot.js +11 -4
  33. package/dist/messages/codes.js +4 -4
  34. package/dist/messages/index.d.ts +5 -2
  35. package/dist/messages/index.js +5 -2
  36. package/dist/messages/log.d.ts +232 -0
  37. package/dist/messages/log.js +105 -0
  38. package/dist/messages/mcp.js +7 -7
  39. package/dist/messages/model.d.ts +40 -1
  40. package/dist/messages/model.js +52 -3
  41. package/dist/messages/query.d.ts +10 -0
  42. package/dist/messages/query.js +13 -0
  43. package/dist/messages/sql-core.d.ts +15 -1
  44. package/dist/messages/sql-core.js +20 -3
  45. package/dist/messages/sql-time.d.ts +15 -0
  46. package/dist/messages/sql-time.js +23 -0
  47. package/dist/messages/write.d.ts +52 -0
  48. package/dist/messages/write.js +77 -6
  49. package/dist/model.d.ts +10 -2
  50. package/dist/model.js +28 -6
  51. package/dist/registry.d.ts +6 -0
  52. package/dist/registry.js +6 -0
  53. package/dist/sql.d.ts +2 -0
  54. package/dist/sql.js +77 -25
  55. package/dist/sync.d.ts +4 -5
  56. package/dist/sync.js +53 -4
  57. package/dist/tx.d.ts +10 -1
  58. package/dist/tx.js +44 -2
  59. package/dist/typed.d.ts +18 -14
  60. package/dist/types.d.ts +91 -8
  61. package/dist/types.js +8 -3
  62. package/dist/up.d.ts +8 -5
  63. package/dist/up.js +71 -23
  64. package/dist/uuid.d.ts +8 -0
  65. package/dist/uuid.js +9 -1
  66. package/dist/validate.js +24 -7
  67. package/dist/watch.d.ts +5 -0
  68. package/dist/watch.js +13 -0
  69. package/dist/write.d.ts +84 -3
  70. package/dist/write.js +427 -40
  71. package/llms.txt +38 -17
  72. package/package.json +12 -6
  73. package/sql/20-context.sql +11 -3
  74. package/sql/30-validate.sql +51 -10
  75. package/sql/40-class.sql +21 -1
  76. package/sql/60-write.sql +18 -6
  77. package/sql/80-auth.sql +88 -17
  78. package/sql/90-time.sql +34 -6
  79. package/sql/99-revision.sql +5 -1
  80. package/sql/migrate/0009-restore-rev.tenants-name.sql +10 -0
  81. package/sql/migrate/0010-impersonation-no-account.sql +5 -0
  82. package/sql/migrate/0011-key-lock.otp-enabled.sql +7 -0
  83. package/sql/migrate/README.md +5 -1
@@ -14,6 +14,12 @@ export const sqlCore = {
14
14
  code: 'acl_denied',
15
15
  ru: 'нет права auth.impersonate',
16
16
  },
17
+ // 20-context.sql, ctx(): имперсонация аккаунта, которого нет (не заводили или удалён); id — значение letopis.account
18
+ 'sql-core.impersonation_no_account': {
19
+ code: 'target_not_found',
20
+ ru: 'аккаунт {id} не найден',
21
+ hint: 'id аккаунта — из ответа verifyPassword(), createTenant() или db.auth.tenants(); удалённый аккаунт через as() не вернуть',
22
+ },
17
23
  // 20-context.sql, ctx(): имперсонация System, сервиса или системного администратора не системным администратором
18
24
  'sql-core.impersonation_denied': {
19
25
  code: 'acl_denied',
@@ -28,6 +34,7 @@ export const sqlCore = {
28
34
  'sql-core.bad_api_key': {
29
35
  code: 'no_session',
30
36
  ru: 'неверный ключ',
37
+ hint: 'ключ сервиса перевыпускает up({ fresh: true }): возьмите новый serviceKey из ответа up() или задайте свой — up({ serviceKey })',
31
38
  },
32
39
  // 30-validate.sql, jp_pattern(): pattern JSON Schema не переводится в регулярное выражение PostgreSQL
33
40
  'sql-core.pattern_trailing_backslash': {
@@ -105,9 +112,14 @@ export const sqlCore = {
105
112
  ru: 'minProperties внутри anyOf не поддерживается (узел {node})',
106
113
  },
107
114
  // 30-validate.sql, validate_data(): сообщения нарушений issues[].message — тексты не для ошибки (lt_text)
108
- 'sql-core.issue_string_length': {
115
+ // minLength, maxLength: limit — граница из описания (длина в символах)
116
+ 'sql-core.issue_string_too_short': {
117
+ code: 'invalid_data',
118
+ ru: 'длина строки меньше {limit}',
119
+ },
120
+ 'sql-core.issue_string_too_long': {
109
121
  code: 'invalid_data',
110
- ru: 'длина строки вне границ',
122
+ ru: 'длина строки больше {limit}',
111
123
  },
112
124
  'sql-core.issue_unique_items': {
113
125
  code: 'invalid_data',
@@ -123,7 +135,12 @@ export const sqlCore = {
123
135
  },
124
136
  'sql-core.issue_enum': {
125
137
  code: 'invalid_data',
126
- ru: 'значение не из перечисления',
138
+ ru: 'значение не из перечисления: допустимо {values}',
139
+ },
140
+ // лишний ключ объекта (additionalProperties: false); keys — имена лишних ключей через запятую
141
+ 'sql-core.issue_additional': {
142
+ code: 'invalid_data',
143
+ ru: 'лишний ключ {keys}',
127
144
  },
128
145
  'sql-core.issue_rule': {
129
146
  code: 'invalid_data',
@@ -15,6 +15,21 @@ export declare const sqlTime: {
15
15
  code: "not_deleted";
16
16
  ru: string;
17
17
  };
18
+ 'sql-time.restore_id_taken': {
19
+ code: "not_deleted";
20
+ ru: string;
21
+ hint: string;
22
+ };
23
+ 'sql-time.restore_already': {
24
+ code: "not_deleted";
25
+ ru: string;
26
+ hint: string;
27
+ };
28
+ 'sql-time.restore_stale': {
29
+ code: "not_deleted";
30
+ ru: string;
31
+ hint: string;
32
+ };
18
33
  'sql-time.restore_class_row': {
19
34
  code: "invalid_class";
20
35
  ru: string;
@@ -19,6 +19,29 @@ export const sqlTime = {
19
19
  code: 'not_deleted',
20
20
  ru: 'объект не удалён: restore — только для удалённых',
21
21
  },
22
+ // 90-time.sql, restore(): живой объект, за последним надгробием которого — create: id занял новый объект с тем же
23
+ // ключом; detail — { id, rev }
24
+ 'sql-time.restore_id_taken': {
25
+ code: 'not_deleted',
26
+ ru: 'id {id} занят новым объектом с тем же ключом — create по ключу удалённого берёт его id, восстанавливать нечего',
27
+ hint: 'объект с этим ключом уже живёт: прочитайте его; перед create() по ключу удалённого проверьте db.Класс({ ключ }).withDeleted()',
28
+ },
29
+ // 90-time.sql, restore(): живой объект, за последним надгробием которого — restore: уже восстановлен (другой запрос
30
+ // успел первым); detail — { id, rev }; библиотека отдаёт его как write.restore_already
31
+ 'sql-time.restore_already': {
32
+ code: 'not_deleted',
33
+ ru: 'restore: {id} уже восстановлен — объект живой, восстанавливать нечего',
34
+ hint: 'его вернул другой запрос: прочитайте объект; восстанавливать снова не нужно',
35
+ },
36
+ // 90-time.sql, restore(p_id, p_rev): последняя версия id не p_rev, и это не «уже восстановлен» и не «id занят» (удалён
37
+ // снова, не удалялся, гонка). detail — { id, rev, expected, after, deleted, live }: after — вид записи журнала сразу
38
+ // за p_rev (в тексте «—», если её нет). Код базы — not_deleted (conflict в каталоге базы нет); библиотека отдаёт его
39
+ // как write.restore_stale (conflict)
40
+ 'sql-time.restore_stale': {
41
+ code: 'not_deleted',
42
+ ru: 'restore: {id} уже не тот, что удаляли — последняя версия {rev}, ожидалась {expected}; после того удаления — {after}',
43
+ hint: 'объект менялся после показанного удаления (detail.after, detail.deleted, detail.live): вещь уже у другого — сообщить пользователю, а не восстанавливать',
44
+ },
22
45
  // 90-time.sql, restore()
23
46
  'sql-time.restore_class_row': {
24
47
  code: 'invalid_class',
@@ -140,6 +140,58 @@ export declare const write: {
140
140
  code: "not_deleted";
141
141
  ru: string;
142
142
  };
143
+ 'write.restore_id_taken': {
144
+ code: "not_deleted";
145
+ ru: string;
146
+ hint: string;
147
+ };
148
+ 'write.restore_already': {
149
+ code: "not_deleted";
150
+ ru: string;
151
+ hint: string;
152
+ };
153
+ 'write.restore_stale': {
154
+ code: "conflict";
155
+ ru: string;
156
+ hint: string;
157
+ };
158
+ 'write.partial_without_expect': {
159
+ code: "invalid_query";
160
+ ru: string;
161
+ };
162
+ 'write.bad_expect': {
163
+ code: "invalid_query";
164
+ ru: string;
165
+ };
166
+ 'write.expect_without_confirm': {
167
+ code: "invalid_query";
168
+ ru: string;
169
+ };
170
+ 'write.preview_changed': {
171
+ code: "conflict";
172
+ ru: string;
173
+ hint: string;
174
+ };
175
+ 'write.delete_restricted': {
176
+ code: "delete_restricted";
177
+ ru: string;
178
+ hint: string;
179
+ };
180
+ 'write.link_in_data': {
181
+ code: "invalid_data";
182
+ ru: string;
183
+ hint: string;
184
+ };
185
+ 'write.confirm_without_expect': {
186
+ code: "invalid_query";
187
+ ru: string;
188
+ hint: string;
189
+ };
190
+ 'write.restore_without_expect': {
191
+ code: "invalid_query";
192
+ ru: string;
193
+ hint: string;
194
+ };
143
195
  'write.commit_unknown': {
144
196
  code: "commit_unknown";
145
197
  ru: string;
@@ -62,7 +62,7 @@ export const write = {
62
62
  // create() или upsert() на возврате к узлу пути
63
63
  'write.create_on_pivot': {
64
64
  code: 'invalid_query',
65
- ru: '{verb}() на возврате к узлу — он ведёт к существующей сущности, используйте update()',
65
+ ru: '{verb}() на возврате к узлу — он ведёт к существующему объекту, используйте update()',
66
66
  },
67
67
  // create() или upsert() после шага с фильтром, а не с id
68
68
  'write.create_with_filter': {
@@ -100,7 +100,7 @@ export const write = {
100
100
  // слот после глагола, который слотов не принимает
101
101
  'write.slot_bad_verb': {
102
102
  code: 'invalid_query',
103
- ru: 'слот .{role} после {verb}() — слоты у create(), update() и upsert()',
103
+ ru: 'шаг роли .{role} после {verb}() — шаги ролей бывают только у create(), update() и upsert()',
104
104
  },
105
105
  // write.ts:slotRole: имя слота — не роль записываемого класса и не класс реестра
106
106
  'write.slot_unknown': {
@@ -115,22 +115,22 @@ export const write = {
115
115
  // write.ts:slotRole: класс слота принимают несколько ролей
116
116
  'write.slot_ambiguous': {
117
117
  code: 'invalid_query',
118
- ru: 'слот {name} у класса {cls} неоднозначен: роли {roles} — назовите роль',
118
+ ru: 'шаг {name} у класса {cls} неоднозначен: роли {roles} — назовите роль',
119
119
  },
120
120
  // конец из пути: шаг подходит нескольким ролям
121
121
  'write.end_ambiguous': {
122
122
  code: 'invalid_query',
123
- ru: 'конец класса {cls} для шага {step} неоднозначен: роли {roles} — назовите шаг ролью или задайте слот',
123
+ ru: 'конец класса {cls} для шага {step} неоднозначен: роли {roles} — назовите шаг ролью или задайте конец шагом роли после глагола',
124
124
  },
125
125
  // конец из пути: шаг дал не одну сущность; count — сколько найдено
126
126
  'write.end_not_single': {
127
127
  code: 'invalid_query',
128
- ru: 'шаг {step} должен дать ровно одну сущность для конца {cls} (найдено {count})',
128
+ ru: 'шаг {step} должен дать ровно один объект для конца {cls} (найдено {count})',
129
129
  },
130
130
  // слот .роль.set() одиночного конца — значение дало несколько сущностей
131
131
  'write.slot_single_many': {
132
132
  code: 'invalid_query',
133
- ru: 'слот .{role}.set(): конец одиночный, а значение дало {count} сущностей',
133
+ ru: 'шаг роли .{role}.set(): конец одиночный, а значение дало объектов: {count}',
134
134
  },
135
135
  // write.ts:existsError: create() — объект уже есть, id не вычислить
136
136
  'write.exists': {
@@ -177,6 +177,77 @@ export const write = {
177
177
  code: 'not_deleted',
178
178
  ru: 'объект не удалён: {verb}() — только для удалённых',
179
179
  },
180
+ // restore() живого объекта, который уже удаляли: за последним надгробием — create (id занял новый объект с тем же
181
+ // ключом); ids — такие id; detail.ids, с expect — ещё detail.stale (все цели, которые не восстановить, с why)
182
+ 'write.restore_id_taken': {
183
+ code: 'not_deleted',
184
+ ru: '{verb}(): id {ids} занят новым объектом с тем же ключом — create() по ключу удалённого берёт его id, восстанавливать нечего; ничего не восстановлено',
185
+ hint: 'объект с этим ключом уже живёт: прочитайте его (db.Класс(id).first()); перед create() по ключу удалённого проверьте db.Класс({ ключ }).withDeleted(); восстановить остальное — restore({ expect, partial: true })',
186
+ },
187
+ // restore() живого объекта, который уже восстановили: за последним надгробием — restore (второй раунд 2.1, A2: другой
188
+ // запрос успел первым); ids — такие id; detail.ids, с expect — ещё detail.stale
189
+ 'write.restore_already': {
190
+ code: 'not_deleted',
191
+ ru: '{verb}(): {ids} уже восстановлены — объекты живые, восстанавливать нечего; ничего не восстановлено',
192
+ hint: 'их вернул другой запрос: прочитайте их (db.Класс(id).first()); восстановить остальное — restore({ expect, partial: true })',
193
+ },
194
+ // restore({ expect }): последняя версия id не та, что в expect, и это не «уже восстановлено» и не «id занят» — удалён
195
+ // снова, не удалялся, гонка; ничего не восстановлено. ids — такие id; detail.ids — они же, detail.stale — все цели,
196
+ // которые не восстановить: { id, rev, expected, after, deleted, live, why }
197
+ 'write.restore_stale': {
198
+ code: 'conflict',
199
+ ru: 'restore({ expect }): {ids} уже не те, что удаляли — после этого удаления их меняли (что было — err.detail.stale: after, deleted, live); ничего не восстановлено',
200
+ hint: 'надгробия — из ответа delete() или withDeleted(), их { id, rev } — в expect; расхождение значит, что вещь уже у другого — сообщить пользователю, а не восстанавливать; восстановить то, что можно, — restore({ expect, partial: true })',
201
+ },
202
+ // restore({ partial: true }) без expect: пропускать не по чему
203
+ 'write.partial_without_expect': {
204
+ code: 'invalid_query',
205
+ ru: 'restore({ partial: true }) — только с { expect }: пропуск считается по показанным надгробиям',
206
+ },
207
+ // restore({ expect }), delete({ expect }): expect — не список строк { id, rev }; verb — глагол
208
+ 'write.bad_expect': {
209
+ code: 'invalid_query',
210
+ ru: '{verb}({ expect }): expect — список строк { id, rev } (строки ответа; у delete — и строки-сводки предпросмотра как есть)',
211
+ },
212
+ // delete({ expect }) без confirm: превью сравнивать не с чем
213
+ 'write.expect_without_confirm': {
214
+ code: 'invalid_query',
215
+ ru: 'delete({ expect }) — только с { confirm: true }: без него delete() и есть предпросмотр',
216
+ },
217
+ // delete({ confirm: true, expect }): превью в транзакции удаления не совпало с показанным; detail.preview — новое превью
218
+ 'write.preview_changed': {
219
+ code: 'conflict',
220
+ ru: 'delete({ confirm: true, expect }): предпросмотр изменился с тех пор, как его показали — ничего не удалено',
221
+ hint: 'показать пользователю новый предпросмотр (err.detail.preview) и удалить с ним: delete({ confirm: true, expect: err.detail.preview })',
222
+ },
223
+ // write.ts, checkRestrict (3.0.1): сверенный предпросмотр delete({ confirm: true, expect }) содержит мешающие строки
224
+ // (restrict) — видимые или сводку скрытых правами; отказ до удаления. visible — число видимых, others — скрытых;
225
+ // detail — { rows: [{ id, class }], others }, как у отказа триггера (sql-write.delete_restricted)
226
+ 'write.delete_restricted': {
227
+ code: 'delete_restricted',
228
+ ru: 'удалению мешают ссылающиеся строки: видимых {visible}, скрытых правами {others} — ничего не удалено',
229
+ hint: 'показать пользователю мешающие строки (err.detail.rows); скрытые правами (err.detail.others) видит только их владелец — обратиться к нему; удалить или перевести мешающие строки (или сменить у их конца onDelete) и заново показать предпросмотр delete()',
230
+ },
231
+ // write.ts, checkFullData и checkPatchEnds (3.0, вместо api.validation_failed_end): лишний ключ данных — конец класса
232
+ // (имя роли или класса на конце); keys — такие ключи, role — роль первого из них. Нарушения ValidationError дописывает
233
+ // к тексту сам
234
+ 'write.link_in_data': {
235
+ code: 'invalid_data',
236
+ ru: '{verb}() у {cls}: {keys} — связь, а не поле данных; конец задают путь цепочки или шаг роли после глагола: .{role}.set(id)',
237
+ hint: 'конец (связь) — не поле данных: путь цепочки (db.Владелец(id).Класс().create(…)) или шаг роли после глагола: db.Класс.create({ … }).Роль.set(id); у конца-списка — .Роль.add(id) / .Роль.remove(id), снять — .Роль.unset()',
238
+ },
239
+ // 3.0: delete({ confirm: true }) без expect и без all — удаление не сверено с показанным превью
240
+ 'write.confirm_without_expect': {
241
+ code: 'invalid_query',
242
+ ru: 'delete({ confirm: true }) без { expect }: удаление не сверено с предпросмотром, который видел пользователь — ничего не удалено',
243
+ hint: 'передайте expect из предпросмотра: const p = await db.Класс(id).delete().rows(); показать p пользователю; await db.Класс(id).delete({ confirm: true, expect: p }).rows(). Осознанно удалить текущий состав без сверки — delete({ confirm: true, all: true })',
244
+ },
245
+ // 3.0: restore() без expect и без all — и для явных id: последнее надгробие могло смениться с тех пор, как его видели
246
+ 'write.restore_without_expect': {
247
+ code: 'invalid_query',
248
+ ru: 'restore() без { expect }: восстановление не сверено с надгробиями, которые видел пользователь — ничего не восстановлено',
249
+ hint: 'передайте expect — надгробия из ответа delete() или из db.Класс(id).withDeleted().rows(): restore({ expect }); восстановить то, что можно, — restore({ expect, partial: true }); осознанно вернуть все удалённые цели шага без сверки — restore({ all: true })',
250
+ },
180
251
  // tx.ts — tx.ts:commitUnknown: связь оборвалась во время commit
181
252
  'write.commit_unknown': {
182
253
  code: 'commit_unknown',
package/dist/model.d.ts CHANGED
@@ -29,7 +29,11 @@ type TargetName<T> = T extends string ? T : T extends {
29
29
  type MinRequired<O> = O extends {
30
30
  min: infer N;
31
31
  } ? (N extends 0 | undefined ? false : number extends N ? boolean : true) : false;
32
- /** Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. `T` — имена классов целей (только для типов). */
32
+ /**
33
+ * Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. Параметры типа: `Many` — конец-список, `Required` —
34
+ * обязателен, `T` — имена классов целей (только для типов). `min`, `max`, `onDeleteRule`, `metaData` необязательны:
35
+ * не заданы — `undefined`.
36
+ */
33
37
  export declare class End<Many extends boolean = boolean, Required extends boolean = boolean, T extends string = string> {
34
38
  readonly targets: readonly Target[];
35
39
  readonly many: Many;
@@ -38,7 +42,7 @@ export declare class End<Many extends boolean = boolean, Required extends boolea
38
42
  readonly max?: number;
39
43
  readonly onDeleteRule?: OnDelete;
40
44
  readonly metaData?: Record<string, unknown>;
41
- /** Только для вывода типов: имена классов целей — строгие переходы и слоты цепочек (план 2.0, этап 4, п. 1). */
45
+ /** Только для вывода типов: имена классов целей — строгие переходы и шаги ролей цепочек. */
42
46
  readonly __targets?: T;
43
47
  constructor(targets: readonly Target[], opts: {
44
48
  many: Many;
@@ -210,7 +214,11 @@ type ModelOf<N extends string, D, S extends z.ZodRawShape> = Model<N, DataOf<Ext
210
214
  */
211
215
  export declare function hub<N extends string, S extends z.ZodRawShape, const O extends ClassOpts>(name: N, shape: S, opts: O): ModelOf<N, O, S>;
212
216
  export declare function hub<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D): ModelOf<N, D, ShapeOf<D['fields']>>;
217
+ /** Короткая форма без опций: hub('Имя', { поле: z… }) — поля класса (полная форма { fields: z.object(…) } — выше). */
218
+ export declare function hub<N extends string, S extends z.ZodRawShape>(name: N, shape: S): ModelOf<N, {}, S>;
213
219
  /** Связь — отношение между объектами со своими данными (бронь, навык). */
214
220
  export declare function link<N extends string, S extends z.ZodRawShape, const O extends ClassOpts>(name: N, shape: S, opts: O): ModelOf<N, O, S>;
215
221
  export declare function link<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D): ModelOf<N, D, ShapeOf<D['fields']>>;
222
+ /** Короткая форма без опций: link('Имя', { поле: z… }) — поля класса (полная форма { fields: z.object(…) } — выше). */
223
+ export declare function link<N extends string, S extends z.ZodRawShape>(name: N, shape: S): ModelOf<N, {}, S>;
216
224
  export { normalizeSchema, translatePattern, type JsonSchema } from './jsonschema.js';
package/dist/model.js CHANGED
@@ -14,7 +14,11 @@ import { z } from 'zod';
14
14
  import { lerr } from './errors.js';
15
15
  import { normalizeSchema } from './jsonschema.js';
16
16
  import { RESERVED_CLASS_NAMES } from './types.js';
17
- /** Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. `T` — имена классов целей (только для типов). */
17
+ /**
18
+ * Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. Параметры типа: `Many` — конец-список, `Required` —
19
+ * обязателен, `T` — имена классов целей (только для типов). `min`, `max`, `onDeleteRule`, `metaData` необязательны:
20
+ * не заданы — `undefined`.
21
+ */
18
22
  export class End {
19
23
  targets;
20
24
  many;
@@ -73,14 +77,17 @@ const DEF_KEYS = new Set(['alias', 'extends', 'abstract', 'ends', 'fields', 'key
73
77
  function isZod(v) {
74
78
  return typeof v === 'object' && v !== null && '_zod' in v;
75
79
  }
76
- /** Пути правил refine/superRefine в схеме Zod — их нет в JSON Schema. */
80
+ /**
81
+ * Пути правил Zod, которых нет в JSON Schema: refine/superRefine (`refine`) и преобразования — `overwrite` (`.trim()`,
82
+ * `.toLowerCase()`, `.toUpperCase()`, `.normalize()` …) и `transform` (`.transform()`, `z.preprocess()`, `.pipe()`).
83
+ * Преобразования база и валидатор letopis не исполняют: их syncModels отклоняет (план 2.1, A11).
84
+ */
77
85
  function collectLost(schema, path, out) {
78
86
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
87
  const def = schema._zod.def;
80
- for (const c of def.checks ?? []) {
81
- if (c?._zod?.def?.check === 'custom')
82
- out.push({ path, kind: 'refine' });
83
- }
88
+ collectChecks(schema, path, out);
89
+ if (def.type === 'transform')
90
+ out.push({ path, kind: 'transform' });
84
91
  if (def.type === 'object')
85
92
  for (const [k, v] of Object.entries(def.shape))
86
93
  collectLost(v, `${path}.${k}`, out);
@@ -95,6 +102,17 @@ function collectLost(schema, path, out) {
95
102
  if (def.innerType)
96
103
  collectLost(def.innerType, path, out);
97
104
  }
105
+ /** Проверки самой схемы (без вложенных): refine/superRefine/check — `refine`, `overwrite` — преобразование. */
106
+ function collectChecks(schema, path, out) {
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ for (const c of schema._zod.def.checks ?? []) {
109
+ const check = c?._zod?.def?.check;
110
+ if (check === 'custom')
111
+ out.push({ path, kind: 'refine' });
112
+ else if (check === 'overwrite')
113
+ out.push({ path, kind: 'overwrite' });
114
+ }
115
+ }
98
116
  function makeModel(kind, name, defArg, optsArg) {
99
117
  if (!NAME.test(name))
100
118
  throw lerr('model.class_name_invalid', { name });
@@ -222,6 +240,10 @@ function makeModel(kind, name, defArg, optsArg) {
222
240
  },
223
241
  lostRules() {
224
242
  const out = [];
243
+ // проверки z.object(…).refine/superRefine/check — на уровне объекта (между полями): в описание класса идёт
244
+ // только shape, поэтому они тоже потеряны — путь — имя класса
245
+ if (fields instanceof z.ZodObject)
246
+ collectChecks(fields, name, out);
225
247
  for (const [k, v] of Object.entries(ownShape))
226
248
  collectLost(v, `${name}.${k}`, out);
227
249
  return out;
@@ -41,6 +41,12 @@ interface ClassRow {
41
41
  resolved: Record<string, unknown>;
42
42
  };
43
43
  }
44
+ /**
45
+ * Реестр классов сессии — `db.registry`: описания классов арендатора и системных классов, как их видит подключение.
46
+ * Методы: `has(имя)` — есть ли класс или алиас; `find(имя)` — описание `ClassInfo` или `undefined`; `resolve(имя)` — то же,
47
+ * а неизвестное имя — `invalid_class`; `all()` — все классы; `family(имя)` — класс и его потомки; `leafType(класс, путь)` —
48
+ * тип поля по пути в данных; `load(строки)` — заменить содержимое (его зовёт само подключение, `db.refresh()`).
49
+ */
44
50
  export declare class Registry {
45
51
  private byName;
46
52
  private byAlias;
package/dist/registry.js CHANGED
@@ -5,6 +5,12 @@
5
5
  */
6
6
  import { lerr } from './errors.js';
7
7
  import { compileValidator } from './validate.js';
8
+ /**
9
+ * Реестр классов сессии — `db.registry`: описания классов арендатора и системных классов, как их видит подключение.
10
+ * Методы: `has(имя)` — есть ли класс или алиас; `find(имя)` — описание `ClassInfo` или `undefined`; `resolve(имя)` — то же,
11
+ * а неизвестное имя — `invalid_class`; `all()` — все классы; `family(имя)` — класс и его потомки; `leafType(класс, путь)` —
12
+ * тип поля по пути в данных; `load(строки)` — заменить содержимое (его зовёт само подключение, `db.refresh()`).
13
+ */
8
14
  export class Registry {
9
15
  byName = new Map();
10
16
  byAlias = new Map();
package/dist/sql.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface Step {
18
18
  tagsFilter?: unknown;
19
19
  ownerFilter?: string;
20
20
  deepMax?: number;
21
+ /** withDeleted(): узел шага — живые и надгробия удалённых (модификатор шага, план 2.1, A9). */
22
+ withDeleted?: true;
21
23
  /** Звено плана записи (этап 4): глагол на этом шаге; построитель чтения его не видит. */
22
24
  op?: import('./write.js').PlanOp;
23
25
  /** Модификаторы цепочки на момент глагола: limit и sort сужают цели записи. */