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
@@ -9,6 +9,13 @@ export interface DbInternals {
9
9
  readonly allTenants: boolean;
10
10
  /** Фасад явной транзакции db.begin(). */
11
11
  readonly explicit: boolean;
12
+ /**
13
+ * Транзакция на администраторском пуле connect({ admin }) с сессией фасада (план 2.0, 7.9 №3): ею пишет syncModels.
14
+ * Только у корня подключения с опцией admin.
15
+ */
16
+ readonly adminTx?: <T>(fn: (tx: import('./tx.js').Tx) => Promise<T>) => Promise<T>;
17
+ /** Логгер фасада (lib/src/log.ts): им пишут syncModels и инструменты MCP. */
18
+ readonly log?: import('./log.js').Log;
12
19
  }
13
20
  /**
14
21
  * Пределы подключения, которых нет в опциях: реестров классов db.as() — не больше asRegistries арендаторов (план 2.0,
@@ -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
@@ -3,6 +3,14 @@ import { type Issue } from './errors.js';
3
3
  import { Registry } from './registry.js';
4
4
  type Sql = postgres.Sql<Record<string, unknown>>;
5
5
  type Raw = Record<string, unknown>;
6
+ /** Ссылка на цель вне загрузки: строка, роль и допустимые классы конца. */
7
+ type ExtRef = {
8
+ line: number;
9
+ role: string;
10
+ allowed: string[];
11
+ };
12
+ /** Цели вне загрузки: id → первая ссылка с каждым набором допустимых классов (сверяется каждый набор). */
13
+ type ExtRefs = Map<string, ExtRef[]>;
6
14
  export interface LoadOptions {
7
15
  /** Повтор id в загрузке: отменить загрузку (по умолчанию), оставить первую или последнюю строку. */
8
16
  duplicates?: 'error' | 'first' | 'last';
@@ -17,15 +25,13 @@ export interface LoadOptions {
17
25
  * переносят историю объектов из другой системы.
18
26
  */
19
27
  withHistory?: boolean;
20
- /** В конце перепроверить загруженные строки проверкой базы. */
21
- verify?: boolean;
22
28
  /** Класс строк без $class (CLI --class). */
23
29
  class?: string;
24
30
  /** Арендатор строк (по умолчанию — арендатор сессии). */
25
31
  tenant?: string;
26
32
  /**
27
33
  * Исполнитель записи — строка, которую журнал сохранит в поле `agent` загруженных версий рядом с автором (по умолчанию
28
- * «letopis load»; импорт — «letopis import»).
34
+ * «letopis load»).
29
35
  */
30
36
  agent?: string;
31
37
  /**
@@ -35,12 +41,28 @@ export interface LoadOptions {
35
41
  */
36
42
  as?: string;
37
43
  /**
38
- * Первая заливка (план 1.1, §2.3): только в пустую схему (кроме строк системных классов — иначе
44
+ * Первая заливка: только в пустую схему (кроме строк системных классов — иначе
39
45
  * not_empty), одной транзакцией; на время загрузки схема закрыта, индексы и триггеры сняты и
40
46
  * перестраиваются в конце. Без commitEvery, existing (кроме 'error') и { as }.
41
47
  */
42
48
  initial?: boolean;
43
49
  }
50
+ /**
51
+ * Все опции load(): неизвестная — invalid_data с похожими (checkOptions; план 2.0, 7.10 №4). Фасад db.load() сверяет их
52
+ * до пула администратора (index.ts), load() — ещё раз до проверки прав.
53
+ */
54
+ export declare const LOAD_OPTIONS: {
55
+ duplicates: true;
56
+ existing: true;
57
+ batch: true;
58
+ commitEvery: true;
59
+ withHistory: true;
60
+ class: true;
61
+ tenant: true;
62
+ agent: true;
63
+ as: true;
64
+ initial: true;
65
+ };
44
66
  export interface LoadReport {
45
67
  /** Строк во входе. */
46
68
  rows: number;
@@ -96,14 +118,8 @@ export interface InitialTuning {
96
118
  interface Pending {
97
119
  reg: Registry;
98
120
  tenant: string;
99
- external: Map<string, {
100
- line: number;
101
- role: string;
102
- allowed: string[];
103
- }>;
121
+ external: ExtRefs;
104
122
  owners: Map<string, number>;
105
- /** verify: true — id записанных строк. */
106
- verify: string[] | null;
107
123
  }
108
124
  /** Состояние сессии первой заливки. */
109
125
  export interface InitialState {
@@ -115,13 +131,9 @@ export interface InitialState {
115
131
  touched: Map<string, Set<string>>;
116
132
  freeze: boolean;
117
133
  }
118
- /** Первая заливка для нескольких загрузок (импорт): соединение и транзакция сессии. */
134
+ /** Первая заливка для нескольких загрузок: загрузки идут на соединении и в транзакции сессии. */
119
135
  export interface InitialSession {
120
- /** Соединение сессии (роль владельца): свои операторы импорта — в той же транзакции. */
121
- readonly sql: Sql;
122
136
  load(input: LoadInput, opts?: LoadOptions): Promise<LoadReport>;
123
- /** id уже есть в схеме: строка системного класса или объект, записанный этой сессией. */
124
- taken(id: string): boolean;
125
137
  }
126
138
  /**
127
139
  * Загрузить строки (§2.6). Одна транзакция (или порции commitEvery): описания классов под
@@ -132,7 +144,7 @@ export interface InitialSession {
132
144
  export declare function load(env: LoadEnv, input: LoadInput, opts?: LoadOptions): Promise<LoadReport>;
133
145
  /**
134
146
  * Первая заливка (план 1.1, §2.3): загрузки fn — одной транзакцией в пустую схему. Блокировка таблиц до
135
- * проверки; в схеме только строки системных классов и записи reset (иначе not_empty); они (с seq и tx
147
+ * проверки; в схеме только строки системных классов (иначе not_empty); они (с seq и tx
136
148
  * журнала) и определения ограничений, индексов и триггеров из каталога (с режимом включения tgenabled:
137
149
  * pg_get_triggerdef его не передаёт) сохраняются в памяти; внешний ключ secret.account снимается до TRUNCATE и
138
150
  * возвращается после первичного ключа entity; TRUNCATE; триггеры, ограничения и индексы