letopis 1.4.2 → 3.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 (123) hide show
  1. package/AGENT-CHEATSHEET.md +516 -398
  2. package/CHANGELOG.md +739 -0
  3. package/LICENSE +75 -21
  4. package/LICENSE-COMMERCIAL.md +49 -0
  5. package/README.md +1312 -1190
  6. package/api-contract.json +381 -0
  7. package/dist/acl.d.ts +81 -1
  8. package/dist/acl.js +281 -13
  9. package/dist/admin.d.ts +11 -141
  10. package/dist/admin.js +28 -186
  11. package/dist/auth.d.ts +20 -5
  12. package/dist/auth.js +80 -21
  13. package/dist/cache.d.ts +8 -1
  14. package/dist/cache.js +30 -8
  15. package/dist/chain.d.ts +46 -48
  16. package/dist/chain.js +556 -223
  17. package/dist/cli.js +73 -96
  18. package/dist/demo/booking.d.ts +352 -36
  19. package/dist/describe.d.ts +47 -0
  20. package/dist/describe.js +195 -0
  21. package/dist/errors.d.ts +77 -13
  22. package/dist/errors.js +231 -30
  23. package/dist/index.d.ts +62 -74
  24. package/dist/index.js +312 -155
  25. package/dist/internal.d.ts +7 -0
  26. package/dist/jsonschema.js +18 -17
  27. package/dist/load.d.ts +29 -17
  28. package/dist/load.js +235 -190
  29. package/dist/log.d.ts +121 -0
  30. package/dist/log.js +633 -0
  31. package/dist/mcp.d.ts +5 -7
  32. package/dist/mcp.js +194 -225
  33. package/dist/messages/access.d.ts +90 -0
  34. package/dist/messages/access.js +113 -0
  35. package/dist/messages/api.d.ts +237 -0
  36. package/dist/messages/api.js +304 -0
  37. package/dist/messages/boot.d.ts +201 -0
  38. package/dist/messages/boot.js +256 -0
  39. package/dist/messages/codes.d.ts +172 -0
  40. package/dist/messages/codes.js +173 -0
  41. package/dist/messages/index.d.ts +98 -0
  42. package/dist/messages/index.js +121 -0
  43. package/dist/messages/log.d.ts +229 -0
  44. package/dist/messages/log.js +102 -0
  45. package/dist/messages/mcp.d.ts +306 -0
  46. package/dist/messages/mcp.js +387 -0
  47. package/dist/messages/model.d.ts +256 -0
  48. package/dist/messages/model.js +314 -0
  49. package/dist/messages/query.d.ts +141 -0
  50. package/dist/messages/query.js +171 -0
  51. package/dist/messages/sql-access.d.ts +70 -0
  52. package/dist/messages/sql-access.js +87 -0
  53. package/dist/messages/sql-core.d.ts +164 -0
  54. package/dist/messages/sql-core.js +198 -0
  55. package/dist/messages/sql-time.d.ts +86 -0
  56. package/dist/messages/sql-time.js +111 -0
  57. package/dist/messages/sql-write.d.ts +246 -0
  58. package/dist/messages/sql-write.js +270 -0
  59. package/dist/messages/time.d.ts +23 -0
  60. package/dist/messages/time.js +29 -0
  61. package/dist/messages/types.d.ts +27 -0
  62. package/dist/messages/types.js +5 -0
  63. package/dist/messages/write.d.ts +452 -0
  64. package/dist/messages/write.js +572 -0
  65. package/dist/model.d.ts +77 -60
  66. package/dist/model.js +43 -34
  67. package/dist/ops.d.ts +35 -0
  68. package/dist/ops.js +101 -0
  69. package/dist/pglite.d.ts +0 -9
  70. package/dist/pglite.js +3 -1
  71. package/dist/registry.d.ts +3 -19
  72. package/dist/registry.js +3 -16
  73. package/dist/signals.d.ts +1 -5
  74. package/dist/signals.js +1 -5
  75. package/dist/sql.d.ts +9 -1
  76. package/dist/sql.js +175 -107
  77. package/dist/sync.d.ts +10 -4
  78. package/dist/sync.js +90 -17
  79. package/dist/tx.d.ts +21 -2
  80. package/dist/tx.js +76 -13
  81. package/dist/typed.d.ts +252 -53
  82. package/dist/types.d.ts +93 -90
  83. package/dist/types.js +17 -14
  84. package/dist/up.d.ts +56 -35
  85. package/dist/up.js +229 -83
  86. package/dist/uuid.d.ts +8 -0
  87. package/dist/uuid.js +14 -6
  88. package/dist/validate.d.ts +13 -1
  89. package/dist/validate.js +82 -33
  90. package/dist/watch.d.ts +10 -16
  91. package/dist/watch.js +36 -77
  92. package/dist/write.d.ts +87 -17
  93. package/dist/write.js +441 -206
  94. package/llms.txt +48 -31
  95. package/package.json +13 -14
  96. package/sql/10-core.sql +7 -50
  97. package/sql/15-errors.sql +64 -10
  98. package/sql/20-context.sql +18 -9
  99. package/sql/30-validate.sql +79 -30
  100. package/sql/40-class.sql +41 -115
  101. package/sql/50-acl.sql +8 -13
  102. package/sql/60-write.sql +220 -676
  103. package/sql/70-read.sql +11 -13
  104. package/sql/80-auth.sql +109 -42
  105. package/sql/90-time.sql +108 -947
  106. package/sql/95-seed.system.sql +7 -14
  107. package/sql/99-revision.sql +6 -1
  108. package/sql/README.md +32 -24
  109. package/sql/migrate/0009-restore-rev.tenants-name.sql +10 -0
  110. package/sql/migrate/0010-impersonation-no-account.sql +5 -0
  111. package/sql/migrate/0011-key-lock.otp-enabled.sql +7 -0
  112. package/sql/migrate/README.md +18 -0
  113. package/AGENT-CHEATSHEET.en.md +0 -488
  114. package/MIGRATION.md +0 -190
  115. package/README.en.md +0 -2910
  116. package/dist/import.d.ts +0 -185
  117. package/dist/import.js +0 -820
  118. package/dist/project.d.ts +0 -359
  119. package/dist/project.js +0 -1258
  120. package/sql/75-history.sql +0 -436
  121. package/sql/88-derived.sql +0 -1236
  122. package/sql/migrate/0006-changed-paths.sql +0 -3
  123. package/sql/migrate/0007-secret-account-fk.sql +0 -12
