letopis 1.4.2 → 2.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 +329 -281
- package/CHANGELOG.md +418 -0
- package/README.md +572 -1053
- package/dist/acl.d.ts +78 -1
- package/dist/acl.js +264 -13
- package/dist/admin.d.ts +10 -141
- package/dist/admin.js +24 -184
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +5 -15
- package/dist/cache.d.ts +8 -1
- package/dist/cache.js +30 -8
- package/dist/chain.d.ts +18 -41
- package/dist/chain.js +165 -203
- package/dist/cli.js +45 -90
- package/dist/demo/booking.d.ts +352 -36
- package/dist/describe.d.ts +47 -0
- package/dist/describe.js +195 -0
- package/dist/errors.d.ts +59 -13
- package/dist/errors.js +190 -29
- package/dist/index.d.ts +52 -70
- package/dist/index.js +189 -122
- package/dist/internal.d.ts +5 -0
- package/dist/jsonschema.js +18 -17
- package/dist/load.d.ts +20 -12
- package/dist/load.js +207 -181
- package/dist/mcp.d.ts +5 -7
- package/dist/mcp.js +168 -214
- package/dist/messages/access.d.ts +90 -0
- package/dist/messages/access.js +113 -0
- package/dist/messages/api.d.ts +171 -0
- package/dist/messages/api.js +215 -0
- package/dist/messages/boot.d.ts +201 -0
- package/dist/messages/boot.js +256 -0
- package/dist/messages/codes.d.ts +172 -0
- package/dist/messages/codes.js +173 -0
- package/dist/messages/index.d.ts +95 -0
- package/dist/messages/index.js +118 -0
- package/dist/messages/mcp.d.ts +306 -0
- package/dist/messages/mcp.js +387 -0
- package/dist/messages/model.d.ts +233 -0
- package/dist/messages/model.js +283 -0
- package/dist/messages/query.d.ts +131 -0
- package/dist/messages/query.js +158 -0
- package/dist/messages/sql-access.d.ts +70 -0
- package/dist/messages/sql-access.js +87 -0
- package/dist/messages/sql-core.d.ts +150 -0
- package/dist/messages/sql-core.js +181 -0
- package/dist/messages/sql-time.d.ts +71 -0
- package/dist/messages/sql-time.js +88 -0
- package/dist/messages/sql-write.d.ts +246 -0
- package/dist/messages/sql-write.js +270 -0
- package/dist/messages/time.d.ts +23 -0
- package/dist/messages/time.js +29 -0
- package/dist/messages/types.d.ts +27 -0
- package/dist/messages/types.js +5 -0
- package/dist/messages/write.d.ts +405 -0
- package/dist/messages/write.js +509 -0
- package/dist/model.d.ts +73 -60
- package/dist/model.js +20 -29
- package/dist/ops.d.ts +35 -0
- package/dist/ops.js +101 -0
- package/dist/pglite.d.ts +0 -9
- package/dist/pglite.js +3 -1
- package/dist/registry.d.ts +3 -19
- package/dist/registry.js +3 -16
- package/dist/signals.d.ts +1 -5
- package/dist/signals.js +1 -5
- package/dist/sql.d.ts +7 -1
- package/dist/sql.js +101 -85
- package/dist/sync.d.ts +9 -3
- package/dist/sync.js +40 -17
- package/dist/tx.d.ts +11 -1
- package/dist/tx.js +32 -11
- package/dist/typed.d.ts +240 -45
- package/dist/types.d.ts +15 -90
- package/dist/types.js +12 -14
- package/dist/up.d.ts +53 -32
- package/dist/up.js +180 -74
- package/dist/uuid.js +5 -5
- package/dist/validate.d.ts +13 -1
- package/dist/validate.js +61 -29
- package/dist/watch.d.ts +5 -16
- package/dist/watch.js +23 -77
- package/dist/write.d.ts +9 -16
- package/dist/write.js +102 -185
- package/llms.txt +22 -25
- package/package.json +2 -9
- package/sql/10-core.sql +7 -50
- package/sql/15-errors.sql +64 -10
- package/sql/20-context.sql +7 -6
- package/sql/30-validate.sql +35 -27
- package/sql/40-class.sql +20 -114
- package/sql/50-acl.sql +8 -13
- package/sql/60-write.sql +220 -676
- package/sql/70-read.sql +11 -13
- package/sql/80-auth.sql +23 -27
- package/sql/90-time.sql +77 -944
- package/sql/95-seed.system.sql +7 -14
- package/sql/99-revision.sql +2 -1
- package/sql/README.md +32 -24
- package/sql/migrate/README.md +14 -0
- package/AGENT-CHEATSHEET.en.md +0 -488
- package/MIGRATION.md +0 -190
- package/README.en.md +0 -2910
- package/dist/import.d.ts +0 -185
- package/dist/import.js +0 -820
- package/dist/project.d.ts +0 -359
- package/dist/project.js +0 -1258
- package/sql/75-history.sql +0 -436
- package/sql/88-derived.sql +0 -1236
- package/sql/migrate/0006-changed-paths.sql +0 -3
- package/sql/migrate/0007-secret-account-fk.sql +0 -12
package/dist/cli.js
CHANGED
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* CLI letopis (план, этап 2, п. 7; этап 4, п. 11; этап 6, п.
|
|
4
|
-
*
|
|
3
|
+
* CLI letopis (план, этап 2, п. 7; этап 4, п. 11; этап 6, п. 8): `letopis sync`, `types`, `load`, `maintain`; план 1.1,
|
|
4
|
+
* §2.5 — `mcp`; план 2.0, этап 3, п. 8 — `sync --check`.
|
|
5
5
|
*
|
|
6
|
-
* letopis sync <модуль моделей> --dsn … --schema v2.x (--token … | --api-key …) [--apply]
|
|
7
|
-
*
|
|
6
|
+
* letopis sync <модуль моделей> --dsn … --schema v2.x (--token … | --api-key …) [--apply | --check]
|
|
7
|
+
* --check — сухой прогон с кодом 1, если он нашёл изменения (создать или изменить); вместе с --apply — USAGE, код 2
|
|
8
|
+
* letopis types --dsn … --schema v2.x (--token … | --api-key …) [--out types.d.ts] [--system]
|
|
9
|
+
* --system — и классы арендатора System (системные, сиды пакета), без него — только классы арендатора сессии
|
|
8
10
|
* letopis load <файлы NDJSON…> --dsn … --schema v2.x (--token … | --api-key …) [--class C]
|
|
9
|
-
* [--duplicates error|first|last] [--existing error|skip|update] [--commit-every N] [--
|
|
10
|
-
*
|
|
11
|
-
* letopis maintain --dsn … --schema v2.x [--token … | --api-key …]
|
|
12
|
-
* letopis describe --dsn … --schema v2.x (--token … | --api-key …) [--format json|text] [--out файл]
|
|
13
|
-
* letopis import --from <строка 0.21> --from-schema v1.x --dsn … --schema v2.x [--tenant <имя>] [--keyless C]…
|
|
14
|
-
* [--rename Старое=Новое]… [--update] [--commit-every N] [--partition p] [--initial] — код 1, если verify нашёл расхождения
|
|
15
|
-
* --initial — первая заливка (план 1.1, §2.3): пустая схема, одна транзакция, индексы и триггеры — после загрузки
|
|
11
|
+
* [--duplicates error|first|last] [--existing error|skip|update] [--commit-every N] [--initial]
|
|
12
|
+
* --initial — первая заливка (план 1.1, §2.3): пустая схема, одна транзакция, индексы и триггеры — после загрузки
|
|
13
|
+
* letopis maintain --dsn … --schema v2.x [--token … | --api-key …]
|
|
16
14
|
* letopis mcp --dsn … --schema v2.x --token … [--write] — сервер MCP по stdio (план 1.1, §2.5); dsn, schema и
|
|
17
15
|
* token — ещё из LETOPIS_DSN, LETOPIS_SCHEMA, LETOPIS_TOKEN; только токен пользователя: --api-key — отказ
|
|
16
|
+
* справка — USAGE (её же читает справочник сайта, guide/scripts/gen-api.ts)
|
|
18
17
|
*/
|
|
19
18
|
import { parseArgs } from 'node:util';
|
|
20
19
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
21
20
|
import { realpathSync } from 'node:fs';
|
|
22
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
22
|
import { resolve } from 'node:path';
|
|
24
|
-
import { connect
|
|
23
|
+
import { connect } from './index.js';
|
|
25
24
|
import { syncModels, generateTypes } from './sync.js';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
letopis types --dsn <строка> --schema <v2.имя> (--token <токен> | --api-key <ключ>) [--out <файл>]
|
|
25
|
+
import { lerr, lmsg } from './errors.js';
|
|
26
|
+
const USAGE = `letopis sync <модуль> --dsn <строка> --schema <v2.имя> (--token <токен> | --api-key <ключ>) [--apply | --check]
|
|
27
|
+
letopis types --dsn <строка> --schema <v2.имя> (--token <токен> | --api-key <ключ>) [--out <файл>] [--system]
|
|
30
28
|
letopis load <файлы…> --dsn <строка> --schema <v2.имя> (--token <токен> | --api-key <ключ>) [--class <Класс>]
|
|
31
|
-
[--duplicates error|first|last] [--existing error|skip|update] [--commit-every <N>] [--
|
|
32
|
-
letopis
|
|
33
|
-
letopis maintain --dsn <строка> --schema <v2.имя> [--token <токен> | --api-key <ключ>] [--batches <N>]
|
|
34
|
-
letopis describe --dsn <строка> --schema <v2.имя> (--token <токен> | --api-key <ключ>) [--format json|text] [--out <файл>]
|
|
35
|
-
letopis import --from <строка 0.21> --from-schema <v1.имя> --dsn <строка> --schema <v2.имя> [--tenant <имя>] [--keyless <Класс>]…
|
|
36
|
-
[--rename <Старое>=<Новое>]… [--update] [--commit-every <N>] [--partition <имя>] [--initial]
|
|
29
|
+
[--duplicates error|first|last] [--existing error|skip|update] [--commit-every <N>] [--initial]
|
|
30
|
+
letopis maintain --dsn <строка> --schema <v2.имя> [--token <токен> | --api-key <ключ>]
|
|
37
31
|
letopis mcp --dsn <строка> --schema <v2.имя> --token <токен пользователя> [--write]
|
|
38
32
|
(или LETOPIS_DSN, LETOPIS_SCHEMA, LETOPIS_TOKEN; ключ сервиса --api-key не принимается)`;
|
|
39
33
|
/** Команды CLI: остальное — USAGE с кодом 2 ещё до подключения. */
|
|
40
|
-
const COMMANDS = new Set(['sync', 'types', 'load', '
|
|
34
|
+
const COMMANDS = new Set(['sync', 'types', 'load', 'maintain', 'mcp']);
|
|
41
35
|
const isModel = (v) => typeof v === 'object' && v !== null && typeof v.describe === 'function' && typeof v.name === 'string';
|
|
36
|
+
/** Модели из модуля (letopis sync): экспорты-модели и массивы моделей, без повторов. */
|
|
37
|
+
async function loadModels(path) {
|
|
38
|
+
const mod = await import(pathToFileURL(resolve(path)).href);
|
|
39
|
+
return [...new Set(Object.values(mod).flatMap((v) => (Array.isArray(v) ? v : [v])).filter(isModel))];
|
|
40
|
+
}
|
|
42
41
|
export async function main(argv) {
|
|
43
42
|
const { values, positionals } = parseArgs({
|
|
44
43
|
args: argv,
|
|
@@ -47,11 +46,7 @@ export async function main(argv) {
|
|
|
47
46
|
dsn: { type: 'string' }, schema: { type: 'string' }, token: { type: 'string' }, 'api-key': { type: 'string' },
|
|
48
47
|
apply: { type: 'boolean' }, out: { type: 'string' }, help: { type: 'boolean' },
|
|
49
48
|
class: { type: 'string' }, duplicates: { type: 'string' }, existing: { type: 'string' }, 'commit-every': { type: 'string' },
|
|
50
|
-
|
|
51
|
-
format: { type: 'string' },
|
|
52
|
-
from: { type: 'string' }, 'from-schema': { type: 'string' }, tenant: { type: 'string' }, partition: { type: 'string' },
|
|
53
|
-
keyless: { type: 'string', multiple: true }, rename: { type: 'string', multiple: true }, update: { type: 'boolean' },
|
|
54
|
-
initial: { type: 'boolean' }, write: { type: 'boolean' },
|
|
49
|
+
initial: { type: 'boolean' }, write: { type: 'boolean' }, system: { type: 'boolean' }, check: { type: 'boolean' },
|
|
55
50
|
},
|
|
56
51
|
});
|
|
57
52
|
const [cmd, arg] = positionals;
|
|
@@ -66,57 +61,35 @@ export async function main(argv) {
|
|
|
66
61
|
console.log(USAGE);
|
|
67
62
|
return 0;
|
|
68
63
|
}
|
|
69
|
-
|
|
64
|
+
// --check — сухой прогон: вместе с --apply смысла нет
|
|
65
|
+
if (!cmd || !COMMANDS.has(cmd) || !values.dsn || !values.schema || (cmd === 'sync' && values.check && values.apply)) {
|
|
70
66
|
console.log(USAGE);
|
|
71
67
|
return cmd ? 2 : 0;
|
|
72
68
|
}
|
|
73
|
-
if (cmd === 'import') {
|
|
74
|
-
// импорт сам открывает администраторские подключения к 0.21 и к 1.0 (загрузчик, функции владельца)
|
|
75
|
-
if (!values.from || !values['from-schema']) {
|
|
76
|
-
console.log(USAGE);
|
|
77
|
-
return 2;
|
|
78
|
-
}
|
|
79
|
-
const rename = {};
|
|
80
|
-
for (const x of values.rename ?? []) {
|
|
81
|
-
const [from, to] = x.split('=');
|
|
82
|
-
if (!from || !to) {
|
|
83
|
-
console.log(USAGE);
|
|
84
|
-
return 2;
|
|
85
|
-
}
|
|
86
|
-
rename[from] = to;
|
|
87
|
-
}
|
|
88
|
-
const r = await import021({
|
|
89
|
-
from: values.from, fromSchema: values['from-schema'], dsn: values.dsn, schema: values.schema, tenant: values.tenant,
|
|
90
|
-
partition: values.partition, keyless: values.keyless, rename, update: values.update,
|
|
91
|
-
commitEvery: values['commit-every'] ? Number(values['commit-every']) : undefined, initial: values.initial,
|
|
92
|
-
});
|
|
93
|
-
console.log(JSON.stringify(r, null, 2));
|
|
94
|
-
return r.verify.ok ? 0 : 1;
|
|
95
|
-
}
|
|
96
69
|
if (cmd === 'mcp')
|
|
97
70
|
return mcp(values);
|
|
98
71
|
// загрузчику нужно администраторское подключение (§2.6, п. 1)
|
|
99
72
|
const db = await connect({ dsn: values.dsn, schema: values.schema, token: values.token, apiKey: values['api-key'], max: 2,
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
...(cmd === 'load' || cmd === '
|
|
73
|
+
// maintain работает и на администраторском подключении без сессии; классы со строками синхронизирует
|
|
74
|
+
// администраторское подключение с сессией арендатора
|
|
75
|
+
...(cmd === 'load' || cmd === 'maintain' || cmd === 'sync' ? { allowBypassRls: true } : {}) });
|
|
103
76
|
try {
|
|
104
77
|
if (cmd === 'sync') {
|
|
105
78
|
if (!arg) {
|
|
106
79
|
console.log(USAGE);
|
|
107
80
|
return 2;
|
|
108
81
|
}
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
for (const [k, list] of [['создать', r.created], ['изменить', r.updated], ['системные (меняет установка)', r.skipped]]) {
|
|
82
|
+
const r = await syncModels(db, await loadModels(arg), { apply: values.apply });
|
|
83
|
+
const counts = { created: r.created.length, updated: r.updated.length, unchanged: r.unchanged.length, skipped: r.skipped.length };
|
|
84
|
+
console.log(lmsg(r.applied ? 'model.cli_sync_applied' : 'model.cli_sync_dry_run', counts));
|
|
85
|
+
for (const [key, list] of [['model.cli_sync_list_create', r.created], ['model.cli_sync_list_update', r.updated], ['model.cli_sync_list_system', r.skipped]]) {
|
|
114
86
|
if (list.length)
|
|
115
|
-
console.log(` ${
|
|
87
|
+
console.log(` ${lmsg(key, { names: list.join(', ') })}`);
|
|
116
88
|
}
|
|
117
89
|
for (const l of r.lost)
|
|
118
|
-
console.log(`
|
|
119
|
-
|
|
90
|
+
console.log(` ${lmsg('model.cli_sync_lost', { kind: l.kind, path: l.path })}`);
|
|
91
|
+
// --check (план 2.0, этап 3, п. 8): модели расходятся с базой — код 1, например для шага CI перед --apply
|
|
92
|
+
return values.check && (r.created.length || r.updated.length) ? 1 : 0;
|
|
120
93
|
}
|
|
121
94
|
if (cmd === 'load') {
|
|
122
95
|
const files = positionals.slice(1);
|
|
@@ -124,8 +97,6 @@ export async function main(argv) {
|
|
|
124
97
|
console.log(USAGE);
|
|
125
98
|
return 2;
|
|
126
99
|
}
|
|
127
|
-
if (values.trust)
|
|
128
|
-
throw new LetopisError('removed', '--trust снят в 1.0.0 — запрос по целям вне загрузки стоит 0,1–1,3 % её времени');
|
|
129
100
|
// несколько файлов — одна загрузка и одна транзакция; в файле — объект на строку
|
|
130
101
|
const rows = [];
|
|
131
102
|
for (const f of files) {
|
|
@@ -138,39 +109,23 @@ export async function main(argv) {
|
|
|
138
109
|
duplicates: values.duplicates,
|
|
139
110
|
existing: values.existing,
|
|
140
111
|
commitEvery: values['commit-every'] ? Number(values['commit-every']) : undefined,
|
|
141
|
-
verify: values.verify,
|
|
142
112
|
initial: values.initial,
|
|
143
113
|
});
|
|
144
114
|
console.log(JSON.stringify(r));
|
|
145
115
|
return 0;
|
|
146
116
|
}
|
|
147
|
-
if (cmd === 'verify') {
|
|
148
|
-
const r = await db.verify({ data: values.data });
|
|
149
|
-
console.log(JSON.stringify(r, null, 2));
|
|
150
|
-
return r.ok ? 0 : 1;
|
|
151
|
-
}
|
|
152
117
|
if (cmd === 'maintain') {
|
|
153
|
-
const r = await db.maintain(
|
|
118
|
+
const r = await db.maintain();
|
|
154
119
|
console.log(JSON.stringify(r));
|
|
155
120
|
return 0;
|
|
156
121
|
}
|
|
157
|
-
if (cmd === 'describe' || cmd === 'types') {
|
|
158
|
-
// обе команды читают реестр сессии: без действующей сессии (токен истёк, погашен, неверен или не задан) он пуст,
|
|
159
|
-
// и команда выдала бы пустой результат с кодом 0, а с --out затёрла бы им файл. acl.check ничего не пишет, а без
|
|
160
|
-
// сессии отказывает no_session — тогда выход с кодом 1, файл не тронут
|
|
161
|
-
await db.acl.check('letopis.cli');
|
|
162
|
-
}
|
|
163
|
-
if (cmd === 'describe') {
|
|
164
|
-
const d = db.describe();
|
|
165
|
-
const text = values.format === 'text' ? describeText(d) : JSON.stringify(d, null, 2);
|
|
166
|
-
if (values.out)
|
|
167
|
-
await writeFile(values.out, text);
|
|
168
|
-
else
|
|
169
|
-
console.log(text);
|
|
170
|
-
return 0;
|
|
171
|
-
}
|
|
172
122
|
if (cmd === 'types') {
|
|
173
|
-
|
|
123
|
+
// реестр сессии без действующей сессии (токен истёк, погашен, неверен или не задан) пуст, и команда выдала бы
|
|
124
|
+
// пустой результат с кодом 0, а с --out затёрла бы им файл. acl.check ничего не пишет, а без сессии отказывает
|
|
125
|
+
// no_session — тогда выход с кодом 1, файл не тронут
|
|
126
|
+
await db.acl.check('letopis.cli');
|
|
127
|
+
// --system — и классы арендатора System: системные и классы сидов пакета (например, booking)
|
|
128
|
+
const text = generateTypes(db.registry, { includeSystem: values.system === true });
|
|
174
129
|
if (values.out)
|
|
175
130
|
await writeFile(values.out, text);
|
|
176
131
|
else
|
|
@@ -192,11 +147,11 @@ export async function main(argv) {
|
|
|
192
147
|
*/
|
|
193
148
|
async function mcp(v) {
|
|
194
149
|
if (v['api-key'] !== undefined) {
|
|
195
|
-
console.error(
|
|
150
|
+
console.error(lerr('model.mcp_api_key_refused').message);
|
|
196
151
|
return 2;
|
|
197
152
|
}
|
|
198
153
|
if (!v.token) {
|
|
199
|
-
console.error('
|
|
154
|
+
console.error(lerr('model.mcp_token_required').message);
|
|
200
155
|
return 2;
|
|
201
156
|
}
|
|
202
157
|
const { createMcpServer } = await import('./mcp.js');
|