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/internal.js CHANGED
@@ -4,6 +4,11 @@
4
4
  * фасад db обходит RLS, не полагаясь на то, что сообщает о себе сам вызывающий.
5
5
  */
6
6
  const facades = new WeakMap();
7
+ /**
8
+ * Пределы подключения, которых нет в опциях: реестров классов db.as() — не больше asRegistries арендаторов (план 2.0,
9
+ * 7.4 №8). Тест меняет предел, чтобы проверить вытеснение без тысячи арендаторов.
10
+ */
11
+ export const limits = { asRegistries: 1000 };
7
12
  /** Запомнить фасад (зовёт makeDb для каждого фасада, который отдаёт наружу). */
8
13
  export function registerDb(db, info) {
9
14
  facades.set(db, info);
@@ -14,7 +14,7 @@
14
14
  * группы и обратные ссылки отклоняются;
15
15
  * - имена полей верхнего уровня, начинающиеся с `$`, отклоняются (ими загрузчик помечает служебные ключи).
16
16
  */
17
- import { LetopisError } from './errors.js';
17
+ import { lerr, lmsg } from './errors.js';
18
18
  /** Подмножество, которое понимают база и валидатор. */
19
19
  export const SUPPORTED = new Set([
20
20
  'type', 'enum', 'const', 'minLength', 'maxLength', 'pattern', 'format', 'minimum', 'maximum',
@@ -48,7 +48,7 @@ export function translatePattern(p, where = 'pattern') {
48
48
  if (c === '\\') {
49
49
  const n = p[i + 1];
50
50
  if (n === undefined)
51
- throw bad(where, p, 'обратная косая черта в конце шаблона');
51
+ throw bad(where, p, 'model.pattern_trailing_backslash');
52
52
  i++;
53
53
  if (n === 'd')
54
54
  out += inClass ? '0-9' : '[0-9]';
@@ -58,21 +58,21 @@ export function translatePattern(p, where = 'pattern') {
58
58
  out += inClass ? SPACE_CLASS : `[${SPACE_CLASS}]`;
59
59
  else if (n === 'D' || n === 'W' || n === 'S') {
60
60
  if (inClass)
61
- throw bad(where, p, `\\${n} внутри набора [ ] не переводится`);
61
+ throw bad(where, p, 'model.pattern_escape_in_set', { escape: `\\${n}` });
62
62
  out += n === 'D' ? '[^0-9]' : n === 'W' ? '[^A-Za-z0-9_]' : `[^${SPACE_CLASS}]`;
63
63
  }
64
64
  else if (n === 'b' || n === 'B')
65
- throw bad(where, p, `\\${n} в PostgreSQL значит другое`);
65
+ throw bad(where, p, 'model.pattern_word_boundary', { escape: `\\${n}` });
66
66
  else if (n === 'p' || n === 'P')
67
- throw bad(where, p, '\\p{…} не поддерживается');
67
+ throw bad(where, p, 'model.pattern_unicode_property');
68
68
  else if (n === 'k' || /[1-9]/.test(n))
69
- throw bad(where, p, 'обратные ссылки не поддерживаются');
69
+ throw bad(where, p, 'model.pattern_backreference');
70
70
  else
71
71
  out += `\\${n}`;
72
72
  continue;
73
73
  }
74
74
  if (!inClass && c === '(' && p.startsWith('(?<', i) && p[i + 3] !== '=' && p[i + 3] !== '!') {
75
- throw bad(where, p, 'именованные группы не поддерживаются');
75
+ throw bad(where, p, 'model.pattern_named_group');
76
76
  }
77
77
  if (c === '[' && !inClass)
78
78
  inClass = true;
@@ -82,30 +82,31 @@ export function translatePattern(p, where = 'pattern') {
82
82
  }
83
83
  return out;
84
84
  }
85
- function bad(where, p, why) {
86
- return new LetopisError('invalid_class', `${where}: шаблон ${p} несовместим с регулярными выражениями PostgreSQL (${why})`);
85
+ /** Шаблон несовместим с PostgreSQL (`invalid_class`): ключ каталога называет причину; where и шаблон — параметры. */
86
+ function bad(where, p, key, params) {
87
+ return lerr(key, { where, pattern: p, ...params });
87
88
  }
88
89
  /** Нормализует JSON Schema; бросает LetopisError('invalid_class') с названием поля и ключевого слова. */
89
90
  export function normalizeSchema(input, opts = {}) {
90
- const where = opts.where ?? 'модель';
91
+ const where = opts.where ?? lmsg('model.schema_default_where');
91
92
  const defs = (input.$defs ?? input.definitions ?? {});
92
93
  const resolve = (ref, seen) => {
93
94
  const m = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
94
95
  if (!m)
95
- throw new LetopisError('invalid_class', `${where}: ссылка ${ref} не поддерживается (только #/$defs/…)`);
96
+ throw lerr('model.schema_ref_unsupported', { where, ref });
96
97
  const name = decodeURIComponent(m[1].replace(/~1/g, '/').replace(/~0/g, '~'));
97
98
  if (seen.includes(name))
98
- throw new LetopisError('invalid_class', `${where}: рекурсивная модель через ${[...seen, name].join(' → ')}`);
99
+ throw lerr('model.schema_recursive', { where, cycle: [...seen, name].join(' → ') });
99
100
  const target = defs[name];
100
101
  if (!target)
101
- throw new LetopisError('invalid_class', `${where}: определение ${name} не найдено`);
102
+ throw lerr('model.schema_def_not_found', { where, name });
102
103
  return node(target, [...seen, name], name);
103
104
  };
104
105
  const node = (s, seen, path) => {
105
106
  if (s === true)
106
107
  return {};
107
108
  if (s === null || typeof s !== 'object' || Array.isArray(s)) {
108
- throw new LetopisError('invalid_class', `${where}: описание узла ${path} — не объект`);
109
+ throw lerr('model.schema_node_not_object', { where, node: path });
109
110
  }
110
111
  if (typeof s.$ref === 'string') {
111
112
  const rest = Object.keys(s).filter((k) => k !== '$ref' && !DROPPED.has(k));
@@ -127,7 +128,7 @@ export function normalizeSchema(input, opts = {}) {
127
128
  out[k] = v;
128
129
  }
129
130
  else if (VOCABULARY.has(k)) {
130
- throw new LetopisError('invalid_class', `${where}: ключевое слово ${k} не поддерживается (узел ${path})`);
131
+ throw lerr('model.schema_keyword_unsupported', { where, keyword: k, node: path });
131
132
  }
132
133
  else {
133
134
  meta[k] = v; // пометки .meta() Zod и прочие свободные ключи
@@ -137,7 +138,7 @@ export function normalizeSchema(input, opts = {}) {
137
138
  out['x-meta'] = { ...(out['x-meta'] ?? {}), ...meta };
138
139
  if (typeof out.format === 'string') {
139
140
  if (!FORMATS.has(out.format))
140
- throw new LetopisError('invalid_class', `${where}: format ${out.format} не поддерживается (узел ${path})`);
141
+ throw lerr('model.schema_format_unsupported', { where, format: out.format, node: path });
141
142
  }
142
143
  if (typeof out.pattern === 'string')
143
144
  out.pattern = translatePattern(out.pattern, `${where}: ${path}`);
@@ -160,7 +161,7 @@ export function normalizeSchema(input, opts = {}) {
160
161
  if (opts.topLevel && out.properties && typeof out.properties === 'object') {
161
162
  for (const k of Object.keys(out.properties)) {
162
163
  if (k.startsWith('$'))
163
- throw new LetopisError('invalid_class', `${where}: имя поля ${k} начинается с $ — так загрузчик помечает служебные ключи`);
164
+ throw lerr('model.schema_field_dollar', { where, field: k });
164
165
  }
165
166
  }
166
167
  return out;
package/dist/load.d.ts CHANGED
@@ -17,15 +17,13 @@ export interface LoadOptions {
17
17
  * переносят историю объектов из другой системы.
18
18
  */
19
19
  withHistory?: boolean;
20
- /** В конце перепроверить загруженные строки проверкой базы. */
21
- verify?: boolean;
22
20
  /** Класс строк без $class (CLI --class). */
23
21
  class?: string;
24
22
  /** Арендатор строк (по умолчанию — арендатор сессии). */
25
23
  tenant?: string;
26
24
  /**
27
25
  * Исполнитель записи — строка, которую журнал сохранит в поле `agent` загруженных версий рядом с автором (по умолчанию
28
- * «letopis load»; импорт — «letopis import»).
26
+ * «letopis load»).
29
27
  */
30
28
  agent?: string;
31
29
  /**
@@ -35,12 +33,28 @@ export interface LoadOptions {
35
33
  */
36
34
  as?: string;
37
35
  /**
38
- * Первая заливка (план 1.1, §2.3): только в пустую схему (кроме строк системных классов — иначе
36
+ * Первая заливка: только в пустую схему (кроме строк системных классов — иначе
39
37
  * not_empty), одной транзакцией; на время загрузки схема закрыта, индексы и триггеры сняты и
40
38
  * перестраиваются в конце. Без commitEvery, existing (кроме 'error') и { as }.
41
39
  */
42
40
  initial?: boolean;
43
41
  }
42
+ /**
43
+ * Все опции load(): неизвестная — invalid_data с похожими (checkOptions; план 2.0, 7.10 №4). Фасад db.load() сверяет их
44
+ * до пула администратора (index.ts), load() — ещё раз до проверки прав.
45
+ */
46
+ export declare const LOAD_OPTIONS: {
47
+ duplicates: true;
48
+ existing: true;
49
+ batch: true;
50
+ commitEvery: true;
51
+ withHistory: true;
52
+ class: true;
53
+ tenant: true;
54
+ agent: true;
55
+ as: true;
56
+ initial: true;
57
+ };
44
58
  export interface LoadReport {
45
59
  /** Строк во входе. */
46
60
  rows: number;
@@ -102,8 +116,6 @@ interface Pending {
102
116
  allowed: string[];
103
117
  }>;
104
118
  owners: Map<string, number>;
105
- /** verify: true — id записанных строк. */
106
- verify: string[] | null;
107
119
  }
108
120
  /** Состояние сессии первой заливки. */
109
121
  export interface InitialState {
@@ -115,13 +127,9 @@ export interface InitialState {
115
127
  touched: Map<string, Set<string>>;
116
128
  freeze: boolean;
117
129
  }
118
- /** Первая заливка для нескольких загрузок (импорт): соединение и транзакция сессии. */
130
+ /** Первая заливка для нескольких загрузок: загрузки идут на соединении и в транзакции сессии. */
119
131
  export interface InitialSession {
120
- /** Соединение сессии (роль владельца): свои операторы импорта — в той же транзакции. */
121
- readonly sql: Sql;
122
132
  load(input: LoadInput, opts?: LoadOptions): Promise<LoadReport>;
123
- /** id уже есть в схеме: строка системного класса или объект, записанный этой сессией. */
124
- taken(id: string): boolean;
125
133
  }
126
134
  /**
127
135
  * Загрузить строки (§2.6). Одна транзакция (или порции commitEvery): описания классов под
@@ -132,7 +140,7 @@ export interface InitialSession {
132
140
  export declare function load(env: LoadEnv, input: LoadInput, opts?: LoadOptions): Promise<LoadReport>;
133
141
  /**
134
142
  * Первая заливка (план 1.1, §2.3): загрузки fn — одной транзакцией в пустую схему. Блокировка таблиц до
135
- * проверки; в схеме только строки системных классов и записи reset (иначе not_empty); они (с seq и tx
143
+ * проверки; в схеме только строки системных классов (иначе not_empty); они (с seq и tx
136
144
  * журнала) и определения ограничений, индексов и триггеров из каталога (с режимом включения tgenabled:
137
145
  * pg_get_triggerdef его не передаёт) сохраняются в памяти; внешний ключ secret.account снимается до TRUNCATE и
138
146
  * возвращается после первичного ключа entity; TRUNCATE; триггеры, ограничения и индексы