@@ -0,0 +1,572 @@
1
+ export const write = {
2
+ // write.ts — write.ts:checkReason: причина изменения (reason у глаголов, db.begin(), батча)
3
+ 'write.bad_reason': {
4
+ code: 'invalid_query',
5
+ ru: '{where}: reason — строка до 1000 символов',
6
+ },
7
+ // данные create(), upsert(): inc() внутри массива
8
+ 'write.inc_in_array': {
9
+ code: 'invalid_data',
10
+ ru: 'inc() внутри массива ({path}) не поддерживается',
11
+ },
12
+ // данные: оператор фильтра внутри вложенного значения
13
+ 'write.op_in_data': {
14
+ code: 'invalid_data',
15
+ ru: 'оператор фильтра в данных ({path}) — данные записываются значениями',
16
+ },
17
+ // патч update(): величина inc(by)
18
+ 'write.inc_by_not_finite': {
19
+ code: 'invalid_data',
20
+ ru: 'inc({by}) по пути {path}: величина — конечное число',
21
+ },
22
+ // патч update(): inc(by, { start })
23
+ 'write.inc_start_not_finite': {
24
+ code: 'invalid_data',
25
+ ru: 'inc: start по пути {path} — конечное число',
26
+ },
27
+ // патч update(): оператор фильтра вместо значения
28
+ 'write.op_in_patch': {
29
+ code: 'invalid_data',
30
+ ru: 'оператор фильтра в патче ({path}) — update записывает значения',
31
+ },
32
+ // данные create(), upsert() — не объект
33
+ 'write.data_not_object': {
34
+ code: 'invalid_data',
35
+ ru: '{verb}(): данные — объект',
36
+ },
37
+ // inc() в данных глагола, кроме update()
38
+ 'write.inc_outside_update': {
39
+ code: 'invalid_data',
40
+ ru: 'inc() работает только в update(): {verb}() по пути {path}',
41
+ },
42
+ // оператор фильтра на верхнем уровне данных глагола
43
+ 'write.op_in_verb_data': {
44
+ code: 'invalid_data',
45
+ ru: 'оператор фильтра в данных {verb}() ({path}) — данные записываются значениями',
46
+ },
47
+ // глагол после asOf()
48
+ 'write.as_of_write': {
49
+ code: 'invalid_query',
50
+ ru: '{verb}(): asOf описывает прошлое — записывать в него нельзя',
51
+ },
52
+ // глагол после withDeleted(), кроме restore()
53
+ 'write.with_deleted_write': {
54
+ code: 'invalid_query',
55
+ ru: '{verb}(): withDeleted описывает удалённые — записывать в них нельзя',
56
+ },
57
+ // create() или upsert() сразу после другого глагола; prev — прежний глагол
58
+ 'write.verb_after_verb': {
59
+ code: 'invalid_query',
60
+ ru: '{verb}() сразу после {prev}() — между глаголами нужен шаг',
61
+ },
62
+ // create() или upsert() на возврате к узлу пути
63
+ 'write.create_on_pivot': {
64
+ code: 'invalid_query',
65
+ ru: '{verb}() на возврате к узлу — он ведёт к существующей сущности, используйте update()',
66
+ },
67
+ // create() или upsert() после шага с фильтром, а не с id
68
+ 'write.create_with_filter': {
69
+ code: 'invalid_query',
70
+ ru: '{verb}() не ищет: {step}(id).{verb}(…) задаёт id, поиск — это update()',
71
+ },
72
+ // upsert() у класса без ключа
73
+ 'write.upsert_no_key': {
74
+ code: 'no_key',
75
+ ru: 'upsert(): у класса {cls} нет ключа — сравнивать не с чем, используйте create()',
76
+ },
77
+ // .tags() перед create() или upsert() — не строка и не список
78
+ 'write.bad_tags': {
79
+ code: 'invalid_query',
80
+ ru: '.tags() перед {verb}() — строка или список',
81
+ },
82
+ // патч update() — не объект
83
+ 'write.patch_not_object': {
84
+ code: 'invalid_data',
85
+ ru: 'update(): патч — объект',
86
+ },
87
+ // update(patch, { rev }) — rev не номер версии
88
+ 'write.bad_rev': {
89
+ code: 'invalid_query',
90
+ ru: 'update(patch, { rev }): rev — номер версии строки',
91
+ },
92
+ // write.ts:precheckPlan (narrowed) — update() или delete({ confirm: true }), а ни у шага глагола, ни у шагов до него нет
93
+ // фильтра, id, непустого tags(), owner() или limit() и раньше в цепочке нет create() или upsert(); у update() цель
94
+ // задаёт и data.id патча (план 2.0, этап 4, п. 2; 7.10 №2); call — сам вызов, cls — класс шага глагола
95
+ 'write.no_filter': {
96
+ code: 'invalid_query',
97
+ ru: '{call} без фильтра ({cls}): ни у шага глагола, ни у шагов до него нет фильтра, id, непустого tags(), owner() или limit(), и раньше в цепочке нет create() или upsert() — правка прошла бы по всем строкам',
98
+ hint: 'сузить цели фильтром, id, tags(), owner() или limit() (цель update() задаёт и data.id патча); правка всех строк — опция глагола { all: true }',
99
+ },
100
+ // слот после глагола, который слотов не принимает
101
+ 'write.slot_bad_verb': {
102
+ code: 'invalid_query',
103
+ ru: 'слот .{role} после {verb}() — слоты у create(), update() и upsert()',
104
+ },
105
+ // write.ts:slotRole: имя слота — не роль записываемого класса и не класс реестра
106
+ 'write.slot_unknown': {
107
+ code: 'invalid_class',
108
+ ru: 'класс или роль {name} не найдены',
109
+ },
110
+ // write.ts:slotRole: класс слота не принимает ни одна роль; ends — концы через запятую или «—»
111
+ 'write.slot_not_end': {
112
+ code: 'invalid_query',
113
+ ru: '{name} — не конец класса {cls} (концы: {ends})',
114
+ },
115
+ // write.ts:slotRole: класс слота принимают несколько ролей
116
+ 'write.slot_ambiguous': {
117
+ code: 'invalid_query',
118
+ ru: 'слот {name} у класса {cls} неоднозначен: роли {roles} — назовите роль',
119
+ },
120
+ // конец из пути: шаг подходит нескольким ролям
121
+ 'write.end_ambiguous': {
122
+ code: 'invalid_query',
123
+ ru: 'конец класса {cls} для шага {step} неоднозначен: роли {roles} — назовите шаг ролью или задайте слот',
124
+ },
125
+ // конец из пути: шаг дал не одну сущность; count — сколько найдено
126
+ 'write.end_not_single': {
127
+ code: 'invalid_query',
128
+ ru: 'шаг {step} должен дать ровно одну сущность для конца {cls} (найдено {count})',
129
+ },
130
+ // слот .роль.set() одиночного конца — значение дало несколько сущностей
131
+ 'write.slot_single_many': {
132
+ code: 'invalid_query',
133
+ ru: 'слот .{role}.set(): конец одиночный, а значение дало {count} сущностей',
134
+ },
135
+ // write.ts:existsError: create() — объект уже есть, id не вычислить
136
+ 'write.exists': {
137
+ code: 'exists',
138
+ ru: 'объект класса {cls} уже существует',
139
+ },
140
+ // write.ts:existsError: create() — объект уже есть, id известен
141
+ 'write.exists_id': {
142
+ code: 'exists',
143
+ ru: 'объект класса {cls} уже существует (id {id})',
144
+ },
145
+ // write.ts:explainMissing: одна цель не видна
146
+ 'write.target_missing': {
147
+ code: 'target_not_found',
148
+ ru: 'цель {id} не найдена',
149
+ },
150
+ // write.ts:explainMissing: несколько целей не видны; ids — через запятую
151
+ 'write.targets_missing': {
152
+ code: 'target_not_found',
153
+ ru: 'цели не найдены: {ids}',
154
+ },
155
+ // write.ts:explainMissing: update(patch, { rev }) — строку успели изменить
156
+ 'write.rev_conflict': {
157
+ code: 'conflict',
158
+ ru: 'строку {id} успели изменить: версия {rev}, а не {expected} — перечитайте и повторите',
159
+ },
160
+ // write.ts:explainMissing: строки видны, но RLS не дала их изменить; ids — через запятую
161
+ 'write.rows_denied': {
162
+ code: 'acl_denied',
163
+ ru: 'строки видны, но изменить их нельзя: {ids}',
164
+ },
165
+ // update(patch, { rev }): у шага больше одной цели; count — сколько
166
+ 'write.rev_many_targets': {
167
+ code: 'rev_ambiguous',
168
+ ru: 'update(patch, { rev }): у шага {step} {count} целей — проверить версию { rev } можно только у одной цели',
169
+ },
170
+ // update(patch, { rev }): у шага нет цели
171
+ 'write.rev_no_target': {
172
+ code: 'target_not_found',
173
+ ru: 'update(patch, { rev }): у шага {step} нет цели',
174
+ },
175
+ // restore() живого объекта
176
+ 'write.not_deleted': {
177
+ code: 'not_deleted',
178
+ ru: 'объект не удалён: {verb}() — только для удалённых',
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 } (строки ответа)',
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, checkFullData и checkPatchEnds (3.0, вместо api.validation_failed_end): лишний ключ данных — конец класса
224
+ // (имя роли или класса на конце); keys — такие ключи, role — роль первого из них. Нарушения ValidationError дописывает
225
+ // к тексту сам
226
+ 'write.link_in_data': {
227
+ code: 'invalid_data',
228
+ ru: '{verb}() у {cls}: {keys} — связь, а не поле данных; конец задают путь цепочки или слот после глагола: .{role}.set(id)',
229
+ hint: 'конец (связь) — не поле данных: путь цепочки (db.Владелец(id).Класс().create(…)) или слот после глагола: db.Класс.create({ … }).Роль.set(id); у конца-списка — .Роль.add(id) / .Роль.remove(id), снять — .Роль.unset()',
230
+ },
231
+ // 3.0: delete({ confirm: true }) без expect и без all — удаление не сверено с показанным превью
232
+ 'write.confirm_without_expect': {
233
+ code: 'invalid_query',
234
+ ru: 'delete({ confirm: true }) без { expect }: удаление не сверено с превью, которое видел пользователь — ничего не удалено',
235
+ hint: 'передайте expect из превью: const p = await db.Класс(id).delete().rows(); показать p пользователю; await db.Класс(id).delete({ confirm: true, expect: p }).rows(). Осознанно удалить текущий состав без сверки — delete({ confirm: true, all: true })',
236
+ },
237
+ // 3.0: restore() без expect и без all — и для явных id: последнее надгробие могло смениться с тех пор, как его видели
238
+ 'write.restore_without_expect': {
239
+ code: 'invalid_query',
240
+ ru: 'restore() без { expect }: восстановление не сверено с надгробиями, которые видел пользователь — ничего не восстановлено',
241
+ hint: 'передайте expect — надгробия из ответа delete() или из db.Класс(id).withDeleted().rows(): restore({ expect }); восстановить то, что можно, — restore({ expect, partial: true }); осознанно вернуть все удалённые цели шага без сверки — restore({ all: true })',
242
+ },
243
+ // tx.ts — tx.ts:commitUnknown: связь оборвалась во время commit
244
+ 'write.commit_unknown': {
245
+ code: 'commit_unknown',
246
+ ru: 'связь оборвалась во время commit: транзакция могла зафиксироваться — проверьте исход повтором той же операции с тем же ключом',
247
+ },
248
+ // tx.ts: запрос в завершённой транзакции db.begin()
249
+ 'write.tx_done': {
250
+ code: 'tx_required',
251
+ ru: 'транзакция db.begin() уже завершена',
252
+ hint: 'транзакция уже зафиксирована или откатана — откройте новую: `const tr = await db.begin()`',
253
+ },
254
+ // tx.ts: lock() в завершённой транзакции db.begin()
255
+ 'write.lock_tx_done': {
256
+ code: 'tx_required',
257
+ ru: 'lock(): транзакция db.begin() уже завершена',
258
+ hint: 'транзакция уже зафиксирована или откатана — откройте новую: `const tr = await db.begin()`',
259
+ },
260
+ // tx.ts: commit() транзакции, в которой была ошибка, — PostgreSQL её откатил
261
+ 'write.tx_rolled_back': {
262
+ code: 'tx_rolled_back',
263
+ ru: 'commit: PostgreSQL откатил транзакцию (в ней была ошибка)',
264
+ },
265
+ // load.ts — загрузчик db.load()
266
+ // Issues.fail(): загрузка отменена, первое нарушение (message — его текст); код ошибки — код этого нарушения
267
+ // (detail.rows[0].code), а не записи: место строит LetopisError с ключом, как fromDbError
268
+ 'write.load_cancelled': {
269
+ code: 'invalid_data',
270
+ ru: 'загрузка отменена: строка {line} — {message}',
271
+ },
272
+ // Issues.fail(): … и ещё rest нарушений, проверены все строки
273
+ 'write.load_cancelled_more': {
274
+ code: 'invalid_data',
275
+ ru: 'загрузка отменена: строка {line} — {message} (и ещё {rest})',
276
+ },
277
+ // Issues.fail(): … проверены не все строки (why — причины через точку с запятой), нарушений не меньше rest + 1
278
+ 'write.load_cancelled_at_least': {
279
+ code: 'invalid_data',
280
+ ru: 'загрузка отменена: строка {line} — {message} (и ещё не меньше {rest}: {why})',
281
+ },
282
+ // Issues.fail(): … нарушение одно, но проверены не все строки
283
+ 'write.load_cancelled_maybe_more': {
284
+ code: 'invalid_data',
285
+ ru: 'загрузка отменена: строка {line} — {message} (возможно, не единственная: {why})',
286
+ },
287
+ // причина в why текста отмены (laterUnchecked): порция commitEvery не последняя
288
+ 'write.load_later_portions': {
289
+ code: 'invalid_data',
290
+ ru: 'следующие порции не проверялись',
291
+ },
292
+ // причина в why (prepareBatch, { as }): условия ACL проверили пачку, а следующие пачки — нет
293
+ 'write.load_later_batches': {
294
+ code: 'invalid_data',
295
+ ru: 'условия ACL следующих пачек не проверялись',
296
+ },
297
+ // причина в why (prepareBatch, pipeline, historyWrite): нарушение нашлось раньше, чем база проверила pattern
298
+ // (его валидатор на TypeScript оставляет базе) у строк, уже прошедших проверку в коде
299
+ 'write.load_db_rules_unchecked': {
300
+ code: 'invalid_data',
301
+ ru: 'шаблоны pattern (их проверяет база) проверены не у всех строк',
302
+ },
303
+ // причина в why (checkInDb): проверка базы называет не больше limit строк
304
+ 'write.load_db_check_limit': {
305
+ code: 'invalid_data',
306
+ ru: 'проверка базы называет не больше {limit} строк',
307
+ },
308
+ // toList: вход db.load() — не объект «класс → строки», не массив и не поток
309
+ 'write.load_bad_input': {
310
+ code: 'invalid_data',
311
+ ru: 'load(): вход — объект «класс → строки», массив или поток строк с $class',
312
+ hint: 'передать объект { Класс: [строки] }, массив строк с $class или их поток (Iterable, AsyncIterable); класс строк без $class — опция class',
313
+ },
314
+ // refOf — значение конца (текст — error нарушений write.load_end_ref и write.load_key_part_ref): строка, но не uuid
315
+ 'write.load_end_not_uuid': {
316
+ code: 'invalid_data',
317
+ ru: 'значение конца не uuid',
318
+ },
319
+ // refOf: не строка и не объект с $key или $ext
320
+ 'write.load_end_value': {
321
+ code: 'invalid_data',
322
+ ru: 'значение конца — id, { $key } или { $ext }',
323
+ },
324
+ // refOf: у полиморфного конца нет $class цели
325
+ 'write.load_end_polymorphic': {
326
+ code: 'invalid_data',
327
+ ru: 'полиморфный конец: укажите $class цели',
328
+ },
329
+ // refOf: класса цели нет в реестре или он абстрактный
330
+ 'write.load_target_class': {
331
+ code: 'invalid_data',
332
+ ru: 'класс цели {name} не найден или абстрактный',
333
+ },
334
+ // refOf: $key цели — не список строк по частям ключа (key — части через запятую)
335
+ 'write.load_target_key': {
336
+ code: 'invalid_data',
337
+ ru: '$key цели {cls} — список строк ключа [{key}]',
338
+ },
339
+ // refOf: $ext у цели класса с ключом
340
+ 'write.load_ext_keyed': {
341
+ code: 'invalid_data',
342
+ ru: '$ext — для класса без ключа, а у {cls} ключ [{key}]',
343
+ },
344
+ // refOf, idOfRow: $ext не строка
345
+ 'write.load_ext_string': {
346
+ code: 'invalid_data',
347
+ ru: '$ext — строка',
348
+ },
349
+ // shape — нарушение строки: значение конца со многими целями не массив
350
+ 'write.load_end_list': {
351
+ code: 'invalid_data',
352
+ ru: 'конец {role} — список целей',
353
+ },
354
+ // shape: значение конца не годится (error — текст refOf)
355
+ 'write.load_end_ref': {
356
+ code: 'invalid_data',
357
+ ru: 'конец {role}: {error}',
358
+ },
359
+ // shape: цель повторяется в списке конца
360
+ 'write.load_end_repeat': {
361
+ code: 'invalid_data',
362
+ ru: 'конец {role}: цель повторяется',
363
+ },
364
+ // shape: inc() в данных строки (path — путь к значению)
365
+ 'write.load_inc': {
366
+ code: 'invalid_data',
367
+ ru: 'inc() — только в update() ({path})',
368
+ },
369
+ // shape: оператор фильтра в данных строки (path — путь к значению)
370
+ 'write.load_filter_op': {
371
+ code: 'invalid_data',
372
+ ru: 'оператор фильтра в данных ({path})',
373
+ },
374
+ // shape: данные не проходят валидатор класса на TypeScript; нарушения — issues строки отчёта
375
+ 'write.load_invalid_data': {
376
+ code: 'invalid_data',
377
+ ru: 'данные не проходят описание класса {cls}',
378
+ },
379
+ // shape: у конца со многими целями меньше min
380
+ 'write.load_end_too_few': {
381
+ code: 'invalid_data',
382
+ ru: 'у конца {role} слишком мало целей',
383
+ },
384
+ // shape: обязательный конец не задан
385
+ 'write.load_end_required': {
386
+ code: 'invalid_data',
387
+ ru: 'обязательный конец {role} не задан',
388
+ },
389
+ // shape: у конца со многими целями больше max
390
+ 'write.load_end_too_many': {
391
+ code: 'invalid_data',
392
+ ru: 'у конца {role} слишком много целей',
393
+ },
394
+ // idOfRow — нарушение строки: часть ключа — конец, а его значение не годится (error — текст refOf)
395
+ 'write.load_key_part_ref': {
396
+ code: 'invalid_data',
397
+ ru: 'часть ключа {part}: {error}',
398
+ },
399
+ // idOfRow: часть ключа — поле, а значение не строка
400
+ 'write.load_key_part_string': {
401
+ code: 'invalid_data',
402
+ ru: 'часть ключа {part} — строка',
403
+ },
404
+ // load(), initialSession(): подключение не может стать владельцем схемы
405
+ 'write.load_admin_required': {
406
+ code: 'admin_required',
407
+ ru: 'загрузчику нужно администраторское подключение (connect({ allowBypassRls: true }) роли, которая может стать владельцем схемы)',
408
+ },
409
+ // load({ as }): не uuid
410
+ 'write.load_as_not_uuid': {
411
+ code: 'invalid_data',
412
+ ru: 'load({ as }): id аккаунта',
413
+ hint: 'передать в as id (uuid) аккаунта, от имени которого идёт загрузка',
414
+ },
415
+ // load({ as, withHistory })
416
+ 'write.load_as_history': {
417
+ code: 'invalid_data',
418
+ ru: 'load({ as }) и режим истории несовместимы: историю импортирует администратор',
419
+ hint: 'историю (withHistory) загружать без as; от имени пользователя — загрузка без истории',
420
+ },
421
+ // load({ withHistory, existing: 'update' })
422
+ 'write.load_history_update': {
423
+ code: 'invalid_data',
424
+ ru: "режим истории: existing — 'error' или 'skip' (новую версию существующего объекта пишет загрузка без истории)",
425
+ hint: "новые версии существующих объектов — отдельной загрузкой без withHistory с existing: 'update'",
426
+ },
427
+ // load({ initial, commitEvery })
428
+ 'write.load_initial_commit_every': {
429
+ code: 'invalid_data',
430
+ ru: 'первая заливка (initial) несовместима с commitEvery: снятые индексы и TRUNCATE требуют одной транзакции',
431
+ hint: 'убрать commitEvery: первая заливка — всегда одна транзакция; порциями пишет загрузка без initial',
432
+ },
433
+ // load({ initial, existing }): existing не 'error'
434
+ 'write.load_initial_existing': {
435
+ code: 'invalid_data',
436
+ ru: "первая заливка (initial): existing — только 'error', в пустой схеме пропускать и обновлять нечего",
437
+ hint: 'убрать existing; пропустить или обновить существующие объекты — загрузкой без initial',
438
+ },
439
+ // load({ initial, as })
440
+ 'write.load_initial_as': {
441
+ code: 'invalid_data',
442
+ ru: 'первая заливка (initial) — от администратора, без { as }',
443
+ hint: 'убрать as; от имени пользователя — загрузка без initial',
444
+ },
445
+ // load(): нет ни { tenant }, ни { as }, ни арендатора сессии
446
+ 'write.load_no_tenant': {
447
+ code: 'invalid_data',
448
+ ru: 'load(): нет арендатора — сессия без арендатора и не задан { tenant }',
449
+ hint: 'передать load(…, { tenant }) или загружать в сессии с арендатором',
450
+ },
451
+ // проход 1 — нарушение строки: класса $class нет в реестре
452
+ 'write.load_class_not_found': {
453
+ code: 'invalid_class',
454
+ ru: 'класс {name} не найден',
455
+ },
456
+ // проход 1: у строки нет $class (и нет опции class)
457
+ 'write.load_no_class': {
458
+ code: 'invalid_class',
459
+ ru: 'нет $class',
460
+ },
461
+ // проход 1: строка класса Class
462
+ 'write.load_class_rows': {
463
+ code: 'class_not_loadable',
464
+ ru: 'строки Class задают letopis sync и create — там база компилирует проверку',
465
+ },
466
+ // проход 1: класс абстрактный
467
+ 'write.load_class_abstract': {
468
+ code: 'invalid_class',
469
+ ru: 'класс {cls} абстрактный',
470
+ },
471
+ // проход 1, первая заливка: id занят строкой системного класса или прошлой загрузкой сессии
472
+ 'write.load_taken': {
473
+ code: 'exists',
474
+ ru: 'объект уже есть в схеме: строка системного класса или запись этой же первой заливки',
475
+ },
476
+ // проход 1: повтор id при duplicates: 'error' (prev — строка, где id был раньше)
477
+ 'write.load_duplicate': {
478
+ code: 'duplicate',
479
+ ru: 'id {id} уже был в строке {prev}',
480
+ },
481
+ // loadPortion ({ as }) — нарушение: у пользователя нет права WRITE на класс
482
+ 'write.load_no_write': {
483
+ code: 'acl_denied',
484
+ ru: 'нет права WRITE на класс {cls}',
485
+ },
486
+ // check, loadPortion, initialSession — нарушение: цели конца нет, она чужая, не того класса или не видна ({ as })
487
+ 'write.load_target_not_found': {
488
+ code: 'target_not_found',
489
+ ru: 'цель конца {role} не найдена',
490
+ },
491
+ // loadPortion, initialSession: владелец ($owner) — не аккаунт
492
+ 'write.load_owner_not_found': {
493
+ code: 'target_not_found',
494
+ ru: 'владелец {id} не найден',
495
+ },
496
+ // loadPortion, initialSession: арендатора нет или он отключён
497
+ 'write.load_tenant_denied': {
498
+ code: 'tenant_denied',
499
+ ru: 'арендатор {tenant} не найден или отключён',
500
+ },
501
+ // explainTaken (existing: 'error') — нарушение: id занят живым объектом
502
+ 'write.load_exists': {
503
+ code: 'exists',
504
+ ru: 'объект уже есть в базе',
505
+ },
506
+ // explainTaken: id занят удалённым объектом
507
+ 'write.load_deleted': {
508
+ code: 'deleted',
509
+ ru: 'объект с этим id жил раньше и удалён (existing: update воскресит)',
510
+ },
511
+ // explainTaken: первичный ключ отказал, а занятых id запрос не нашёл
512
+ 'write.load_objects_exist': {
513
+ code: 'exists',
514
+ ru: 'загрузка отменена: часть объектов уже есть в базе',
515
+ },
516
+ // check — нарушение строки: Account не в арендаторе System
517
+ 'write.load_account_tenant': {
518
+ code: 'tenant_denied',
519
+ ru: 'аккаунты лежат в арендаторе System',
520
+ },
521
+ // check: конец Tenant строки member — не арендатор строки
522
+ 'write.load_member_tenant': {
523
+ code: 'invalid_data',
524
+ ru: 'конец Tenant членства — арендатор строки',
525
+ },
526
+ // check: $tags — не список строк
527
+ 'write.load_tags': {
528
+ code: 'invalid_data',
529
+ ru: '$tags — список строк',
530
+ },
531
+ // check: $owner при { as }
532
+ 'write.load_as_owner': {
533
+ code: 'invalid_data',
534
+ ru: 'load({ as }): $owner не задаётся — владелец по ownerDefault класса',
535
+ },
536
+ // check: $owner не uuid
537
+ 'write.load_owner_id': {
538
+ code: 'invalid_data',
539
+ ru: '$owner — id аккаунта',
540
+ },
541
+ // prepareBatch (existing: 'update') — нарушение: id занят объектом другого класса (was)
542
+ 'write.load_other_class': {
543
+ code: 'exists',
544
+ ru: 'объект с этим id уже есть в классе {was}: строка класса {cls} его не правит',
545
+ },
546
+ // flushBatch ({ as }) — нарушение: условие ACL запрещает строку
547
+ 'write.load_acl_row': {
548
+ code: 'acl_denied',
549
+ ru: 'строку запрещает условие ACL',
550
+ },
551
+ // flushBatch ({ as }, existing: 'update'): условие ACL запрещает правку — прежние или новые значения
552
+ 'write.load_acl_update': {
553
+ code: 'acl_denied',
554
+ ru: 'правку строки запрещает условие ACL',
555
+ },
556
+ // historyWrite — нарушение: $rev версий объекта не подряд от 1 или $at не строка
557
+ 'write.load_history_versions': {
558
+ code: 'invalid_data',
559
+ ru: 'режим истории: версии с $rev подряд от 1 и $at — время версии',
560
+ },
561
+ // initialSession: в схеме есть строки, кроме системных классов
562
+ 'write.load_not_empty': {
563
+ code: 'not_empty',
564
+ ru: 'первая заливка — только в пустую схему: в entity и log, кроме строк системных классов (Class, Account, member, Resource, rule), ничего быть не должно',
565
+ },
566
+ // tx.ts, hintTxRetry: хвост к тексту взаимной блокировки или ошибки сериализации в явной транзакции (lmsg) — с
567
+ // пробелом и тире в начале; называет db.begin(), поэтому второй раз не дописывается
568
+ 'write.tx_retry_hint': {
569
+ code: 'invalid_query',
570
+ ru: ' — откатите транзакцию и повторите весь блок db.begin()',
571
+ },
572
+ };