letopis 2.0.0 → 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.
- package/AGENT-CHEATSHEET.md +484 -414
- package/CHANGELOG.md +321 -0
- package/LICENSE +75 -21
- package/LICENSE-COMMERCIAL.md +49 -0
- package/README.md +786 -183
- package/api-contract.json +381 -0
- package/dist/acl.d.ts +3 -0
- package/dist/acl.js +19 -2
- package/dist/admin.d.ts +2 -1
- package/dist/admin.js +6 -4
- package/dist/auth.d.ts +18 -3
- package/dist/auth.js +75 -6
- package/dist/chain.d.ts +38 -17
- package/dist/chain.js +434 -63
- package/dist/cli.js +28 -6
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +46 -6
- package/dist/index.d.ts +10 -4
- package/dist/index.js +136 -46
- package/dist/internal.d.ts +2 -0
- package/dist/load.d.ts +9 -5
- package/dist/load.js +30 -11
- package/dist/log.d.ts +121 -0
- package/dist/log.js +633 -0
- package/dist/mcp.js +26 -11
- package/dist/messages/api.d.ts +66 -0
- package/dist/messages/api.js +90 -1
- package/dist/messages/boot.js +4 -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 +229 -0
- package/dist/messages/log.js +102 -0
- package/dist/messages/model.d.ts +24 -1
- package/dist/messages/model.js +34 -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 +47 -0
- package/dist/messages/write.js +63 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +23 -5
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +77 -25
- package/dist/sync.d.ts +1 -1
- package/dist/sync.js +50 -0
- package/dist/tx.d.ts +10 -1
- package/dist/tx.js +44 -2
- package/dist/typed.d.ts +14 -10
- package/dist/types.d.ts +82 -4
- package/dist/types.js +6 -1
- package/dist/up.d.ts +3 -3
- package/dist/up.js +57 -17
- 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 +78 -1
- package/dist/write.js +357 -39
- package/llms.txt +37 -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/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
package/dist/mcp.js
CHANGED
|
@@ -27,9 +27,11 @@ import { createRequire } from 'node:module';
|
|
|
27
27
|
import { describeModel } from './describe.js';
|
|
28
28
|
import { LetopisError, checkOptions, lerr, lmsg, similar } from './errors.js';
|
|
29
29
|
import { internalsOf } from './internal.js';
|
|
30
|
+
import { NOLOG } from './log.js';
|
|
30
31
|
import { OPERATORS, opHint, or } from './ops.js';
|
|
31
32
|
import { cursorOf } from './sql.js';
|
|
32
33
|
import { ACL_CLASS_NAMES, RESERVED_CLASS_NAMES } from './types.js';
|
|
34
|
+
import { UUID_RE } from './uuid.js';
|
|
33
35
|
/** Все опции createMcpServer: неизвестная — invalid_data с похожими именами (checkOptions; план 2.0, 7.10 №4). */
|
|
34
36
|
const MCP_OPTIONS = {
|
|
35
37
|
write: true, maxRows: true, maxBytes: true, allowAclWrite: true, allowSystemAdmin: true,
|
|
@@ -43,7 +45,7 @@ const STATE_KEY = randomBytes(32);
|
|
|
43
45
|
const CONFIRM_TTL_MS = 10 * 60_000;
|
|
44
46
|
const RESERVED = new Set(RESERVED_CLASS_NAMES);
|
|
45
47
|
const SYSTEM_ID = '2eba6d0a-1edd-4bb6-a85a-a703dab49035';
|
|
46
|
-
const UUID =
|
|
48
|
+
const UUID = UUID_RE;
|
|
47
49
|
const TERMINALS = ['rows', 'first', 'ids', 'count', 'sum', 'avg', 'min', 'max', 'countBy'];
|
|
48
50
|
const PAGED = new Set(['rows', 'first', 'ids']);
|
|
49
51
|
/** Шагов цепочки и условий $or в одном фильтре не больше (ревью безопасности №2): запрос растёт с каждым. */
|
|
@@ -375,6 +377,8 @@ function stepFilter(a, where) {
|
|
|
375
377
|
return undefined;
|
|
376
378
|
}
|
|
377
379
|
const step = (x, name) => x[name];
|
|
380
|
+
/** Шаг с фильтром или без: без фильтра — вызов без аргумента (X(undefined) цепочка отмечает предупреждением, A13). */
|
|
381
|
+
const stepWith = (fn, f) => (f === undefined ? fn() : fn(f));
|
|
378
382
|
const encodeCursor = (c) => Buffer.from(JSON.stringify(c)).toString('base64url');
|
|
379
383
|
function decodeCursor(s) {
|
|
380
384
|
let c;
|
|
@@ -425,7 +429,7 @@ async function query(db, a, o) {
|
|
|
425
429
|
if (typeof terminal !== 'string' || !TERMINALS.includes(terminal)) {
|
|
426
430
|
throw lerr('mcp.unknown_terminal', { terminal: String(terminal), similar: similar(String(terminal), TERMINALS) });
|
|
427
431
|
}
|
|
428
|
-
let chain = step(db, stepName(reg, a.class, 'letopis_query.class'))
|
|
432
|
+
let chain = stepWith(step(db, stepName(reg, a.class, 'letopis_query.class')), stepFilter(a, 'letopis_query'));
|
|
429
433
|
if (a.steps !== undefined) {
|
|
430
434
|
if (!Array.isArray(a.steps))
|
|
431
435
|
throw lerr('mcp.steps_not_list');
|
|
@@ -436,7 +440,7 @@ async function query(db, a, o) {
|
|
|
436
440
|
if (!isObj(s))
|
|
437
441
|
throw lerr('mcp.step_not_object', { where });
|
|
438
442
|
checkKeys(s, ['step', 'id', 'ids', 'filter'], where);
|
|
439
|
-
chain = step(chain, stepName(reg, s.step, `${where}.step`))
|
|
443
|
+
chain = stepWith(step(chain, stepName(reg, s.step, `${where}.step`)), stepFilter(s, where));
|
|
440
444
|
});
|
|
441
445
|
}
|
|
442
446
|
if (a.asOf !== undefined) {
|
|
@@ -674,13 +678,14 @@ async function write(db, a, ctx, sdk, server, o, who) {
|
|
|
674
678
|
return c.ask;
|
|
675
679
|
if (!c.granted)
|
|
676
680
|
throw lerr(c.reason);
|
|
677
|
-
// превью и удаление — одной транзакцией: подтверждено ровно то, что
|
|
681
|
+
// превью и удаление — одной транзакцией: подтверждено ровно то, что удаляется; delete сверяет превью сам (expect,
|
|
682
|
+
// 3.0) — под блокировкой замыкания каскада
|
|
678
683
|
const tx = await db.begin();
|
|
679
684
|
try {
|
|
680
685
|
const again = await step(tx, cls)(targets).delete(opts).rows();
|
|
681
686
|
if (operationOf(db, cls, targets, again) !== op)
|
|
682
687
|
throw lerr('mcp.delete_data_changed');
|
|
683
|
-
const rows = await step(tx, cls)(targets).delete({ confirm: true, ...opts }).rows();
|
|
688
|
+
const rows = await step(tx, cls)(targets).delete({ confirm: true, expect: again, ...opts }).rows();
|
|
684
689
|
await tx.commit();
|
|
685
690
|
return ok({ deleted: rows, operation: op });
|
|
686
691
|
}
|
|
@@ -748,19 +753,29 @@ async function serve(db, opts) {
|
|
|
748
753
|
const who = await checkCredentials(db, o);
|
|
749
754
|
const sdk = await loadSdk();
|
|
750
755
|
const server = new sdk.McpServer({ name: 'letopis', version: VERSION }, { instructions: instructions(o), capabilities: { tools: {} } });
|
|
751
|
-
|
|
756
|
+
// логирование 3.0: вызов инструмента — mcp.call (имя, время, исход), отказ — ещё error; логгер — фасада db
|
|
757
|
+
const log = internalsOf(db)?.log ?? NOLOG;
|
|
758
|
+
const guard = (tool, fn) => async (args, ctx) => {
|
|
759
|
+
const t0 = performance.now();
|
|
752
760
|
try {
|
|
753
|
-
|
|
761
|
+
const r = await fn(isObj(args) ? args : {}, ctx);
|
|
762
|
+
if (log.wants('mcp.call')) {
|
|
763
|
+
log.emit('mcp.call', { op: tool, ms: performance.now() - t0, detail: { ok: !r.isError } });
|
|
764
|
+
}
|
|
765
|
+
return r;
|
|
754
766
|
}
|
|
755
767
|
catch (e) {
|
|
768
|
+
log.error(e, { op: tool, ms: performance.now() - t0 });
|
|
769
|
+
if (log.wants('mcp.call'))
|
|
770
|
+
log.emit('mcp.call', { op: tool, ms: performance.now() - t0, detail: { ok: false } });
|
|
756
771
|
return failure(e);
|
|
757
772
|
}
|
|
758
773
|
};
|
|
759
|
-
server.registerTool('letopis_model', { title: 'Модель letopis', description: DESCRIBE_MODEL, inputSchema: sdk.fromJsonSchema(MODEL_SCHEMA), annotations: READ_ONLY }, guard((a) => model(db, a, o)));
|
|
760
|
-
server.registerTool('letopis_query', { title: 'Чтение letopis', description: describeQuery(o), inputSchema: sdk.fromJsonSchema(QUERY_SCHEMA), annotations: READ_ONLY }, guard((a) => query(db, a, o)));
|
|
761
|
-
server.registerTool('letopis_history', { title: 'История letopis', description: describeHistory(o), inputSchema: sdk.fromJsonSchema(HISTORY_SCHEMA), annotations: READ_ONLY }, guard((a) => history(db, a, o)));
|
|
774
|
+
server.registerTool('letopis_model', { title: 'Модель letopis', description: DESCRIBE_MODEL, inputSchema: sdk.fromJsonSchema(MODEL_SCHEMA), annotations: READ_ONLY }, guard('letopis_model', (a) => model(db, a, o)));
|
|
775
|
+
server.registerTool('letopis_query', { title: 'Чтение letopis', description: describeQuery(o), inputSchema: sdk.fromJsonSchema(QUERY_SCHEMA), annotations: READ_ONLY }, guard('letopis_query', (a) => query(db, a, o)));
|
|
776
|
+
server.registerTool('letopis_history', { title: 'История letopis', description: describeHistory(o), inputSchema: sdk.fromJsonSchema(HISTORY_SCHEMA), annotations: READ_ONLY }, guard('letopis_history', (a) => history(db, a, o)));
|
|
762
777
|
if (o.write) {
|
|
763
|
-
server.registerTool('letopis_write', { title: 'Запись letopis', description: describeWrite(o), inputSchema: sdk.fromJsonSchema(WRITE_SCHEMA), annotations: DESTRUCTIVE }, guard((a, ctx) => write(db, a, ctx, sdk, server, o, who)));
|
|
778
|
+
server.registerTool('letopis_write', { title: 'Запись letopis', description: describeWrite(o), inputSchema: sdk.fromJsonSchema(WRITE_SCHEMA), annotations: DESTRUCTIVE }, guard('letopis_write', (a, ctx) => write(db, a, ctx, sdk, server, o, who)));
|
|
764
779
|
}
|
|
765
780
|
return server;
|
|
766
781
|
}
|
package/dist/messages/api.d.ts
CHANGED
|
@@ -6,6 +6,51 @@ export declare const api: {
|
|
|
6
6
|
'api.unknown_step': {
|
|
7
7
|
code: "invalid_class";
|
|
8
8
|
ru: string;
|
|
9
|
+
hint: string;
|
|
10
|
+
};
|
|
11
|
+
'api.token_not_active': {
|
|
12
|
+
code: "no_session";
|
|
13
|
+
ru: string;
|
|
14
|
+
hint: string;
|
|
15
|
+
};
|
|
16
|
+
'api.unknown_step_no_classes': {
|
|
17
|
+
code: "invalid_class";
|
|
18
|
+
ru: string;
|
|
19
|
+
hint: string;
|
|
20
|
+
};
|
|
21
|
+
'api.chain_not_rows': {
|
|
22
|
+
code: "invalid_query";
|
|
23
|
+
ru: string;
|
|
24
|
+
hint: string;
|
|
25
|
+
};
|
|
26
|
+
'api.step_undefined_filter': {
|
|
27
|
+
code: "invalid_query";
|
|
28
|
+
ru: string;
|
|
29
|
+
};
|
|
30
|
+
'api.sort_not_last': {
|
|
31
|
+
code: "invalid_query";
|
|
32
|
+
ru: string;
|
|
33
|
+
};
|
|
34
|
+
'api.with_deleted_step': {
|
|
35
|
+
code: "invalid_query";
|
|
36
|
+
ru: string;
|
|
37
|
+
};
|
|
38
|
+
'api.db_bad_value': {
|
|
39
|
+
code: "invalid_query";
|
|
40
|
+
ru: string;
|
|
41
|
+
hint: string;
|
|
42
|
+
};
|
|
43
|
+
'api.validation_issue': {
|
|
44
|
+
code: "invalid_data";
|
|
45
|
+
ru: string;
|
|
46
|
+
};
|
|
47
|
+
'api.validation_root': {
|
|
48
|
+
code: "invalid_data";
|
|
49
|
+
ru: string;
|
|
50
|
+
};
|
|
51
|
+
'api.validation_more': {
|
|
52
|
+
code: "invalid_data";
|
|
53
|
+
ru: string;
|
|
9
54
|
};
|
|
10
55
|
'api.connect_schema': {
|
|
11
56
|
code: "invalid_data";
|
|
@@ -49,6 +94,7 @@ export declare const api: {
|
|
|
49
94
|
'api.for_update_unsupported': {
|
|
50
95
|
code: "lock_unsupported";
|
|
51
96
|
ru: string;
|
|
97
|
+
hint: string;
|
|
52
98
|
};
|
|
53
99
|
'api.lock_denied': {
|
|
54
100
|
code: "acl_denied";
|
|
@@ -119,6 +165,11 @@ export declare const api: {
|
|
|
119
165
|
ru: string;
|
|
120
166
|
hint: string;
|
|
121
167
|
};
|
|
168
|
+
'api.begin_arg': {
|
|
169
|
+
code: "invalid_query";
|
|
170
|
+
ru: string;
|
|
171
|
+
hint: string;
|
|
172
|
+
};
|
|
122
173
|
'api.nested_begin': {
|
|
123
174
|
code: "invalid_query";
|
|
124
175
|
ru: string;
|
|
@@ -132,6 +183,11 @@ export declare const api: {
|
|
|
132
183
|
ru: string;
|
|
133
184
|
hint: string;
|
|
134
185
|
};
|
|
186
|
+
'api.connect_empty_credential': {
|
|
187
|
+
code: "invalid_data";
|
|
188
|
+
ru: string;
|
|
189
|
+
hint: string;
|
|
190
|
+
};
|
|
135
191
|
'api.connect_admin_type': {
|
|
136
192
|
code: "invalid_data";
|
|
137
193
|
ru: string;
|
|
@@ -168,4 +224,14 @@ export declare const api: {
|
|
|
168
224
|
code: "invalid_data";
|
|
169
225
|
ru: string;
|
|
170
226
|
};
|
|
227
|
+
'api.create_tenant_admin': {
|
|
228
|
+
code: "invalid_query";
|
|
229
|
+
ru: string;
|
|
230
|
+
hint: string;
|
|
231
|
+
};
|
|
232
|
+
'api.versions_terminal': {
|
|
233
|
+
code: "invalid_query";
|
|
234
|
+
ru: string;
|
|
235
|
+
hint: string;
|
|
236
|
+
};
|
|
171
237
|
};
|
package/dist/messages/api.js
CHANGED
|
@@ -8,6 +8,67 @@ export const api = {
|
|
|
8
8
|
'api.unknown_step': {
|
|
9
9
|
code: 'invalid_class',
|
|
10
10
|
ru: 'класс или роль {name} не найдены',
|
|
11
|
+
hint: 'классы арендатора видны только в его сессии: арендаторы пользователя — db.auth.tenants(), переход — db.auth.switch(tenant) или db.as(аккаунт, { tenant }); имя — как в модели (классы записывает syncModels)',
|
|
12
|
+
},
|
|
13
|
+
// chain.ts: шаг не найден, а в реестре сессии нет ни одного класса, кроме классов System — сессия, похоже, в
|
|
14
|
+
// личном арендаторе (вход без switch); похожие имена — params.similar
|
|
15
|
+
// index.ts: connect({ token }) — токен не даёт сессии (погашен, истёк, неверен); chain.ts: шаг корня без сессии
|
|
16
|
+
// (сессия погасла после connect) — отказ терминала, а не синхронный
|
|
17
|
+
'api.token_not_active': {
|
|
18
|
+
code: 'no_session',
|
|
19
|
+
ru: 'нет действующей сессии: токен погашен, истёк или неверен',
|
|
20
|
+
hint: 'войти заново и подключиться с новым токеном; веб-сервису — одно подключение сервиса и svc.as(account, { tenant }) на запрос, а не connect({ token }) на каждый вход',
|
|
21
|
+
},
|
|
22
|
+
'api.unknown_step_no_classes': {
|
|
23
|
+
code: 'invalid_class',
|
|
24
|
+
ru: 'класс или роль {name} не найдены: в реестре сессии нет ни одного класса арендатора — сессия, похоже, в личном арендаторе',
|
|
25
|
+
hint: 'после входа сессия работает в личном арендаторе пользователя: найти нужного — db.auth.tenants(), перейти — db.auth.switch(tenant) или db.as(аккаунт, { tenant }); классы в арендатор записывает syncModels',
|
|
26
|
+
},
|
|
27
|
+
// chain.ts: у цепочки спросили свойство массива (length, map, filter, 0 …) или перебрали её — она не массив строк
|
|
28
|
+
'api.chain_not_rows': {
|
|
29
|
+
code: 'invalid_query',
|
|
30
|
+
ru: 'цепочка — не массив: {name} есть у строк ответа, а не у цепочки',
|
|
31
|
+
hint: 'получить строки терминалом с await: const rows = await db.Класс(…).rows(), одну строку — await ….first()',
|
|
32
|
+
},
|
|
33
|
+
// chain.ts, A13: шаг вызван с аргументом undefined — фильтра нет, шаг находит все строки класса (как без аргумента);
|
|
34
|
+
// предупреждение (lwarn), один раз на имя шага
|
|
35
|
+
'api.step_undefined_filter': {
|
|
36
|
+
code: 'invalid_query',
|
|
37
|
+
ru: '{name}(undefined) — фильтра нет: шаг находит все строки, как {name}(). Проверьте переменную с id',
|
|
38
|
+
},
|
|
39
|
+
// chain.ts, A13: шаг после sort() — сортировка относится к последнему шагу цепочки, а не к тому, после которого
|
|
40
|
+
// стоит; предупреждение (lwarn), один раз на пару
|
|
41
|
+
'api.sort_not_last': {
|
|
42
|
+
code: 'invalid_query',
|
|
43
|
+
ru: "sort('{field}') перед шагом {step} сортирует строки последнего шага ({step}), а не прежнего: ставьте sort() в конец цепочки",
|
|
44
|
+
},
|
|
45
|
+
// chain.ts, A9: withDeleted() не у последнего шага, а у последнего — нет; предупреждение (lwarn), один раз на пару:
|
|
46
|
+
// с 2.1 withDeleted() — модификатор своего шага
|
|
47
|
+
'api.with_deleted_step': {
|
|
48
|
+
code: 'invalid_query',
|
|
49
|
+
ru: 'withDeleted() у шага {step} относится к нему, а не к последнему шагу {last}: удалённые {last} — .{last}().withDeleted()',
|
|
50
|
+
},
|
|
51
|
+
// errors.ts, fromDbError: подстраховка — значение не того типа (SQLSTATE 22P02), которое не поймала проверка до
|
|
52
|
+
// запроса; text — текст сервера
|
|
53
|
+
'api.db_bad_value': {
|
|
54
|
+
code: 'invalid_query',
|
|
55
|
+
ru: 'значение не того типа: {text}',
|
|
56
|
+
hint: 'id — строка uuid, числа — числа, даты — строки ISO; проверьте значения фильтра, курсора и опций',
|
|
57
|
+
},
|
|
58
|
+
// errors.ts, ValidationError: первое нарушение в тексте ошибки — путь и сообщение; текст не для ошибки (lmsg)
|
|
59
|
+
'api.validation_issue': {
|
|
60
|
+
code: 'invalid_data',
|
|
61
|
+
ru: '{path} — {issue}',
|
|
62
|
+
},
|
|
63
|
+
// errors.ts, ValidationError: путь нарушения на верхнем уровне данных (пустой путь); текст не для ошибки (lmsg)
|
|
64
|
+
'api.validation_root': {
|
|
65
|
+
code: 'invalid_data',
|
|
66
|
+
ru: 'данные',
|
|
67
|
+
},
|
|
68
|
+
// errors.ts, ValidationError: число нарушений, если их больше одного; текст не для ошибки (lmsg)
|
|
69
|
+
'api.validation_more': {
|
|
70
|
+
code: 'invalid_data',
|
|
71
|
+
ru: ' (всего нарушений: {n}, все — err.issues)',
|
|
11
72
|
},
|
|
12
73
|
// index.ts: connect({ schema })
|
|
13
74
|
'api.connect_schema': {
|
|
@@ -61,9 +122,11 @@ export const api = {
|
|
|
61
122
|
ru: 'forUpdate() — только внутри db.begin(): вне явной транзакции блокировка снялась бы сразу',
|
|
62
123
|
},
|
|
63
124
|
// chain.ts, lockedRead: forUpdate() с другим терминалом, агрегатом, срезом, глаголом
|
|
125
|
+
// chain.ts, lockedRead: what — что мешает блокировке (терминал, asOf(), withDeleted(), deep(), глагол)
|
|
64
126
|
'api.for_update_unsupported': {
|
|
65
127
|
code: 'lock_unsupported',
|
|
66
|
-
ru: 'forUpdate()
|
|
128
|
+
ru: 'forUpdate() нельзя с {what}: блокировка — только у чтения rows(), first(), ids()',
|
|
129
|
+
hint: 'forUpdate() — в db.begin(), перед rows(), first() или ids(); без asOf(), versions(), withDeleted(), deep(), count(), paths(), агрегатов и глаголов. Правка — отдельной цепочкой после чтения: await tr.X(id).forUpdate().first(); await tr.X(id).update(…).rows()',
|
|
67
130
|
},
|
|
68
131
|
// chain.ts, lockedRead: строки видны, но политика правки не даёт их заблокировать; ids — через запятую (detail.ids — списком)
|
|
69
132
|
'api.lock_denied': {
|
|
@@ -152,6 +215,13 @@ export const api = {
|
|
|
152
215
|
hint: 'повторить вызов: между запросами db.as() сессия сменила арендатора или кончилась',
|
|
153
216
|
},
|
|
154
217
|
// index.ts: db.begin() внутри db.begin()
|
|
218
|
+
// index.ts: db.begin(аргумент) — не объект опций (функция, строка, число); функцию begin() не вызывает; type — typeof
|
|
219
|
+
// аргумента (array — массив)
|
|
220
|
+
'api.begin_arg': {
|
|
221
|
+
code: 'invalid_query',
|
|
222
|
+
ru: 'db.begin() принимает только опции { reason }, а не {type}: транзакцию открывает begin(), закрывают commit() и rollback()',
|
|
223
|
+
hint: 'const tr = await db.begin(); try { …; await tr.commit() } catch (e) { await tr.rollback(); throw e }',
|
|
224
|
+
},
|
|
155
225
|
'api.nested_begin': {
|
|
156
226
|
code: 'invalid_query',
|
|
157
227
|
ru: 'вложенных транзакций нет: db.begin() внутри db.begin()',
|
|
@@ -167,6 +237,12 @@ export const api = {
|
|
|
167
237
|
ru: 'connect: нужен dsn или sql',
|
|
168
238
|
hint: 'передать dsn — строку подключения — или sql — готовый пул postgres.js',
|
|
169
239
|
},
|
|
240
|
+
// index.ts: connect({ apiKey | service | token }) передан, но пуст (null, '', не строка) — опция есть, а значения нет
|
|
241
|
+
'api.connect_empty_credential': {
|
|
242
|
+
code: 'invalid_data',
|
|
243
|
+
ru: 'connect: {name} передан, но пуст ({value}) — подключение без сессии не открывается',
|
|
244
|
+
hint: 'передать ключ сервиса или токен строкой (process.env.SERVICE_KEY не задана?) — либо убрать опцию, если сессия не нужна; ключ сервиса up() выдаёт один раз — храните его сами',
|
|
245
|
+
},
|
|
170
246
|
// index.ts: connect({ admin }) — не строка и не пул
|
|
171
247
|
'api.connect_admin_type': {
|
|
172
248
|
code: 'invalid_data',
|
|
@@ -212,4 +288,17 @@ export const api = {
|
|
|
212
288
|
code: 'invalid_data',
|
|
213
289
|
ru: 'движок схемы {schema} ревизии {rev} новее библиотеки (она знает {known}) — обновите пакет letopis',
|
|
214
290
|
},
|
|
291
|
+
// up.ts, createTenant(): первый аргумент — не пул postgres() (строка DSN, подключение connect(), объект без begin и
|
|
292
|
+
// unsafe); type — typeof значения или null
|
|
293
|
+
'api.create_tenant_admin': {
|
|
294
|
+
code: 'invalid_query',
|
|
295
|
+
ru: 'createTenant(admin, …): admin — пул postgres() с правами администратора, а не {type}',
|
|
296
|
+
hint: "const admin = postgres(dsnАдминистратора); await createTenant(admin, 'salon', { name }); await admin.end(). DSN-строку и подключение connect() letopis createTenant не принимает",
|
|
297
|
+
},
|
|
298
|
+
// chain.ts, versions(): у результата versions() вызвали метод цепочки (rows, first, sort…); method — его имя
|
|
299
|
+
'api.versions_terminal': {
|
|
300
|
+
code: 'invalid_query',
|
|
301
|
+
ru: 'versions() — терминал: отдаёт строки версий, у результата нет .{method}()',
|
|
302
|
+
hint: "const v = await db.Класс(id).versions(); модификаторы — до versions(): db.Класс(id).sort('at', 'desc').limit(10).versions()",
|
|
303
|
+
},
|
|
215
304
|
};
|
package/dist/messages/boot.js
CHANGED
|
@@ -3,7 +3,7 @@ const LOGIN = 'передать login: { user, password } — имя обычн
|
|
|
3
3
|
/** Подсказка отказов user.roles в createTenant() и up({ tenant }). */
|
|
4
4
|
const ROLES = "передать user.roles — список разных имён ролей членства, например ['staff'], или не передавать: по умолчанию ['owner']";
|
|
5
5
|
/** Подсказка отказов createTenant() об удалённой строке. */
|
|
6
|
-
const GONE = 'вернуть удалённое restore() (текст называет, кто это может) или выбрать другое имя или логин: id вычисляется из них';
|
|
6
|
+
const GONE = 'вернуть удалённое restore({ expect }) — надгробие из withDeleted().rows() (текст называет, кто это может) или выбрать другое имя или логин: id вычисляется из них';
|
|
7
7
|
export const boot = {
|
|
8
8
|
// up.ts, substitute(): в тексте SQL (ядро, миграция, свой сид) маркер, которого установщик не знает
|
|
9
9
|
'boot.unknown_marker': {
|
|
@@ -123,19 +123,19 @@ export const boot = {
|
|
|
123
123
|
// up.ts, createTenant(): строки арендатора нет, а его история в журнале есть
|
|
124
124
|
'boot.tenant_deleted': {
|
|
125
125
|
code: 'invalid_data',
|
|
126
|
-
ru: 'createTenant: арендатор «{name}» ({id}) удалён, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.Account(id).restore() от имени системного администратора или выберите другое имя',
|
|
126
|
+
ru: 'createTenant: арендатор «{name}» ({id}) удалён, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.Account(id).restore({ expect: await db.Account(id).withDeleted().rows() }) от имени системного администратора или выберите другое имя',
|
|
127
127
|
hint: GONE,
|
|
128
128
|
},
|
|
129
129
|
// up.ts, createTenant(): строки аккаунта пользователя нет, а его история в журнале есть
|
|
130
130
|
'boot.tenant_user_deleted': {
|
|
131
131
|
code: 'invalid_data',
|
|
132
|
-
ru: 'createTenant: аккаунт пользователя {login} ({id}) удалён, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.Account(id).restore() от имени системного администратора или выберите другой логин',
|
|
132
|
+
ru: 'createTenant: аккаунт пользователя {login} ({id}) удалён, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.Account(id).restore({ expect: await db.Account(id).withDeleted().rows() }) от имени системного администратора или выберите другой логин',
|
|
133
133
|
hint: GONE,
|
|
134
134
|
},
|
|
135
135
|
// up.ts, createTenant(): строки членства нет, а его история в журнале есть
|
|
136
136
|
'boot.tenant_member_deleted': {
|
|
137
137
|
code: 'invalid_data',
|
|
138
|
-
ru: 'createTenant: членство {login} в арендаторе «{name}» ({id}) удалено, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.member(id).restore() в этом арендаторе или выберите другой логин',
|
|
138
|
+
ru: 'createTenant: членство {login} в арендаторе «{name}» ({id}) удалено, а его история осталась в журнале — повтор удалённое не воскрешает: верните его db.member(id).restore({ expect: await db.member(id).withDeleted().rows() }) в этом арендаторе или выберите другой логин',
|
|
139
139
|
hint: GONE,
|
|
140
140
|
},
|
|
141
141
|
// up.ts, refuse1x(): схема letopis 1.x — ревизия из комментария схемы ниже первой ревизии 2.0 (FIRST_REVISION) или
|
package/dist/messages/codes.js
CHANGED
|
@@ -27,7 +27,7 @@ export const codes = {
|
|
|
27
27
|
'codes.acl_denied': {
|
|
28
28
|
code: 'acl_denied',
|
|
29
29
|
ru: 'правила доступа запрещают это действие',
|
|
30
|
-
hint: '
|
|
30
|
+
hint: 'выдать право `db.acl.grant` (правила — класс `rule`) или выполнить действие от пользователя, которому оно разрешено; проверить заранее — `db.acl.checkData`, `db.acl.check`',
|
|
31
31
|
},
|
|
32
32
|
'codes.id_mismatch': {
|
|
33
33
|
code: 'id_mismatch',
|
|
@@ -72,7 +72,7 @@ export const codes = {
|
|
|
72
72
|
'codes.not_deleted': {
|
|
73
73
|
code: 'not_deleted',
|
|
74
74
|
ru: '`restore` работает только для удалённых объектов',
|
|
75
|
-
hint: 'сначала `delete()`, потом `restore()`',
|
|
75
|
+
hint: 'сначала `delete({ confirm: true, expect })`, потом `restore({ expect })` с надгробиями из ответа `delete()` или `withDeleted().rows()`',
|
|
76
76
|
},
|
|
77
77
|
'codes.exists': {
|
|
78
78
|
code: 'exists',
|
|
@@ -91,8 +91,8 @@ export const codes = {
|
|
|
91
91
|
},
|
|
92
92
|
'codes.conflict': {
|
|
93
93
|
code: 'conflict',
|
|
94
|
-
ru: '`update` с `{ rev }
|
|
95
|
-
hint: 'перечитать
|
|
94
|
+
ru: 'то, что вы видели, уже изменилось — ничего не записано: `update` с `{ rev }` (строку изменили после чтения, `write.rev_conflict`), `delete({ confirm: true, expect })` (превью изменилось, `write.preview_changed`; в том числе цели уже удалены или не найдены — `detail.preview: []`), `restore({ expect })` (объект меняли после удаления, `write.restore_stale`)',
|
|
95
|
+
hint: 'перечитать и повторить с новыми данными: `update` — с новым `rev` (для чисел — `inc` вместо записи итога); `delete` — показать новое превью `err.detail.preview` и удалить с ним; `restore` — сообщить пользователю (вещь уже у другого) или `restore({ expect, partial: true })`',
|
|
96
96
|
},
|
|
97
97
|
'codes.rev_ambiguous': {
|
|
98
98
|
code: 'rev_ambiguous',
|
package/dist/messages/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 1. Раздел — по файлу, где брошена ошибка:
|
|
10
10
|
* api — index, chain, typed, errors, internal; write — write, tx, load; query — sql, ops;
|
|
11
11
|
* time — watch, admin, cache, signals; access — auth, acl; model — model, registry, sync, cli, validate,
|
|
12
|
-
* jsonschema, uuid; boot — up, pglite; mcp — mcp, describe;
|
|
12
|
+
* jsonschema, uuid; boot — up, pglite; mcp — mcp, describe; log — log (и тексты логирования из index, tx);
|
|
13
13
|
* sql-core — sql/10, 15, 20, 30, 40; sql-write — sql/60; sql-access — sql/50, 80;
|
|
14
14
|
* sql-time — sql/70, 90, 95, 99 и sql/migrate.
|
|
15
15
|
* codes.ts — запись на каждый код: заголовок и подсказка. Новый код — ещё ErrorCode (types.ts), для кода базы —
|
|
@@ -56,6 +56,7 @@ import { access } from './access.js';
|
|
|
56
56
|
import { api } from './api.js';
|
|
57
57
|
import { boot } from './boot.js';
|
|
58
58
|
import { codes } from './codes.js';
|
|
59
|
+
import { log } from './log.js';
|
|
59
60
|
import { mcp } from './mcp.js';
|
|
60
61
|
import { model } from './model.js';
|
|
61
62
|
import { query } from './query.js';
|
|
@@ -67,9 +68,11 @@ import { time } from './time.js';
|
|
|
67
68
|
import { write } from './write.js';
|
|
68
69
|
import type { ErrorCode, Msg, Params } from './types.js';
|
|
69
70
|
export type { ErrorCode, Msg, Params, Section } from './types.js';
|
|
71
|
+
/** Каталог событий логирования (не сообщения об ошибках — в MESSAGES не входит). */
|
|
72
|
+
export { LOG_EVENTS, type LogEventInfo, type LogEventLevel, type LogEventName } from './log.js';
|
|
70
73
|
/** Разделы, которые установщик кладёт в базу (message_catalog() в lib/sql/15-errors.sql). */
|
|
71
74
|
export declare const SQL_SECTIONS: readonly ["sql-core", "sql-write", "sql-access", "sql-time"];
|
|
72
|
-
type All = typeof codes & typeof api & typeof write & typeof query & typeof time & typeof access & typeof model & typeof boot & typeof mcp & typeof sqlCore & typeof sqlWrite & typeof sqlAccess & typeof sqlTime;
|
|
75
|
+
type All = typeof codes & typeof api & typeof write & typeof query & typeof time & typeof access & typeof model & typeof boot & typeof mcp & typeof log & typeof sqlCore & typeof sqlWrite & typeof sqlAccess & typeof sqlTime;
|
|
73
76
|
/** Ключ каталога: опечатку в lerr('…') ловит проверка типов. */
|
|
74
77
|
export type MsgKey = keyof All;
|
|
75
78
|
/** Все записи каталога по ключу. */
|
package/dist/messages/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 1. Раздел — по файлу, где брошена ошибка:
|
|
10
10
|
* api — index, chain, typed, errors, internal; write — write, tx, load; query — sql, ops;
|
|
11
11
|
* time — watch, admin, cache, signals; access — auth, acl; model — model, registry, sync, cli, validate,
|
|
12
|
-
* jsonschema, uuid; boot — up, pglite; mcp — mcp, describe;
|
|
12
|
+
* jsonschema, uuid; boot — up, pglite; mcp — mcp, describe; log — log (и тексты логирования из index, tx);
|
|
13
13
|
* sql-core — sql/10, 15, 20, 30, 40; sql-write — sql/60; sql-access — sql/50, 80;
|
|
14
14
|
* sql-time — sql/70, 90, 95, 99 и sql/migrate.
|
|
15
15
|
* codes.ts — запись на каждый код: заголовок и подсказка. Новый код — ещё ErrorCode (types.ts), для кода базы —
|
|
@@ -56,6 +56,7 @@ import { access } from './access.js';
|
|
|
56
56
|
import { api } from './api.js';
|
|
57
57
|
import { boot } from './boot.js';
|
|
58
58
|
import { codes } from './codes.js';
|
|
59
|
+
import { log } from './log.js';
|
|
59
60
|
import { mcp } from './mcp.js';
|
|
60
61
|
import { model } from './model.js';
|
|
61
62
|
import { query } from './query.js';
|
|
@@ -65,9 +66,11 @@ import { sqlTime } from './sql-time.js';
|
|
|
65
66
|
import { sqlWrite } from './sql-write.js';
|
|
66
67
|
import { time } from './time.js';
|
|
67
68
|
import { write } from './write.js';
|
|
69
|
+
/** Каталог событий логирования (не сообщения об ошибках — в MESSAGES не входит). */
|
|
70
|
+
export { LOG_EVENTS } from './log.js';
|
|
68
71
|
/** Разделы каталога: имя — префикс ключа и имя файла. */
|
|
69
72
|
const SECTIONS = {
|
|
70
|
-
codes, api, write, query, time, access, model, boot, mcp,
|
|
73
|
+
codes, api, write, query, time, access, model, boot, mcp, log,
|
|
71
74
|
'sql-core': sqlCore, 'sql-write': sqlWrite, 'sql-access': sqlAccess, 'sql-time': sqlTime,
|
|
72
75
|
};
|
|
73
76
|
/** Разделы, которые установщик кладёт в базу (message_catalog() в lib/sql/15-errors.sql). */
|