letopis 1.3.0 → 1.4.1

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 (56) hide show
  1. package/AGENT-CHEATSHEET.en.md +14 -6
  2. package/AGENT-CHEATSHEET.md +18 -10
  3. package/CHANGELOG.md +247 -0
  4. package/README.en.md +146 -58
  5. package/README.md +129 -51
  6. package/dist/acl.d.ts +11 -5
  7. package/dist/admin.d.ts +30 -14
  8. package/dist/admin.js +42 -16
  9. package/dist/auth.d.ts +61 -17
  10. package/dist/auth.js +16 -2
  11. package/dist/cache.d.ts +5 -1
  12. package/dist/chain.d.ts +91 -15
  13. package/dist/chain.js +47 -4
  14. package/dist/cli.js +6 -0
  15. package/dist/errors.d.ts +2 -2
  16. package/dist/errors.js +5 -5
  17. package/dist/import.js +7 -0
  18. package/dist/index.d.ts +74 -45
  19. package/dist/index.js +18 -4
  20. package/dist/load.d.ts +10 -3
  21. package/dist/load.js +19 -4
  22. package/dist/mcp.d.ts +20 -11
  23. package/dist/mcp.js +4 -4
  24. package/dist/model.d.ts +4 -1
  25. package/dist/ops.d.ts +58 -18
  26. package/dist/ops.js +58 -18
  27. package/dist/project.d.ts +44 -12
  28. package/dist/project.js +90 -8
  29. package/dist/registry.d.ts +4 -1
  30. package/dist/registry.js +4 -1
  31. package/dist/sql.d.ts +4 -1
  32. package/dist/sql.js +12 -4
  33. package/dist/sync.js +11 -5
  34. package/dist/tx.js +10 -1
  35. package/dist/types.d.ts +14 -7
  36. package/dist/types.js +13 -6
  37. package/dist/up.d.ts +65 -25
  38. package/dist/up.js +152 -40
  39. package/dist/validate.d.ts +9 -2
  40. package/dist/validate.js +19 -3
  41. package/dist/watch.d.ts +17 -5
  42. package/dist/watch.js +40 -8
  43. package/dist/write.d.ts +3 -2
  44. package/dist/write.js +29 -10
  45. package/llms.txt +1 -1
  46. package/package.json +1 -1
  47. package/sql/10-core.sql +6 -1
  48. package/sql/50-acl.sql +5 -1
  49. package/sql/60-write.sql +77 -22
  50. package/sql/80-auth.sql +39 -2
  51. package/sql/88-derived.sql +82 -1
  52. package/sql/90-time.sql +75 -38
  53. package/sql/95-seed.system.sql +6 -3
  54. package/sql/99-revision.sql +3 -2
  55. package/sql/README.md +2 -0
  56. package/sql/migrate/0007-secret-account-fk.sql +12 -0
