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.
- package/AGENT-CHEATSHEET.md +491 -414
- package/CHANGELOG.md +361 -0
- package/LICENSE +75 -21
- package/LICENSE-COMMERCIAL.md +49 -0
- package/README.md +969 -210
- package/api-contract.json +385 -0
- package/dist/acl.d.ts +7 -2
- package/dist/acl.js +19 -2
- package/dist/admin.d.ts +7 -1
- package/dist/admin.js +16 -9
- package/dist/auth.d.ts +47 -11
- package/dist/auth.js +129 -15
- package/dist/chain.d.ts +52 -25
- package/dist/chain.js +445 -64
- package/dist/cli.js +72 -20
- package/dist/errors.d.ts +23 -6
- package/dist/errors.js +52 -13
- package/dist/index.d.ts +32 -15
- package/dist/index.js +209 -68
- package/dist/internal.d.ts +2 -0
- package/dist/load.d.ts +10 -6
- package/dist/load.js +30 -11
- package/dist/log.d.ts +121 -0
- package/dist/log.js +654 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +34 -16
- package/dist/messages/access.d.ts +10 -0
- package/dist/messages/access.js +13 -0
- package/dist/messages/api.d.ts +76 -0
- package/dist/messages/api.js +109 -7
- package/dist/messages/boot.d.ts +5 -0
- package/dist/messages/boot.js +11 -4
- package/dist/messages/codes.js +4 -4
- package/dist/messages/index.d.ts +5 -2
- package/dist/messages/index.js +5 -2
- package/dist/messages/log.d.ts +232 -0
- package/dist/messages/log.js +105 -0
- package/dist/messages/mcp.js +7 -7
- package/dist/messages/model.d.ts +40 -1
- package/dist/messages/model.js +52 -3
- package/dist/messages/query.d.ts +10 -0
- package/dist/messages/query.js +13 -0
- package/dist/messages/sql-core.d.ts +15 -1
- package/dist/messages/sql-core.js +20 -3
- package/dist/messages/sql-time.d.ts +15 -0
- package/dist/messages/sql-time.js +23 -0
- package/dist/messages/write.d.ts +52 -0
- package/dist/messages/write.js +77 -6
- package/dist/model.d.ts +10 -2
- package/dist/model.js +28 -6
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +6 -0
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +77 -25
- package/dist/sync.d.ts +4 -5
- package/dist/sync.js +53 -4
- package/dist/tx.d.ts +10 -1
- package/dist/tx.js +44 -2
- package/dist/typed.d.ts +18 -14
- package/dist/types.d.ts +91 -8
- package/dist/types.js +8 -3
- package/dist/up.d.ts +8 -5
- package/dist/up.js +71 -23
- package/dist/uuid.d.ts +8 -0
- package/dist/uuid.js +9 -1
- package/dist/validate.js +24 -7
- package/dist/watch.d.ts +5 -0
- package/dist/watch.js +13 -0
- package/dist/write.d.ts +84 -3
- package/dist/write.js +427 -40
- package/llms.txt +38 -17
- package/package.json +12 -6
- package/sql/20-context.sql +11 -3
- package/sql/30-validate.sql +51 -10
- package/sql/40-class.sql +21 -1
- package/sql/60-write.sql +18 -6
- package/sql/80-auth.sql +88 -17
- package/sql/90-time.sql +34 -6
- package/sql/99-revision.sql +5 -1
- package/sql/migrate/0009-restore-rev.tenants-name.sql +10 -0
- package/sql/migrate/0010-impersonation-no-account.sql +5 -0
- package/sql/migrate/0011-key-lock.otp-enabled.sql +7 -0
- 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
|
-
|
|
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',
|
package/dist/messages/write.d.ts
CHANGED
|
@@ -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;
|
package/dist/messages/write.js
CHANGED
|
@@ -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}() на возврате к узлу — он ведёт к
|
|
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: '
|
|
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: '
|
|
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} должен дать ровно
|
|
128
|
+
ru: 'шаг {step} должен дать ровно один объект для конца {cls} (найдено {count})',
|
|
129
129
|
},
|
|
130
130
|
// слот .роль.set() одиночного конца — значение дало несколько сущностей
|
|
131
131
|
'write.slot_single_many': {
|
|
132
132
|
code: 'invalid_query',
|
|
133
|
-
ru: '
|
|
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
|
-
/**
|
|
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
|
-
/** Только для вывода типов: имена классов целей — строгие переходы и
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
package/dist/registry.d.ts
CHANGED
|
@@ -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 сужают цели записи. */
|