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