package/dist/sync.js CHANGED
@@ -12,13 +12,19 @@ export async function syncModels(db, models, opts = {}) {
12
12
  const res = { applied: Boolean(opts.apply), created: [], updated: [], unchanged: [], skipped: [], lost: models.flatMap((m) => m.lostRules()) };
13
13
  const S = `"${db.schema.replace(/"/g, '""')}"`;
14
14
  // предок раньше потомка; класс-источник агрегата раньше цели: агрегат над классом, которого ещё нет, база отклоняет.
15
- // Цикл агрегатов база отклоняет сама — обход его только не зацикливает
15
+ // Круг (агрегат над собственным потомком, цикл агрегатов) так не упорядочить — отказ до записи с самим кругом (хвост
16
+ // ревью доработок после 1.2.0: раньше обход молча ставил потомка раньше предка)
16
17
  const ordered = [];
17
- const visiting = new Set();
18
+ const path = [];
18
19
  const visit = (m) => {
19
- if (ordered.includes(m) || visiting.has(m))
20
+ if (ordered.includes(m))
20
21
  return;
21
- visiting.add(m);
22
+ const at = path.indexOf(m);
23
+ if (at >= 0) {
24
+ throw new LetopisError('invalid_class', `syncModels: модели зависят друг от друга по кругу: ${[...path.slice(at), m].map((x) => x.name).join(' → ')} — `
25
+ + 'родителя и источник агрегата база ждёт раньше класса');
26
+ }
27
+ path.push(m);
22
28
  if (m.parent && models.includes(m.parent))
23
29
  visit(m.parent);
24
30
  for (const a of Object.values(m.describe().aggregate ?? {})) {
@@ -26,7 +32,7 @@ export async function syncModels(db, models, opts = {}) {
26
32
  if (src)
27
33
  visit(src);
28
34
  }
29
- visiting.delete(m);
35
+ path.pop();
30
36
  ordered.push(m);
31
37
  };
32
38
  models.forEach(visit);
package/dist/tx.js CHANGED
@@ -282,7 +282,16 @@ export class ExplicitTx {
282
282
  let changed = null;
283
283
  try {
284
284
  // чтение letopis.changed и COMMIT — одним конвейером (как в runTx); обрыв — исход неизвестен
285
- const [rows, res] = await Promise.all([this.conn.unsafe(`select current_setting('letopis.changed', true) as changed`), this.conn.unsafe('commit')]);
285
+ const read = this.conn.unsafe(`select current_setting('letopis.changed', true) as changed`);
286
+ const commit = this.conn.unsafe('commit');
287
+ const [rows, res] = (await Promise.all([read, commit]).catch(async (e) => {
288
+ // транзакцию уже прервала ошибка, которую код внутри db.begin() поймал и проглотил: чтение получает 25P02,
289
+ // а COMMIT того же конвейера отвечает ROLLBACK — это tx_rolled_back, а не сырая ошибка PostgreSQL
290
+ const tail = e.code === '25P02' ? await commit.catch(() => null) : null;
291
+ if (tail?.command === 'ROLLBACK')
292
+ return [[], tail];
293
+ throw e;
294
+ }));
286
295
  changed = rows[0]?.changed ?? null;
287
296
  if (res.command === 'ROLLBACK') {
288
297
  this.failure = new LetopisError('tx_rolled_back', 'commit: PostgreSQL откатил транзакцию (в ней была ошибка)');
package/dist/types.d.ts CHANGED
@@ -1,23 +1,30 @@
1
1
  /**
2
2
  * Общие типы letopis 1.0. Файл наполняется по этапам плана (reports/PLAN-LETOPIS-1.0.md).
3
3
  */
4
- /** Ревизия движка в базе: штамп в lib/sql/99-revision.sql должен совпадать (этап 1). */
5
- export declare const DDL_REVISION = 6;
4
+ /**
5
+ * Ревизия схемы, которую знает эта версия пакета, — номер версии SQL-части letopis (таблиц и функций в схеме установки).
6
+ * После `up()` у схемы та же ревизия (`r.revision`); схему отставшей ревизии `up()` обновляет только с `upgrade: true`,
7
+ * а `connect()` о ней предупреждает.
8
+ */
9
+ export declare const DDL_REVISION = 7;
6
10
  /**
7
11
  * Классы, строки которых меняют права и схему: их запись повышает счётчик прав (загрузчик), а сигнал
8
12
  * сбрасывает весь кэш результатов и пропуска прав. Тот же список — в триггерах базы (60-write.sql).
9
13
  */
10
14
  export declare const ACL_CLASS_NAMES: readonly string[];
11
- /** Методы цепочки: терминалы, модификаторы, глаголы записи (этап 4), охранники снесённого API. */
15
+ /**
16
+ * Имена методов цепочки: модификаторы, терминалы, глаголы записи и слоты концов, а также `then` и снятые методы прежних
17
+ * версий. После шага цепочки эти имена библиотека не ищет среди классов; все они входят в `RESERVED_CLASS_NAMES`.
18
+ */
12
19
  export declare const CHAIN_METHODS: readonly ["then", "entity", "paths", "rows", "first", "ids", "count", "limit", "offset", "sort", "asOf", "withDeleted", "deep", "exact", "sum", "avg", "min", "max", "countBy", "after", "versions", "create", "update", "upsert", "set", "unset", "add", "remove", "delete", "anonymize", "reclass", "restore", "purge", "alias", "tags", "owner", "forUpdate", "cache", "rekey", "run", "execute", "account", "diff", "blame"];
13
20
  /** Свойства корня db (и транзакции db.begin()). */
14
21
  export declare const DB_METHODS: readonly ["as", "begin", "commit", "rollback", "lock", "batch", "watch", "close", "registry", "sql", "load", "verify", "maintain", "reset", "describe", "auth", "acl", "idOf", "refresh", "transaction", "flushSignals", "schema", "token", "tenant", "reloadSchema", "accounts", "credentials", "resources", "rules", "compact", "trimHistory", "rekeyClass", "cache", "projector", "rebuildAggregates"];
15
22
  /** Свойства батча db.batch(name). */
16
23
  export declare const BATCH_METHODS: readonly ["discard", "size"];
17
24
  /**
18
- * Зарезервированные имена: Proxy разбирает их раньше классов, поэтому класс с таким именем
19
- * недостижим как шаг. Установщик подставляет список в функцию базы reserved_names(), которая
20
- * отклоняет связи с такими именами; совпадение со switch Proxy проверяет check-docs.
25
+ * Имена методов цепочки, корня `db` и батча. Там, где имя занято методом, библиотека не ищет его среди классов, и шаг с
26
+ * таким именем до класса не дойдёт; класс-связь (`link`) с таким именем база не создаст (`invalid_class`). Не давайте
27
+ * классам эти имена.
21
28
  */
22
29
  export declare const RESERVED_CLASS_NAMES: readonly string[];
23
30
  /** Строка ответа: текущая версия (или версия журнала для asOf, versions, withDeleted). */
@@ -78,7 +85,7 @@ export type Filter = string | string[] | Op | {
78
85
  } | {
79
86
  [field: string]: FieldFilter | undefined;
80
87
  };
81
- /** Курсор keyset-пагинации: значение поля сортировки и id последней строки страницы. */
88
+ /** Курсор постраничного вывода для `after()`: значение поля сортировки и id последней строки страницы. */
82
89
  export interface Cursor {
83
90
  v: string | number | boolean | null;
84
91
  id: string;
package/dist/types.js CHANGED
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Общие типы letopis 1.0. Файл наполняется по этапам плана (reports/PLAN-LETOPIS-1.0.md).
3
3
  */
4
- /** Ревизия движка в базе: штамп в lib/sql/99-revision.sql должен совпадать (этап 1). */
5
- export const DDL_REVISION = 6;
4
+ /**
5
+ * Ревизия схемы, которую знает эта версия пакета, — номер версии SQL-части letopis (таблиц и функций в схеме установки).
6
+ * После `up()` у схемы та же ревизия (`r.revision`); схему отставшей ревизии `up()` обновляет только с `upgrade: true`,
7
+ * а `connect()` о ней предупреждает.
8
+ */
9
+ export const DDL_REVISION = 7;
6
10
  /**
7
11
  * Классы, строки которых меняют права и схему: их запись повышает счётчик прав (загрузчик), а сигнал
8
12
  * сбрасывает весь кэш результатов и пропуска прав. Тот же список — в триггерах базы (60-write.sql).
@@ -11,7 +15,10 @@ export const ACL_CLASS_NAMES = ['rule', 'Resource', 'member', 'Account', 'Class'
11
15
  // ---------------------------------------------------------------------------
12
16
  // Методы цепочки — единственный источник зарезервированных имён (план, §2.14, этап 3, п. 10)
13
17
  // ---------------------------------------------------------------------------
14
- /** Методы цепочки: терминалы, модификаторы, глаголы записи (этап 4), охранники снесённого API. */
18
+ /**
19
+ * Имена методов цепочки: модификаторы, терминалы, глаголы записи и слоты концов, а также `then` и снятые методы прежних
20
+ * версий. После шага цепочки эти имена библиотека не ищет среди классов; все они входят в `RESERVED_CLASS_NAMES`.
21
+ */
15
22
  export const CHAIN_METHODS = [
16
23
  'then', 'entity', 'paths', 'rows', 'first', 'ids', 'count', 'limit', 'offset', 'sort', 'asOf', 'withDeleted',
17
24
  'deep', 'exact', 'sum', 'avg', 'min', 'max', 'countBy', 'after', 'versions', 'create', 'update', 'upsert', 'set',
@@ -28,9 +35,9 @@ export const DB_METHODS = [
28
35
  /** Свойства батча db.batch(name). */
29
36
  export const BATCH_METHODS = ['discard', 'size'];
30
37
  /**
31
- * Зарезервированные имена: Proxy разбирает их раньше классов, поэтому класс с таким именем
32
- * недостижим как шаг. Установщик подставляет список в функцию базы reserved_names(), которая
33
- * отклоняет связи с такими именами; совпадение со switch Proxy проверяет check-docs.
38
+ * Имена методов цепочки, корня `db` и батча. Там, где имя занято методом, библиотека не ищет его среди классов, и шаг с
39
+ * таким именем до класса не дойдёт; класс-связь (`link`) с таким именем база не создаст (`invalid_class`). Не давайте
40
+ * классам эти имена.
34
41
  */
35
42
  export const RESERVED_CLASS_NAMES = [...new Set([...CHAIN_METHODS, ...DB_METHODS, ...BATCH_METHODS])];
36
43
  /** Метка операторов фильтра (символ не пересекается с данными). */
package/dist/up.d.ts CHANGED
@@ -8,28 +8,36 @@ export interface InstallOptions {
8
8
  schema: string;
9
9
  owner?: string;
10
10
  app?: string;
11
- /** Константа установки: разрешён ли reset (план, §2.2). По умолчанию выключен. */
11
+ /**
12
+ * Разрешить `db.reset()` и `maintain({ now })` — для тестовых установок и стендов; по умолчанию запрещено. Значение
13
+ * задаётся при установке и хранится в схеме: каждый `up()` записывает его заново (план, §2.2).
14
+ */
12
15
  allowReset?: boolean;
13
16
  /**
14
- * Константа установки: политика хранения истории для классов без своей (план, §2.11), например
15
- * `{ all: '1 day', daily: '1 week', weekly: '1 month', monthly: '1 year', yearly: 'forever' }`.
16
- * Не задана — история хранится вся.
17
+ * Политика хранения истории для классов без своей, например
18
+ * `{ all: '1 day', daily: '1 week', weekly: '1 month', monthly: '1 year', yearly: 'forever' }`; не задана — история
19
+ * хранится вся. Значение задаётся при установке и хранится в схеме: каждый `up()` записывает его заново (план, §2.11).
17
20
  */
18
21
  history?: HistoryPolicy;
19
22
  /** Номер последнего накатываемого файла — для пошаговых тестов. */
20
23
  upTo?: number;
21
24
  /** Сиды после ядра: имена lib/sql/seed.<имя>.sql (например 'booking') или пути к своим файлам .sql. */
22
25
  seeds?: string[];
23
- /** Обновление (upgrade): ревизия схемы до наката — миграции lib/sql/migrate/ с номером выше неё. */
26
+ /**
27
+ * Обновление (upgrade): ревизия схемы до наката — миграции lib/sql/migrate/ с номером выше неё. Если под блокировкой
28
+ * схемы видна более новая ревизия (её уже обновил одновременный up()), миграции считаются от неё.
29
+ */
24
30
  migrateFrom?: number;
31
+ /** Удалить схему и поставить заново (up({ fresh })) — под той же блокировкой схемы, что и накат. ДАННЫЕ ТЕРЯЮТСЯ. */
32
+ fresh?: boolean;
25
33
  /** Каталог миграций (по умолчанию lib/sql/migrate). */
26
34
  migrationsDir?: string;
27
35
  /** Свой ключ сервиса: зарегистрировать, если такого ещё нет (вместо выпуска случайного). */
28
36
  serviceKey?: string;
29
37
  /**
30
- * Режим сигналов NOTIFY (план 1.1, §2.2): 'class' (по умолчанию, как в 1.0) — имена классов; 'tenant' —
31
- * имя класса и токен арендатора; 'opaque' — только токены; 'none' — сигналов нет. Режим хранится в
32
- * схеме: накат без опции его не меняет, reset({ all }) — сохраняет.
38
+ * Что видно в сигналах базы (`NOTIFY`): `'class'` (у новой схемы по умолчанию) — имя класса; `'tenant'` — имя класса и
39
+ * псевдоним арендатора (токен); `'opaque'` — только псевдонимы класса и арендатора; `'none'` — сигналов нет.
40
+ * Режим хранится в схеме: `up()` без опции его не меняет, `reset({ all })` сохраняет (план 1.1, §2.2).
33
41
  */
34
42
  signals?: SignalMode;
35
43
  }
@@ -47,7 +55,11 @@ export interface InstallResult {
47
55
  schema: string;
48
56
  owner: string;
49
57
  app: string;
50
- /** Ключ сервисного аккаунта; выдаётся только при первой установке (или зарегистрированный свой), дальше — null. */
58
+ /**
59
+ * Ключ API сервисного аккаунта, выпущенный или зарегистрированный этим накатом, иначе `null`. Без опции `serviceKey`
60
+ * новый ключ выпускается на любом накате, где в схеме не осталось ни одного неотозванного ключа API, — не только при
61
+ * первой установке; свой `serviceKey` регистрируется и возвращается, если неотозванного ключа с таким значением ещё нет.
62
+ */
51
63
  serviceKey: string | null;
52
64
  /** Применённые миграции. */
53
65
  migrations: string[];
@@ -57,8 +69,27 @@ export interface InstallResult {
57
69
  export declare const quoteIdent: (name: string) => string;
58
70
  /** Файлы ядра по номеру: `10-core.sql` … `99-revision.sql`; сиды демо-домена сюда не входят. */
59
71
  export declare function engineFiles(upTo?: number): Promise<string[]>;
72
+ /** Штамп движка: версия пакета и хэш файлов ядра (`99-revision.sql` пишет его в комментарий схемы). */
73
+ export interface EngineStamp {
74
+ version: string;
75
+ sql: string;
76
+ }
77
+ /**
78
+ * Штамп движка этого пакета. Хэш — по исходному тексту файлов ядра до подстановок (переводы строк — LF): одна и та же
79
+ * сборка даёт один штамп при любых опциях установки, а версия пакета без правок SQL штамп не меняет.
80
+ */
81
+ export declare function engineStamp(): Promise<EngineStamp>;
82
+ /** Сравнение версий semver (метаданные сборки не учитываются): меньше нуля, ноль, больше нуля. */
83
+ export declare function compareVersions(a: string, b: string): number;
84
+ /**
85
+ * Штамп схемы (комментарий схемы) против файлов ядра пакета — текст предупреждения или null. Ревизия та же, а
86
+ * функции поставила другая версия движка: после обновления пакета не накатили up() (так было при 1.1 → 1.2).
87
+ */
88
+ export declare function stampDrift(schema: string, comment: string): Promise<string | null>;
60
89
  export declare function substitute(text: string, vars: Record<string, string>): string;
61
- export declare function install(sql: postgres.Sql, opts: InstallOptions): Promise<InstallResult>;
90
+ export declare function install(sql: postgres.Sql, opts: InstallOptions): Promise<InstallResult & {
91
+ found: number | null;
92
+ }>;
62
93
  /** Миграции с номером выше ревизии from и не выше DDL_REVISION: `0004-описание.sql`, по номеру. */
63
94
  export declare function migrationFiles(dir: string, from: number): Promise<string[]>;
64
95
  export interface UpOptions extends Omit<InstallOptions, 'schema' | 'upTo' | 'migrateFrom'> {
@@ -77,11 +108,16 @@ export interface UpOptions extends Omit<InstallOptions, 'schema' | 'upTo' | 'mig
77
108
  /** Сколько ждать базу, мс (по умолчанию 60 000). */
78
109
  waitTimeoutMs?: number;
79
110
  /**
80
- * База для разработки без PostgreSQL (решение 6 точки А): PGlite с pgcrypto через pglite-socket;
81
- * одно соединение — connect({ dsn: r.pglite.dsn, listen: false, max: 1 }); не для гонок и CI.
111
+ * Запустить PGlite — PostgreSQL прямо в процессе Node — для разработки без своего сервера (решение 6 точки А);
112
+ * подключение к нему — `connect({ dsn: r.pglite.dsn, schema: r.schema, listen: false, max: 1, allowBypassRls: true })`.
113
+ * Соединение у PGlite одно, поэтому одновременные транзакции и гонки на нём не проверить: для таких тестов и в CI нужен
114
+ * настоящий PostgreSQL.
82
115
  */
83
116
  pglite?: boolean | PgliteOptions;
84
- /** Арендатор и его первый пользователь — без своего сида (createTenant). */
117
+ /**
118
+ * Арендатор и его первый пользователь: после установки `up()` заводит их сам, как `createTenant()` с этими опциями, и
119
+ * кладёт итог в `r.tenant` — свой сид для этого не нужен.
120
+ */
85
121
  tenant?: TenantOptions;
86
122
  }
87
123
  export interface UpResult extends InstallResult {
@@ -117,14 +153,18 @@ export declare function ensureDatabase(dsn: string, log?: (m: string) => void):
117
153
  */
118
154
  export declare function checkServer(versionNum: number, encoding: string): string[];
119
155
  /** Ревизия движка схемы по комментарию 'letopis ddl_revision=N'; схемы нет — null. */
120
- export declare function schemaRevision(sql: postgres.Sql, schema: string): Promise<number | null>;
156
+ export declare function schemaRevision(sql: Pick<postgres.Sql, 'unsafe'>, schema: string): Promise<number | null>;
121
157
  /**
122
- * Поставить или обновить установку letopis. Схема есть и её ревизия совпадает — повторный накат
123
- * (идемпотентный); ревизия отстала — только с upgrade: true; новее библиотеки — отказ.
158
+ * Ставит или обновляет установку letopis в схеме. Если схема уже есть и её ревизия та же, что у пакета, `up()` выполняет
159
+ * повторный накат: заново ставит функции, не трогая данные, — поэтому его можно вызывать при каждом запуске приложения.
160
+ * Схему отставшей ревизии `up()` обновляет только с `upgrade: true`, а на схеме новее пакета отказывает.
124
161
  */
125
162
  export declare function up(opts: UpOptions): Promise<UpResult>;
126
163
  export interface TenantOptions {
127
- /** Имя арендатора (аккаунт в System; id — v5 от «tenant:<имя>», как у letopis import --tenant). */
164
+ /**
165
+ * Имя арендатора — его аккаунта в System. Из имени вычисляется id арендатора, поэтому одно имя — всегда один и тот же
166
+ * арендатор (так же его находит `letopis import --tenant`).
167
+ */
128
168
  name: string;
129
169
  /** Правило арендатора «аутентифицированным можно всё» (READ, WRITE, DELETE; по умолчанию да). */
130
170
  grantAll?: boolean;
@@ -133,6 +173,10 @@ export interface TenantOptions {
133
173
  name: string;
134
174
  login: string;
135
175
  password: string;
176
+ /**
177
+ * Вид пароля (по умолчанию PASSWORD) — как у auth.setPassword: `^[A-Z][A-Z0-9_]{1,31}$`, не служебный вид (SESSION,
178
+ * APIKEY, KEYSECRET, OTP, TOTP, KEY, PROJECTOR, LOCK); иначе invalid_data.
179
+ */
136
180
  kind?: string;
137
181
  ttl?: string;
138
182
  /** Роли членства (по умолчанию ['owner']); роли уже существующего членства повтор не меняет. */
@@ -150,14 +194,10 @@ export interface TenantResult {
150
194
  created: boolean;
151
195
  }
152
196
  /**
153
- * Арендатор и его первый пользователь — то, что раньше делал свой сид функциями владельца
154
- * (sys_create, sys_grant_all). Нужно администраторское подключение (роль может стать владельцем);
155
- * всё — одной транзакцией от владельца. Повтор с тем же именем и логином ничего не дублирует:
156
- * id — v5 от имени и логина, существующие строки пропускаются (пароль и роли членства не меняются),
157
- * сессия выдаётся заново. Удалённые арендатор, аккаунт пользователя или членство (строки нет, а
158
- * журнал id есть) — отказ invalid_data, транзакция откатывается целиком: повтор не воскрешает то,
159
- * что удалил администратор (up({ tenant }) идёт при каждом запуске), а sys_create пишет только
160
- * версию 1 — её занимает история; вернуть строку — restore() цепочкой.
197
+ * Заводит арендатора и, по желанию, его первого пользователя (аккаунт, членство, пароль, сессию) одной транзакцией от
198
+ * роли-владельца схемы — поэтому нужен администраторский пул. Повтор с тем же именем и логином ничего не дублирует и не
199
+ * меняет, только выдаёт пользователю новую сессию; одновременные вызовы заводят арендатора один раз. Если арендатор,
200
+ * пользователь или членство были удалены, повтор их не воскрешает — отказ `invalid_data`.
161
201
  */
162
202
  export declare function createTenant(sql: postgres.Sql, schema: string, opts: TenantOptions, owner?: string): Promise<TenantResult>;
163
203
  export {};