letopis 1.4.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.
- package/AGENT-CHEATSHEET.en.md +10 -5
- package/AGENT-CHEATSHEET.md +14 -9
- package/CHANGELOG.md +159 -0
- package/README.en.md +119 -52
- package/README.md +107 -47
- package/dist/acl.d.ts +11 -5
- package/dist/admin.d.ts +30 -14
- package/dist/admin.js +42 -16
- package/dist/auth.d.ts +61 -17
- package/dist/auth.js +16 -2
- package/dist/cache.d.ts +5 -1
- package/dist/chain.d.ts +91 -15
- package/dist/chain.js +47 -4
- package/dist/cli.js +6 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +5 -5
- package/dist/import.js +7 -0
- package/dist/index.d.ts +74 -45
- package/dist/index.js +12 -5
- package/dist/load.d.ts +10 -3
- package/dist/load.js +19 -4
- package/dist/mcp.d.ts +20 -11
- package/dist/mcp.js +4 -4
- package/dist/model.d.ts +4 -1
- package/dist/ops.d.ts +58 -18
- package/dist/ops.js +58 -18
- package/dist/project.d.ts +30 -11
- package/dist/project.js +30 -4
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +4 -1
- package/dist/sql.d.ts +4 -1
- package/dist/sql.js +12 -4
- package/dist/tx.js +10 -1
- package/dist/types.d.ts +14 -7
- package/dist/types.js +13 -6
- package/dist/up.d.ts +48 -25
- package/dist/up.js +89 -41
- package/dist/validate.d.ts +9 -2
- package/dist/validate.js +19 -3
- package/dist/watch.d.ts +17 -5
- package/dist/watch.js +40 -8
- package/dist/write.d.ts +3 -2
- package/dist/write.js +29 -10
- package/package.json +1 -1
- package/sql/10-core.sql +6 -1
- package/sql/60-write.sql +38 -18
- package/sql/80-auth.sql +39 -2
- package/sql/88-derived.sql +1 -0
- package/sql/90-time.sql +15 -5
- package/sql/95-seed.system.sql +6 -3
- package/sql/99-revision.sql +1 -1
- package/sql/migrate/0007-secret-account-fk.sql +12 -0
package/dist/errors.js
CHANGED
|
@@ -12,13 +12,13 @@ export class LetopisError extends Error {
|
|
|
12
12
|
this.detail = detail;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
/** Версия, в которой снято старое API (её берёт и контракт API — scripts/gen-api-contract.mjs). */
|
|
16
|
+
export const REMOVED_IN = '1.0.0';
|
|
15
17
|
/** Охранник снесённого API (план, этап 3, п. 11): понятная ошибка с версией и заменой. */
|
|
16
18
|
export function removed(name, replacement) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export function notYet(name, stage) {
|
|
21
|
-
return new LetopisError('not_implemented', `${name} появится на этапе ${stage} плана 1.0`);
|
|
19
|
+
// «опция …» — женского рода; методы и свойства — мужского
|
|
20
|
+
const verb = name.startsWith('опция ') ? 'снята' : 'снят';
|
|
21
|
+
return new LetopisError('removed', `${name} ${verb} в ${REMOVED_IN} — ${replacement}`);
|
|
22
22
|
}
|
|
23
23
|
/** Данные не проходят описание класса (`invalid_data`). */
|
|
24
24
|
export class ValidationError extends LetopisError {
|
package/dist/import.js
CHANGED
|
@@ -625,7 +625,14 @@ export async function import021(opts) {
|
|
|
625
625
|
}
|
|
626
626
|
if (secretRows.length) {
|
|
627
627
|
await x.begin(async (tx) => {
|
|
628
|
+
// кред аккаунта, которого в установке нет (его нет и в Account 0.21), привязать не к чему: строка secret ссылается
|
|
629
|
+
// на аккаунт внешним ключом (ревизия 7) — такой кред пропускается и считается в skipped
|
|
630
|
+
const known = new Set((await tx.unsafe(`select id::text as id from ${S}.entity where id = any ($1::uuid[])`, [[...new Set(secretRows.map((s) => s.account))]])).map((r) => r.id));
|
|
628
631
|
for (const s of secretRows) {
|
|
632
|
+
if (!known.has(s.account)) {
|
|
633
|
+
report.credentials.skipped++;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
629
636
|
const [dup] = await tx.unsafe(`select account::text as account from ${S}.secret
|
|
630
637
|
where kind = $1 and casefold(ident) collate pg_unicode_fast = casefold($2) collate pg_unicode_fast and deleted_at is null`, [s.kind, s.ident]);
|
|
631
638
|
if (dup) {
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { syncModels, generateTypes, schemaToTs, type SyncResult } from './sync.j
|
|
|
29
29
|
export { ne, gt, gte, lt, lte, between, inList, like, ilike, starts, ends, has, hasAny, hasAll, exists, isNull, not, or, isOp } from './ops.js';
|
|
30
30
|
export { cursorOf } from './sql.js';
|
|
31
31
|
export { inc, isInc, type IncMarker, type WriteRow } from './write.js';
|
|
32
|
-
export { totpCode, hashPassword, verifyHash, type AuthApi, type LoginResult, type Credential } from './auth.js';
|
|
32
|
+
export { totpCode, hashPassword, verifyHash, type AuthApi, type LoginResult, type Credential, type SessionInfo } from './auth.js';
|
|
33
33
|
export type { AclApi, AclDecision, AclDataDecision, AclOp } from './acl.js';
|
|
34
34
|
export type { LoadOptions, LoadReport, LoadIssue, LoadInput } from './load.js';
|
|
35
35
|
export type { Chain, ChainCore, StepFn, BatchEntry } from './chain.js';
|
|
@@ -54,36 +54,41 @@ export interface ConnectOptions {
|
|
|
54
54
|
sql?: postgres.Sql;
|
|
55
55
|
/** Схема установки, например `v2.salon`. */
|
|
56
56
|
schema: string;
|
|
57
|
-
/** Токен сессии; либо ключ APIKEY в `apiKey` (он же `service` — ключ сервиса) — его обменяют на сессию. */
|
|
57
|
+
/** Токен сессии; либо ключ `APIKEY` в `apiKey` (он же `service` — ключ сервиса) — его обменяют на сессию. */
|
|
58
58
|
token?: string;
|
|
59
59
|
apiKey?: string;
|
|
60
60
|
service?: string;
|
|
61
61
|
/** Продлевать сессию раз в минуту работы (по умолчанию да, §2.9). */
|
|
62
62
|
refreshSession?: boolean;
|
|
63
|
-
/** Разрешить роль, которая обходит RLS (суперпользователь, BYPASSRLS
|
|
63
|
+
/** Разрешить роль, которая обходит RLS (суперпользователь, `BYPASSRLS`, член роли-владельца), — администраторское подключение (§2.9). */
|
|
64
64
|
allowBypassRls?: boolean;
|
|
65
|
-
/** Имя роли-владельца (по умолчанию letopis_owner). */
|
|
65
|
+
/** Имя роли-владельца (по умолчанию `letopis_owner`). */
|
|
66
66
|
owner?: string;
|
|
67
67
|
/** Интервал отправки отложенного сигнала, мс (по умолчанию 100; решение 9 точки А). */
|
|
68
68
|
notifyIntervalMs?: number;
|
|
69
|
-
/** Размер пула, если подключение создаёт сам connect()
|
|
69
|
+
/** Размер пула, если подключение создаёт сам `connect()`. */
|
|
70
70
|
max?: number;
|
|
71
71
|
/** Хук на каждый запрос цепочки (метрики, лог). */
|
|
72
72
|
onQuery?: (e: QueryEvent) => void;
|
|
73
|
-
/** Порог медленного запроса, мс: событие получает slow
|
|
73
|
+
/** Порог медленного запроса, мс: событие получает `slow`; без `onQuery` — `console.warn`. */
|
|
74
74
|
slowMs?: number;
|
|
75
|
-
/** Модели letopis/model — только для типов цепочек (TypedDb); описания классов берутся из базы. */
|
|
75
|
+
/** Модели `letopis/model` — только для типов цепочек (`TypedDb`); описания классов берутся из базы. */
|
|
76
76
|
models?: readonly AnyModelLike[];
|
|
77
77
|
/**
|
|
78
|
-
* Слушать сигналы базы (LISTEN
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
78
|
+
* Слушать сигналы базы (`LISTEN`). `false` — для пула в режиме транзакций (PgBouncer): тогда реестр классов сам не
|
|
79
|
+
* обновляется (новые и изменённые классы подключение увидит после `db.refresh()`), подписка `watch()` только опрашивает
|
|
80
|
+
* журнал, а кэш результатов работает лишь с `cache: { ttlOnly: true }` — по сроку. В установке с
|
|
81
|
+
* `up({ signals: 'none' })` так всегда, только изменения классов подключение раз в 5 секунд проверяет само.
|
|
82
82
|
*/
|
|
83
83
|
listen?: boolean;
|
|
84
|
-
/** Кэш результатов (§2.12): срок по умолчанию, размер, ttlOnly
|
|
84
|
+
/** Кэш результатов (§2.12): срок по умолчанию, размер, `ttlOnly`; `false` — выключен. */
|
|
85
85
|
cache?: CacheOptions | false;
|
|
86
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Запускать `maintain()` по расписанию: интервал — число миллисекунд или строка вроде `'15m'`, `'1h'`, от 1 мс до
|
|
88
|
+
* 2³¹−1 мс (около 24 суток); иначе `connect()` отказывает с `invalid_data`, не открывая соединений. Если обслуживание в
|
|
89
|
+
* этот момент уже ведёт другой процесс или ещё идёт прошлый запуск, запуск пропускается. `close()` ждёт, пока идущий
|
|
90
|
+
* запуск доделает текущую пачку.
|
|
91
|
+
*/
|
|
87
92
|
maintain?: string | number;
|
|
88
93
|
}
|
|
89
94
|
export interface DbCore {
|
|
@@ -92,50 +97,72 @@ export interface DbCore {
|
|
|
92
97
|
/** Арендатор сессии. */
|
|
93
98
|
readonly tenant: string | null;
|
|
94
99
|
readonly registry: Registry;
|
|
95
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Вход и сессии: способы входа (креды) — пароль, ключ API, TOTP, одноразовый код из письма или СМС, привязка внешней
|
|
102
|
+
* учётной записи; вход по ним, выпуск и гашение сессий, выбор арендатора сессии.
|
|
103
|
+
*/
|
|
96
104
|
readonly auth: AuthApi;
|
|
97
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Права того, от чьего имени идёт запрос, — заранее, для интерфейса и проверок в приложении: `check(endpoint)` — можно
|
|
107
|
+
* ли обратиться к ресурсу приложения, `checkData(класс, операция)` — можно ли читать, менять или удалять объекты
|
|
108
|
+
* класса. Сами права база применяет при каждом запросе.
|
|
109
|
+
*/
|
|
98
110
|
readonly acl: AclApi;
|
|
99
|
-
/** Сырой SQL под сессией и RLS, одной транзакцией (в db.begin() — в ней): db.sql`select
|
|
111
|
+
/** Сырой SQL под сессией и RLS, одной транзакцией (в `db.begin()` — в ней): `` db.sql`select …` ``. */
|
|
100
112
|
sql(strings: TemplateStringsArray, ...values: unknown[]): Promise<postgres.Row[]>;
|
|
101
|
-
/** Транзакция под сессией: fn получает соединение postgres.js (в db.begin() — ту же транзакцию). */
|
|
113
|
+
/** Транзакция под сессией: `fn` получает соединение postgres.js (в `db.begin()` — ту же транзакцию). */
|
|
102
114
|
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
103
|
-
/** id объекта
|
|
115
|
+
/** id объекта по классу и значениям ключа — такой же, какой вычислит база в арендаторе сессии. */
|
|
104
116
|
idOf(cls: string, key: Readonly<Record<string, unknown>>): string;
|
|
105
117
|
/** Перечитать описания классов. */
|
|
106
118
|
refresh(): Promise<void>;
|
|
107
119
|
/** Отправить накопленные сигналы сейчас. */
|
|
108
120
|
flushSignals(): Promise<void>;
|
|
109
121
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
122
|
+
* Проверяет целостность журнала всей установки: сходится ли хэш каждой версии с хэшем предыдущей и текущая строка
|
|
123
|
+
* объекта — с его последней версией; с `{ data: true }` проверяет ещё каждую строку. Якорь в ответе — один хэш текущих
|
|
124
|
+
* версий всех объектов: храните его вне базы, тогда заметна и подмена самых свежих версий. Нужно право `letopis.verify`
|
|
125
|
+
* (по умолчанию оно у системного администратора) или администраторское подключение.
|
|
112
126
|
*/
|
|
113
127
|
verify(opts?: {
|
|
114
128
|
data?: boolean;
|
|
115
129
|
}): Promise<VerifyResult>;
|
|
116
130
|
/** Обслуживание: истёкшие сессии и коды, прореживание журнала по политике хранения, очередь сигналов. */
|
|
117
131
|
maintain(opts?: MaintainOptions): Promise<MaintainReport>;
|
|
118
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Сбрасывает тестовую установку: сессии, данные одного арендатора или всё — смотря по `level`. Работает, только если
|
|
134
|
+
* установка поставлена с `up({ allowReset: true })`, у вызывающего есть право `letopis.reset` (или подключение
|
|
135
|
+
* администраторское), а `confirm` совпадает с именем схемы.
|
|
136
|
+
*/
|
|
119
137
|
reset(opts: ResetOptions): Promise<ResetResult>;
|
|
120
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Стирает версии, сделанные раньше момента `before`; текущая версия остаётся всегда. Вместо стёртого начала истории
|
|
140
|
+
* остаётся служебная запись `trim` со временем появления объекта.
|
|
141
|
+
*/
|
|
121
142
|
trimHistory(target: string | string[] | Row | Row[], before: string | Date): Promise<number>;
|
|
122
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Меняет ключ класса. Объекты его семейства (сам класс и потомки), у которых по новому ключу выходит другой id,
|
|
145
|
+
* переезжают на новый id, а ссылки на них переводятся.
|
|
146
|
+
*/
|
|
123
147
|
rekeyClass(cls: string, key: string[]): Promise<number>;
|
|
124
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Описание модели для программ и языковых моделей: классы арендатора сессии и общие классы System с полями, ключом,
|
|
150
|
+
* ссылками и метаданными `meta`. `describeText(db.describe())` превращает его в текст.
|
|
151
|
+
*/
|
|
125
152
|
describe(): ModelDescription;
|
|
126
|
-
/** Подписка по курсору: for await (const ev of db.watch({ from }))
|
|
153
|
+
/** Подписка по курсору: `for await (const ev of db.watch({ from })) …`; `sub.cursor` — откуда продолжить. */
|
|
127
154
|
watch(opts?: WatchOptions): Subscription;
|
|
128
155
|
/** Кэш результатов подключения. */
|
|
129
156
|
readonly cache: {
|
|
130
157
|
clear(): void;
|
|
131
158
|
stats(): CacheStats;
|
|
132
159
|
};
|
|
133
|
-
/** Начать путь с готового узла: db.entity(row | цепочка)
|
|
160
|
+
/** Начать путь с готового узла: `db.entity(row | цепочка)`. */
|
|
134
161
|
entity(x: Chain | Row): Chain;
|
|
135
162
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
163
|
+
* Подключение на том же пуле от имени другого аккаунта: запросы идут с его правами и в его арендаторе. Журнал
|
|
164
|
+
* записывает автором версий этот аккаунт, а исполнителем (`agent`) — аккаунт сессии, например сервис. Нужно право
|
|
165
|
+
* `auth.impersonate`.
|
|
139
166
|
*/
|
|
140
167
|
as(account: string | {
|
|
141
168
|
id: string;
|
|
@@ -143,50 +170,52 @@ export interface DbCore {
|
|
|
143
170
|
owner?: string;
|
|
144
171
|
tenant?: string;
|
|
145
172
|
}): Promise<Db>;
|
|
146
|
-
/** Явная транзакция read committed
|
|
147
|
-
/** Явная транзакция; reason — причина изменения для всех операций блока (план 1.1). */
|
|
173
|
+
/** Явная транзакция `read committed`: те же цепочки, `commit()`, `rollback()`, `lock()`, `forUpdate()`. */
|
|
174
|
+
/** Явная транзакция; `reason` — причина изменения для всех операций блока (план 1.1). */
|
|
148
175
|
begin(opts?: {
|
|
149
176
|
reason?: string;
|
|
150
177
|
}): Promise<DbTx>;
|
|
151
178
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
179
|
+
* Массовая загрузка строк любых классов одной транзакцией (или порциями `commitEvery`) — быстрее записи цепочками.
|
|
180
|
+
* Строки проверяются по тому же описанию класса, что и при обычной записи (данные, ссылки, ключ), уходят в базу
|
|
181
|
+
* командой `COPY`, а ссылки на объекты вне загрузки сверяет один запрос в конце; права проверяются только при загрузке
|
|
182
|
+
* от имени пользователя (опция `as`). Работает только на администраторском подключении, иначе `admin_required`.
|
|
154
183
|
*/
|
|
155
184
|
load(input: LoadInput, opts?: LoadOptions): Promise<LoadReport>;
|
|
156
185
|
/**
|
|
157
|
-
* Проектор из журнала (план 1.1, §2.4): строит строки производных классов (derivedBy:
|
|
158
|
-
* классов from
|
|
159
|
-
* Только на администраторском подключении (иначе admin_required).
|
|
186
|
+
* Проектор из журнала (план 1.1, §2.4): строит строки производных классов (`derivedBy: имя`) из изменений
|
|
187
|
+
* классов `from`; `run()` — догнать журнал и следовать за ним, `rebuild()` — перестроить с текущего состояния.
|
|
188
|
+
* Только на администраторском подключении (иначе `admin_required`).
|
|
160
189
|
*/
|
|
161
190
|
projector(name: string, opts: ProjectorOptions): Projector;
|
|
162
191
|
/**
|
|
163
192
|
* Ремонт агрегатов в базе (план 1.1, §2.4): поля класса (с потомками) во всех арендаторах — заново из живых строк
|
|
164
193
|
* источников, под исключительными блокировками; версия — только у исправленных. Возвращает их число. Только на
|
|
165
|
-
* администраторском подключении (иначе admin_required).
|
|
194
|
+
* администраторском подключении (иначе `admin_required`).
|
|
166
195
|
*/
|
|
167
196
|
rebuildAggregates(cls: string): Promise<number>;
|
|
168
197
|
/**
|
|
169
|
-
* Именованная очередь планов фасада: batch.Класс(…).create(…)
|
|
170
|
-
* у каждого db.as() и у каждой db.begin() очереди свои: план исполняется от имени фасада, который его поставил.
|
|
198
|
+
* Именованная очередь планов фасада: `batch.Класс(…).create(…)`; `batch.run()` — одной транзакцией. У корня,
|
|
199
|
+
* у каждого `db.as()` и у каждой `db.begin()` очереди свои: план исполняется от имени фасада, который его поставил.
|
|
171
200
|
*/
|
|
172
201
|
batch(name: string, opts?: {
|
|
173
202
|
reason?: string;
|
|
174
203
|
}): Batch;
|
|
175
|
-
/** Advisory-блокировка до конца транзакции — только в db.begin() (иначе tx_required). */
|
|
204
|
+
/** Advisory-блокировка до конца транзакции — только в `db.begin()` (иначе `tx_required`). */
|
|
176
205
|
lock(...keys: (string | number)[]): Promise<void>;
|
|
177
|
-
/** Зафиксировать транзакцию tr (то же, что tr.commit()). */
|
|
206
|
+
/** Зафиксировать транзакцию `tr` (то же, что `tr.commit()`). */
|
|
178
207
|
commit(tr?: DbTx): Promise<void>;
|
|
179
|
-
/** Откатить транзакцию tr (то же, что tr.rollback()). */
|
|
208
|
+
/** Откатить транзакцию `tr` (то же, что `tr.rollback()`). */
|
|
180
209
|
rollback(tr?: DbTx): Promise<void>;
|
|
181
210
|
close(): Promise<void>;
|
|
182
211
|
}
|
|
183
|
-
/** Подключение: служебные методы и классы как стартовые шаги цепочек — db.Класс(фильтр)
|
|
212
|
+
/** Подключение: служебные методы и классы как стартовые шаги цепочек — `db.Класс(фильтр)`. */
|
|
184
213
|
export type Db = DbCore & {
|
|
185
214
|
[className: string]: StepFn;
|
|
186
215
|
};
|
|
187
|
-
/** Явная транзакция db.begin()
|
|
216
|
+
/** Явная транзакция `db.begin()`: тот же API; ошибка любого запроса её ломает (§2.5). */
|
|
188
217
|
export type DbTx = Db;
|
|
189
|
-
/** Подключение с типами шагов, строк и фильтров по моделям: connect({ models })
|
|
218
|
+
/** Подключение с типами шагов, строк и фильтров по моделям: `connect({ models })`. */
|
|
190
219
|
export type TypedDb<M extends readonly AnyModelLike[]> = DbCore & StepsOf<M> & {
|
|
191
220
|
[className: string]: StepFn;
|
|
192
221
|
};
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { ACL_CLASSES, ResultCache } from './cache.js';
|
|
|
23
23
|
import { Subscription } from './watch.js';
|
|
24
24
|
import { Projector } from './project.js';
|
|
25
25
|
import { SIGNAL_MODES, Signals, bucket, throttle } from './signals.js';
|
|
26
|
-
import { describe as describeModel, maintain as runMaintain, rebuildAggregates as runRebuildAggregates, rekeyClass as runRekeyClass, reset as runReset, scheduleMaintain, trimHistory as runTrim, verify as runVerify, } from './admin.js';
|
|
26
|
+
import { describe as describeModel, maintain as runMaintain, rebuildAggregates as runRebuildAggregates, rekeyClass as runRekeyClass, reset as runReset, MAINTAIN_STOP, parseInterval, scheduleMaintain, trimHistory as runTrim, verify as runVerify, } from './admin.js';
|
|
27
27
|
import { stampDrift } from './up.js';
|
|
28
28
|
export { DDL_REVISION } from './types.js';
|
|
29
29
|
export { uuidv5, v5Name, v5Id, idOf } from './uuid.js';
|
|
@@ -233,6 +233,8 @@ export async function connect(opts) {
|
|
|
233
233
|
for (const k of Object.keys(opts))
|
|
234
234
|
if (k in REMOVED_OPTIONS)
|
|
235
235
|
throw removed(`опция connect({ ${k} })`, REMOVED_OPTIONS[k]);
|
|
236
|
+
// интервал обслуживания — до пула и таймеров: неверный не оставляет за собой открытых соединений
|
|
237
|
+
const maintainEvery = opts.maintain === undefined ? undefined : parseInterval(opts.maintain);
|
|
236
238
|
const own = !opts.sql;
|
|
237
239
|
// timestamptz — строкой: Date в JS режет микросекунды, а на них держатся asOf и курсоры по времени
|
|
238
240
|
const sql = (opts.sql ?? postgres(opts.dsn, {
|
|
@@ -268,7 +270,11 @@ export async function connect(opts) {
|
|
|
268
270
|
throw new LetopisError('invalid_data', `connect: схемы ${opts.schema} нет — установите её: up({ schema })`);
|
|
269
271
|
if (rev < DDL_REVISION)
|
|
270
272
|
console.warn(`letopis: движок схемы ${opts.schema} ревизии ${rev}, библиотека ждёт ${DDL_REVISION} — обновите: up({ schema: '${opts.schema}', upgrade: true })`);
|
|
271
|
-
|
|
273
|
+
// схему обновил более новый пакет (во время раскатки старые экземпляры ещё работают): подключаемся, но предупреждаем —
|
|
274
|
+
// этот пакет может не знать функций и колонок новой ревизии; up() такую схему не трогает и отказывает
|
|
275
|
+
else if (rev > DDL_REVISION)
|
|
276
|
+
console.warn(`letopis: движок схемы ${opts.schema} ревизии ${rev} новее библиотеки (она знает ${DDL_REVISION}) — обновите пакет letopis`);
|
|
277
|
+
else {
|
|
272
278
|
// ревизия та же, а функции схемы ставила другая версия движка — штамп в комментарии схемы (99-revision.sql)
|
|
273
279
|
const drift = await stampDrift(opts.schema, String(rv.c ?? ''));
|
|
274
280
|
if (drift)
|
|
@@ -472,7 +478,8 @@ export async function connect(opts) {
|
|
|
472
478
|
signals,
|
|
473
479
|
listen,
|
|
474
480
|
async close() {
|
|
475
|
-
|
|
481
|
+
// идущий maintain() по расписанию доделывает пачку: пул закрывается после неё, а не посреди транзакции
|
|
482
|
+
await stopMaintain?.();
|
|
476
483
|
resync.stop();
|
|
477
484
|
startReload.stop();
|
|
478
485
|
if (stamper)
|
|
@@ -484,8 +491,8 @@ export async function connect(opts) {
|
|
|
484
491
|
},
|
|
485
492
|
};
|
|
486
493
|
const db = makeDb(shared, { txc: ctx, tenant: () => root.tenant, registry: () => root.registry, root: true }, poolExecutor(ctx));
|
|
487
|
-
if (
|
|
488
|
-
stopMaintain = scheduleMaintain(() => db.maintain(),
|
|
494
|
+
if (maintainEvery !== undefined)
|
|
495
|
+
stopMaintain = scheduleMaintain((stopped) => db.maintain({ [MAINTAIN_STOP]: stopped }), maintainEvery);
|
|
489
496
|
return db;
|
|
490
497
|
}
|
|
491
498
|
catch (e) {
|
package/dist/load.d.ts
CHANGED
|
@@ -12,7 +12,10 @@ export interface LoadOptions {
|
|
|
12
12
|
batch?: number;
|
|
13
13
|
/** Фиксировать порциями примерно по N строк (порция — классы в порядке зависимостей, цикл — целиком). */
|
|
14
14
|
commitEvery?: number;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Режим истории (импорт): каждая строка — версия объекта с номером `$rev`, временем `$at` и видом операции `$op`; так
|
|
17
|
+
* переносят историю объектов из другой системы.
|
|
18
|
+
*/
|
|
16
19
|
withHistory?: boolean;
|
|
17
20
|
/** В конце перепроверить загруженные строки проверкой базы. */
|
|
18
21
|
verify?: boolean;
|
|
@@ -20,7 +23,10 @@ export interface LoadOptions {
|
|
|
20
23
|
class?: string;
|
|
21
24
|
/** Арендатор строк (по умолчанию — арендатор сессии). */
|
|
22
25
|
tenant?: string;
|
|
23
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Исполнитель записи — строка, которую журнал сохранит в поле `agent` загруженных версий рядом с автором (по умолчанию
|
|
28
|
+
* «letopis load»; импорт — «letopis import»).
|
|
29
|
+
*/
|
|
24
30
|
agent?: string;
|
|
25
31
|
/**
|
|
26
32
|
* Загрузка от имени пользователя (§2.6, п. 10): автор версий — он; арендатор (по умолчанию —
|
|
@@ -128,7 +134,8 @@ export declare function load(env: LoadEnv, input: LoadInput, opts?: LoadOptions)
|
|
|
128
134
|
* Первая заливка (план 1.1, §2.3): загрузки fn — одной транзакцией в пустую схему. Блокировка таблиц до
|
|
129
135
|
* проверки; в схеме только строки системных классов и записи reset (иначе not_empty); они (с seq и tx
|
|
130
136
|
* журнала) и определения ограничений, индексов и триггеров из каталога (с режимом включения tgenabled:
|
|
131
|
-
* pg_get_triggerdef его не передаёт) сохраняются в памяти;
|
|
137
|
+
* pg_get_triggerdef его не передаёт) сохраняются в памяти; внешний ключ secret.account снимается до TRUNCATE и
|
|
138
|
+
* возвращается после первичного ключа entity; TRUNCATE; триггеры, ограничения и индексы
|
|
132
139
|
* снимаются — BEFORE ROW делает COPY построчным одним своим наличием, триггеры после оператора копят
|
|
133
140
|
* переходные таблицы; системные строки возвращаются (seq журнала — generated always: overriding system
|
|
134
141
|
* value); загрузки пишут COPY FREEZE. В конце — ограничения (дубликат найдёт сортировка), индексы,
|
package/dist/load.js
CHANGED
|
@@ -924,7 +924,7 @@ export async function load(env, input, opts = {}) {
|
|
|
924
924
|
lg as (
|
|
925
925
|
insert into ${S}.log (id, rev, class, cv, tenant, owner, links, data, tags, at, author, agent, op, reason, moved, prev_hash)
|
|
926
926
|
select id, rev, class, cv, tenant, owner, links, data, tags, at, author, agent, op, reason, moved, prev_hash from up
|
|
927
|
-
where ${S}.history_on(up.class, up.tenant))
|
|
927
|
+
where ${S}.history_on(up.class, up.tenant) order by up.id)
|
|
928
928
|
select up.id, up.class from up`, [updates.map((u) => ({ id: u.id, data: u.data, links: u.links, tags: u.tags })), ts, author, agent]);
|
|
929
929
|
report.updated += res.length;
|
|
930
930
|
report.skipped += updates.length - res.length;
|
|
@@ -1020,12 +1020,17 @@ export async function load(env, input, opts = {}) {
|
|
|
1020
1020
|
await copyLines(conn, `copy ${S}.entity (${ENTITY_COLS}) from stdin${ini?.freeze ? ' with (freeze)' : ''}`, ent);
|
|
1021
1021
|
}
|
|
1022
1022
|
}
|
|
1023
|
-
/**
|
|
1023
|
+
/**
|
|
1024
|
+
* Определения из каталога: ограничения (первичный ключ, уникальные), внешние ключи других таблиц на entity и log
|
|
1025
|
+
* (secret.account, ревизия 7), прочие индексы, триггеры с режимом, колонки.
|
|
1026
|
+
*/
|
|
1024
1027
|
async function catalogOf(conn, S) {
|
|
1025
1028
|
const rels = [`${S}.entity`, `${S}.log`];
|
|
1026
1029
|
const sel = `(select x::regclass from unnest($1::text[]) x)`;
|
|
1027
1030
|
const constraints = await conn.unsafe(`select conname as name, conrelid::regclass::text as tbl, pg_get_constraintdef(oid) as def
|
|
1028
1031
|
from pg_constraint where conrelid in ${sel} and contype in ('p', 'u') order by conrelid, conname`, [rels]);
|
|
1032
|
+
const foreign = await conn.unsafe(`select conname as name, conrelid::regclass::text as tbl, pg_get_constraintdef(oid) as def
|
|
1033
|
+
from pg_constraint where confrelid in ${sel} and contype = 'f' order by conrelid, conname`, [rels]);
|
|
1029
1034
|
const indexes = await conn.unsafe(`select i.indexrelid::regclass::text as name, pg_get_indexdef(i.indexrelid) as def
|
|
1030
1035
|
from pg_index i where i.indrelid in ${sel}
|
|
1031
1036
|
and not exists (select 1 from pg_constraint c where c.conindid = i.indexrelid and c.conrelid = i.indrelid and c.contype in ('p', 'u', 'x'))
|
|
@@ -1037,6 +1042,7 @@ async function catalogOf(conn, S) {
|
|
|
1037
1042
|
where attrelid = $1::regclass and attnum > 0 and not attisdropped and attgenerated = ''`, [rel]))[0].c;
|
|
1038
1043
|
return {
|
|
1039
1044
|
constraints: constraints,
|
|
1045
|
+
foreign: foreign,
|
|
1040
1046
|
indexes: indexes,
|
|
1041
1047
|
triggers: triggers,
|
|
1042
1048
|
entityCols: await cols(rels[0]),
|
|
@@ -1047,7 +1053,8 @@ async function catalogOf(conn, S) {
|
|
|
1047
1053
|
* Первая заливка (план 1.1, §2.3): загрузки fn — одной транзакцией в пустую схему. Блокировка таблиц до
|
|
1048
1054
|
* проверки; в схеме только строки системных классов и записи reset (иначе not_empty); они (с seq и tx
|
|
1049
1055
|
* журнала) и определения ограничений, индексов и триггеров из каталога (с режимом включения tgenabled:
|
|
1050
|
-
* pg_get_triggerdef его не передаёт) сохраняются в памяти;
|
|
1056
|
+
* pg_get_triggerdef его не передаёт) сохраняются в памяти; внешний ключ secret.account снимается до TRUNCATE и
|
|
1057
|
+
* возвращается после первичного ключа entity; TRUNCATE; триггеры, ограничения и индексы
|
|
1051
1058
|
* снимаются — BEFORE ROW делает COPY построчным одним своим наличием, триггеры после оператора копят
|
|
1052
1059
|
* переходные таблицы; системные строки возвращаются (seq журнала — generated always: overriding system
|
|
1053
1060
|
* value); загрузки пишут COPY FREEZE. В конце — ограничения (дубликат найдёт сортировка), индексы,
|
|
@@ -1081,7 +1088,11 @@ export async function initialSession(env, fn, tune = {}) {
|
|
|
1081
1088
|
const triggers = tune.keepTriggers ? [] : cat.triggers;
|
|
1082
1089
|
const keys = tune.keepKeys ? [] : cat.constraints;
|
|
1083
1090
|
const indexes = cat.indexes.filter((i) => !(tune.keepGin && /\busing gin\b/i.test(i.def)));
|
|
1084
|
-
// 4. TRUNCATE; триггеры, ограничения и индексы —
|
|
1091
|
+
// 4. TRUNCATE; триггеры, ограничения и индексы — снять. Внешний ключ secret.account на entity (ревизия 7) — до
|
|
1092
|
+
// TRUNCATE при любом keepKeys: таблицу, на которую ссылается внешний ключ, TRUNCATE без неё не очищает, а secret
|
|
1093
|
+
// (ключ установки, сессии, креды) заливка не трогает
|
|
1094
|
+
for (const f of cat.foreign)
|
|
1095
|
+
await q(`alter table ${f.tbl} drop constraint ${ident(f.name)}`);
|
|
1085
1096
|
await q(`truncate ${S}.entity, ${S}.log`);
|
|
1086
1097
|
for (const t of triggers)
|
|
1087
1098
|
await q(`drop trigger ${ident(t.name)} on ${t.tbl}`);
|
|
@@ -1107,6 +1118,10 @@ export async function initialSession(env, fn, tune = {}) {
|
|
|
1107
1118
|
least(current_setting('max_parallel_workers')::int, 4))::text, true)`);
|
|
1108
1119
|
for (const c of keys)
|
|
1109
1120
|
await q(`alter table ${c.tbl} add constraint ${ident(c.name)} ${c.def}`);
|
|
1121
|
+
// внешние ключи на entity — после её первичного ключа: системные строки вернулись с прежними id, строки secret
|
|
1122
|
+
// ссылаются на них
|
|
1123
|
+
for (const f of cat.foreign)
|
|
1124
|
+
await q(`alter table ${f.tbl} add constraint ${ident(f.name)} ${f.def}`);
|
|
1110
1125
|
for (const i of indexes)
|
|
1111
1126
|
await q(i.def);
|
|
1112
1127
|
// 8. триггеры — с прежним режимом включения (ENABLE ALWAYS работает и при session_replication_role = replica)
|
package/dist/mcp.d.ts
CHANGED
|
@@ -3,26 +3,35 @@ import type { Db } from './index.js';
|
|
|
3
3
|
export interface McpOptions {
|
|
4
4
|
/** Инструмент letopis_write (create, update, upsert, delete); по умолчанию сервер только читает. */
|
|
5
5
|
write?: boolean;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Предел строк в одном ответе (по умолчанию 100). Остальные строки `letopis_query` отдаёт на следующих страницах по
|
|
8
|
+
* курсору, а версии, группы `countBy` и изменения `diff` и `blame` сверх предела обрезаются с пометкой `more` или
|
|
9
|
+
* `truncated`.
|
|
10
|
+
*/
|
|
7
11
|
maxRows?: number;
|
|
8
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Предел размера строк одного ответа в байтах JSON (по умолчанию 50 000); что не поместилось — так же, как сверх
|
|
14
|
+
* `maxRows`. Описание модели больше предела `letopis_model` отдаёт кратким.
|
|
15
|
+
*/
|
|
9
16
|
maxBytes?: number;
|
|
10
17
|
/**
|
|
11
|
-
* letopis_write
|
|
12
|
-
* описания классов). По умолчанию — отказ acl_denied
|
|
13
|
-
*
|
|
18
|
+
* Разрешить `letopis_write` писать и классы прав — `rule`, `Resource`, `member`, `Account`, `Class` и их потомков
|
|
19
|
+
* (правила, ресурсы, членства, аккаунты, описания классов). По умолчанию — отказ `acl_denied`: агент, которому в данных
|
|
20
|
+
* подсунули чужие инструкции, мог бы без подтверждения человека выдать постороннему членство или право
|
|
21
|
+
* (ревью безопасности №2, О5).
|
|
14
22
|
*/
|
|
15
23
|
allowAclWrite?: boolean;
|
|
16
24
|
/**
|
|
17
|
-
*
|
|
18
|
-
* членства, правила и letopis
|
|
25
|
+
* Принять сессию в арендаторе System, то есть сессию системного администратора: с `true` агент получает его права на
|
|
26
|
+
* аккаунты, членства, правила и `letopis.*`, но писать классы прав по-прежнему может только с `allowAclWrite`. По
|
|
27
|
+
* умолчанию такая сессия — отказ `user_session_required` (ревью безопасности №2, О5).
|
|
19
28
|
*/
|
|
20
29
|
allowSystemAdmin?: boolean;
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
|
-
* Сервер MCP для одного пользователя letopis: инструменты letopis_model
|
|
24
|
-
* { write: true } — letopis_write
|
|
25
|
-
* Транспорт выбирает приложение: server.connect(транспорт)
|
|
26
|
-
* createMcpHandler(() => createMcpServer(db)) SDK
|
|
32
|
+
* Сервер MCP для одного пользователя letopis: инструменты `letopis_model`, `letopis_query`, `letopis_history`, а с
|
|
33
|
+
* `{ write: true }` — ещё `letopis_write`. Подключение — сессия пользователя (`connect({ token })`) или фасад
|
|
34
|
+
* `db.as(пользователь)`. Транспорт выбирает приложение: `server.connect(транспорт)`, `serveStdio(() => createMcpServer(db))`
|
|
35
|
+
* или обработчик HTTP `createMcpHandler(() => createMcpServer(db))` из SDK; токен запроса HTTP проверяет приложение.
|
|
27
36
|
*/
|
|
28
37
|
export declare function createMcpServer(db: Db, opts?: McpOptions): Promise<McpServer>;
|
package/dist/mcp.js
CHANGED
|
@@ -782,10 +782,10 @@ async function checkCredentials(db, o) {
|
|
|
782
782
|
return { account: c.account, tenant: String(c.tenant ?? '') };
|
|
783
783
|
}
|
|
784
784
|
/**
|
|
785
|
-
* Сервер MCP для одного пользователя letopis: инструменты letopis_model
|
|
786
|
-
* { write: true } — letopis_write
|
|
787
|
-
* Транспорт выбирает приложение: server.connect(транспорт)
|
|
788
|
-
* createMcpHandler(() => createMcpServer(db)) SDK
|
|
785
|
+
* Сервер MCP для одного пользователя letopis: инструменты `letopis_model`, `letopis_query`, `letopis_history`, а с
|
|
786
|
+
* `{ write: true }` — ещё `letopis_write`. Подключение — сессия пользователя (`connect({ token })`) или фасад
|
|
787
|
+
* `db.as(пользователь)`. Транспорт выбирает приложение: `server.connect(транспорт)`, `serveStdio(() => createMcpServer(db))`
|
|
788
|
+
* или обработчик HTTP `createMcpHandler(() => createMcpServer(db))` из SDK; токен запроса HTTP проверяет приложение.
|
|
789
789
|
*/
|
|
790
790
|
export async function createMcpServer(db, opts = {}) {
|
|
791
791
|
const o = limitsOf(opts);
|
package/dist/model.d.ts
CHANGED
|
@@ -191,7 +191,10 @@ type ShapeOf<F> = F extends z.ZodObject<infer S> ? S : F extends z.ZodRawShape ?
|
|
|
191
191
|
type AliasOf<D> = D extends {
|
|
192
192
|
alias: infer A extends string;
|
|
193
193
|
} ? A : string;
|
|
194
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Описывает класс объектов (хаб): поля, ключ, концы и свойства класса. Класс-связь объектов со своими данными
|
|
196
|
+
* описывает `link()`.
|
|
197
|
+
*/
|
|
195
198
|
export declare function hub<N extends string, S extends z.ZodRawShape>(name: N, shape: S, opts: Omit<ClassDef, 'fields'>): Model<N, z.output<z.ZodObject<S>>, Record<never, never>>;
|
|
196
199
|
export declare function hub<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D): Model<N, DataOf<D['extends'], ShapeOf<D['fields']>>, AllLinks<D['extends'], D['ends'] extends EndsInput ? D['ends'] : Record<never, never>>, AliasOf<D>>;
|
|
197
200
|
/** Связь — отношение между объектами со своими данными (бронь, навык). */
|
package/dist/ops.d.ts
CHANGED
|
@@ -5,39 +5,79 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type Op, type Scalar } from './types.js';
|
|
7
7
|
export declare const isOp: (v: unknown) => v is Op;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Не равно: `db.Order({ status: ne('cancelled') })`. Подходят и строки, где поля нет или в нём `null`, а `ne(null)` —
|
|
10
|
+
* строки, где у поля есть значение.
|
|
11
|
+
*/
|
|
9
12
|
export declare const ne: (v: Scalar) => Op;
|
|
10
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Больше: `db.Product({ price: gt(1000) })`. Сравнение — по типу поля из описания класса (числа — как числа, даты — как
|
|
15
|
+
* даты), поле без описания сравнивается как текст; строки без поля не подходят.
|
|
16
|
+
*/
|
|
11
17
|
export declare const gt: (v: Scalar) => Op;
|
|
12
|
-
/**
|
|
18
|
+
/** Больше или равно: `db.Product({ price: gte(500) })`. Сравнение — как у `gt()`. */
|
|
13
19
|
export declare const gte: (v: Scalar) => Op;
|
|
14
|
-
/**
|
|
20
|
+
/** Меньше: `db.Product({ price: lt(1000) })`. Сравнение — как у `gt()`. */
|
|
15
21
|
export declare const lt: (v: Scalar) => Op;
|
|
16
|
-
/**
|
|
22
|
+
/** Меньше или равно: `db.Product({ price: lte(900) })`. Сравнение — как у `gt()`. */
|
|
17
23
|
export declare const lte: (v: Scalar) => Op;
|
|
18
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* От `a` до `b` включительно: `db.Product({ price: between(500, 3000) })`. Сравнение — как у `gt()`; если `a` больше
|
|
26
|
+
* `b`, не подходит ни одна строка.
|
|
27
|
+
*/
|
|
19
28
|
export declare const between: (a: Scalar, b: Scalar) => Op;
|
|
20
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Одно из значений списка: `db.Product({ sku: inList(['K-1', 'M-1']) })`. Сравнение — по типу поля, как у `gt()`;
|
|
31
|
+
* пустой список не находит ничего, а `null` в списке не находит строк без значения — для них есть `isNull()`.
|
|
32
|
+
*/
|
|
21
33
|
export declare const inList: (vs: Scalar[]) => Op;
|
|
22
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Текст по шаблону с учётом регистра: `%` — любые символы, в том числе ни одного, `_` — ровно один символ;
|
|
36
|
+
* `db.Product({ title: like('%чай%') })`. Поле берётся как текст; буквальные `%` и `_` пишут с обратной косой чертой:
|
|
37
|
+
* `like('100\\%')`.
|
|
38
|
+
*/
|
|
23
39
|
export declare const like: (s: string) => Op;
|
|
24
|
-
/**
|
|
40
|
+
/** Как `like()`, но без учёта регистра: `db.Product({ title: ilike('%чай%') })`. */
|
|
25
41
|
export declare const ilike: (s: string) => Op;
|
|
26
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Текст начинается с `s`, с учётом регистра: `db.Product({ sku: starts('T-') })`. Это `like(s + '%')`: `%`, `_` и
|
|
44
|
+
* обратная косая черта внутри `s` не экранируются и работают как в шаблоне `like()`.
|
|
45
|
+
*/
|
|
27
46
|
export declare const starts: (s: string) => Op;
|
|
28
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Текст заканчивается на `s`, с учётом регистра: `db.Product({ sku: ends('-1') })`. Это `like('%' + s)`: `%`, `_` и
|
|
49
|
+
* обратная косая черта внутри `s` не экранируются и работают как в шаблоне `like()`.
|
|
50
|
+
*/
|
|
29
51
|
export declare const ends: (s: string) => Op;
|
|
30
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Поле-массив содержит элемент `v`: `db.Product({ labels: has('хит') })`. Тип важен: `has(5)` найдёт число 5, но не
|
|
54
|
+
* строку `'5'`.
|
|
55
|
+
*/
|
|
31
56
|
export declare const has: (v: Scalar) => Op;
|
|
32
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Поле-массив содержит хотя бы один элемент из списка: `db.Product({ labels: hasAny(['хит', 'акция']) })`. Находит
|
|
59
|
+
* только строковые элементы; пустой список не находит ничего.
|
|
60
|
+
*/
|
|
33
61
|
export declare const hasAny: (vs: Scalar[]) => Op;
|
|
34
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Поле-массив содержит все элементы списка, в любом порядке: `db.Product({ labels: hasAll(['хит', 'стекло']) })`. Тип
|
|
64
|
+
* элементов важен, как у `has()`; пустой список подходит любой строке, даже без этого поля.
|
|
65
|
+
*/
|
|
35
66
|
export declare const hasAll: (vs: Scalar[]) => Op;
|
|
36
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Поле есть в данных (`true`, по умолчанию) или его нет (`false`); значение не важно — поле с `null` тоже есть:
|
|
69
|
+
* `db.Product({ labels: exists(false) })` — товары без поля `labels`.
|
|
70
|
+
*/
|
|
37
71
|
export declare const exists: (yes?: boolean) => Op;
|
|
38
|
-
/** поле null или
|
|
72
|
+
/** В поле `null` или поля нет совсем: `db.Product({ specs: isNull() })`. Отличить одно от другого помогает `exists()`. */
|
|
39
73
|
export declare const isNull: () => Op;
|
|
40
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Отрицание оператора: `db.Product({ price: not(gte(1000)) })` — цена меньше 1000 или поля нет. Простое значение вместо
|
|
76
|
+
* оператора значит «не равно», как `ne()`: `db.Order({ status: not('cancelled') })`.
|
|
77
|
+
*/
|
|
41
78
|
export declare const not: (v: Scalar | Op) => Op;
|
|
42
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Хотя бы один из фильтров: `db.Product(or({ sku: 'K-1' }, { price: lt(500) }))` — товар K-1 или дешевле 500. Ставится
|
|
81
|
+
* вместо всего фильтра шага, а не значением поля; условия внутри одного фильтра соединяются через «и».
|
|
82
|
+
*/
|
|
43
83
|
export declare const or: (...filters: Record<string, unknown>[]) => Op;
|