letopis 0.20.3 → 1.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.
- package/AGENT-CHEATSHEET.en.md +368 -0
- package/AGENT-CHEATSHEET.md +354 -0
- package/CHANGELOG.md +348 -0
- package/MIGRATION.md +190 -0
- package/README.en.md +1937 -0
- package/README.md +1493 -3466
- package/dist/acl.d.ts +26 -50
- package/dist/acl.js +22 -267
- package/dist/admin.d.ts +138 -0
- package/dist/admin.js +170 -0
- package/dist/auth.d.ts +120 -73
- package/dist/auth.js +121 -306
- package/dist/cache.d.ts +73 -0
- package/dist/cache.js +148 -0
- package/dist/chain.d.ts +124 -191
- package/dist/chain.js +369 -551
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +164 -0
- package/dist/demo/booking.d.ts +289 -0
- package/dist/demo/booking.js +159 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +70 -0
- package/dist/import.d.ts +179 -0
- package/dist/import.js +792 -0
- package/dist/index.d.ts +172 -26
- package/dist/index.js +304 -178
- package/dist/jsonschema.d.ts +22 -0
- package/dist/jsonschema.js +167 -0
- package/dist/load.d.ts +76 -0
- package/dist/load.js +884 -0
- package/dist/model.d.ts +166 -0
- package/dist/model.js +224 -0
- package/dist/ops.d.ts +7 -6
- package/dist/ops.js +7 -51
- package/dist/pglite.d.ts +22 -0
- package/dist/pglite.js +45 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.js +82 -0
- package/dist/sql.d.ts +59 -142
- package/dist/sql.js +568 -654
- package/dist/sync.d.ts +31 -0
- package/dist/sync.js +108 -0
- package/dist/tx.d.ts +129 -8
- package/dist/tx.js +300 -73
- package/dist/typed.d.ts +97 -0
- package/dist/typed.js +1 -0
- package/dist/types.d.ts +71 -250
- package/dist/types.js +27 -108
- package/dist/up.d.ts +140 -47
- package/dist/up.js +339 -267
- package/dist/uuid.d.ts +21 -6
- package/dist/uuid.js +48 -64
- package/dist/validate.d.ts +24 -0
- package/dist/validate.js +251 -0
- package/dist/watch.d.ts +62 -0
- package/dist/watch.js +168 -0
- package/dist/write.d.ts +117 -74
- package/dist/write.js +658 -720
- package/llms.txt +26 -0
- package/package.json +49 -19
- package/sql/10-core.sql +136 -0
- package/sql/15-errors.sql +60 -0
- package/sql/20-context.sql +153 -0
- package/sql/30-validate.sql +423 -0
- package/sql/40-class.sql +259 -0
- package/sql/50-acl.sql +539 -0
- package/sql/60-write.sql +1369 -0
- package/sql/70-read.sql +245 -0
- package/sql/80-auth.sql +827 -0
- package/sql/90-time.sql +957 -0
- package/sql/95-seed.system.sql +178 -0
- package/sql/99-revision.sql +3 -0
- package/sql/README.md +56 -0
- package/sql/seed.booking.sql +39 -112
- package/dist/schema.d.ts +0 -15
- package/dist/schema.js +0 -351
- package/dist/sessions.d.ts +0 -32
- package/dist/sessions.js +0 -114
- package/dist/tables.d.ts +0 -105
- package/dist/tables.js +0 -248
- package/docker/Dockerfile +0 -40
- package/docker/start.sh +0 -18
- package/scripts/check-docs.mjs +0 -375
- package/scripts/gen-api-contract.mjs +0 -226
- package/scripts/gen-types.mjs +0 -350
- package/scripts/release-notes.mjs +0 -76
- package/scripts/schema-sync.mjs +0 -185
- package/sql/ddl.sql +0 -600
- package/sql/seed.auth.sql +0 -73
package/dist/watch.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Подписка на изменения (план, этап 6, п. 5): `for await (const ev of db.watch({ from }))`.
|
|
3
|
+
*
|
|
4
|
+
* «now» — голова в момент подписки: курсор с горизонта и снимок, чтобы не отдать транзакции,
|
|
5
|
+
* зафиксированные до подписки, но ещё не ушедшие за горизонт.
|
|
6
|
+
*
|
|
7
|
+
* Курсор — (tx, seq) записи журнала: события читаются только ниже горизонта завершённых
|
|
8
|
+
* транзакций (pg_snapshot_xmin) — транзакцию с меньшим номером, зафиксированную позже, курсор уже
|
|
9
|
+
* не обгонит, поэтому событий нет ни потерянных, ни повторных. Поток стоит, пока открыта более
|
|
10
|
+
* старая пишущая транзакция (горизонт общий для кластера); метрика — lag(). Чтение идёт под RLS
|
|
11
|
+
* журнала: каждый видит только своё. Сигнал базы с именем класса только будит подписку (чужие
|
|
12
|
+
* классы — не будят); без LISTEN и на случай потери сигнала — запасной опрос.
|
|
13
|
+
*
|
|
14
|
+
* Курсор подписчика старше яруса all политики хранения его классов — cursor_expired: события могли
|
|
15
|
+
* проредиться, нужно перечитать текущее состояние и подписаться заново.
|
|
16
|
+
*/
|
|
17
|
+
import { LetopisError } from './errors.js';
|
|
18
|
+
import { toRow } from './sql.js';
|
|
19
|
+
/** Курсор события: «tx:seq[:время][~снимок]». */
|
|
20
|
+
const encode = (p) => `${p.tx}:${p.seq}${p.at ? `:${p.at}` : ''}${p.snap ? `~${p.snap}` : ''}`;
|
|
21
|
+
function decode(c) {
|
|
22
|
+
const [head, snap] = c.split('~');
|
|
23
|
+
const m = /^(\d+):(\d+)(?::(.+))?$/.exec(head);
|
|
24
|
+
if (!m || (snap !== undefined && !/^\d+:\d+:[\d,]*$/.test(snap))) {
|
|
25
|
+
throw new LetopisError('invalid_query', `watch: курсор ${c} — ожидается курсор события`);
|
|
26
|
+
}
|
|
27
|
+
return { tx: m[1], seq: m[2], at: m[3] ?? null, ...(snap ? { snap } : {}) };
|
|
28
|
+
}
|
|
29
|
+
/** Снимок ещё нужен: курсор не дошёл до его xmax. */
|
|
30
|
+
const keepSnap = (snap, tx) => snap && BigInt(tx) < BigInt(snap.split(':')[1]) ? snap : undefined;
|
|
31
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
32
|
+
export class Subscription {
|
|
33
|
+
ctx;
|
|
34
|
+
opts;
|
|
35
|
+
pos = null;
|
|
36
|
+
buf = [];
|
|
37
|
+
closed = false;
|
|
38
|
+
wake = null;
|
|
39
|
+
dirty = true;
|
|
40
|
+
off = null;
|
|
41
|
+
timer = null;
|
|
42
|
+
failure = null;
|
|
43
|
+
batch;
|
|
44
|
+
pollMs;
|
|
45
|
+
/** 'now' — голова журнала в момент подписки (запрос уходит сразу). */
|
|
46
|
+
start = null;
|
|
47
|
+
constructor(ctx, opts) {
|
|
48
|
+
this.ctx = ctx;
|
|
49
|
+
this.opts = opts;
|
|
50
|
+
this.batch = opts.batch ?? 500;
|
|
51
|
+
this.pollMs = opts.pollMs ?? 1000;
|
|
52
|
+
const from = opts.from ?? 'now';
|
|
53
|
+
if (from === 'start')
|
|
54
|
+
this.pos = { tx: '0', seq: '0', at: null };
|
|
55
|
+
else if (from !== 'now')
|
|
56
|
+
this.pos = decode(from);
|
|
57
|
+
else {
|
|
58
|
+
this.start = this.head();
|
|
59
|
+
this.start.catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
if (ctx.onSignal) {
|
|
62
|
+
const watched = ctx.classes ? new Set(ctx.classes) : null;
|
|
63
|
+
this.off = ctx.onSignal((name) => {
|
|
64
|
+
if (name === '' || name.startsWith('session:'))
|
|
65
|
+
return;
|
|
66
|
+
if (watched && name !== '*' && !watched.has(name))
|
|
67
|
+
return;
|
|
68
|
+
this.dirty = true;
|
|
69
|
+
this.wake?.();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
this.timer = setInterval(() => {
|
|
73
|
+
this.dirty = true;
|
|
74
|
+
this.wake?.();
|
|
75
|
+
}, this.pollMs);
|
|
76
|
+
this.timer.unref?.();
|
|
77
|
+
}
|
|
78
|
+
/** Курсор последнего выданного события (или начальной точки). */
|
|
79
|
+
get cursor() {
|
|
80
|
+
return this.pos ? encode(this.pos) : null;
|
|
81
|
+
}
|
|
82
|
+
async head() {
|
|
83
|
+
const [r] = await this.ctx.exec.run((q) => q.unsafe(`select tx::text as tx, seq::text as seq, snap::text as snap from ${this.ctx.S}.watch_head()`), { read: true });
|
|
84
|
+
return { tx: r.tx, seq: r.seq, at: null, snap: r.snap };
|
|
85
|
+
}
|
|
86
|
+
/** Следующая пачка событий ниже горизонта; пусто — горизонт ещё не дошёл или событий нет. */
|
|
87
|
+
async fetch() {
|
|
88
|
+
if (!this.pos)
|
|
89
|
+
this.pos = await (this.start ?? this.head());
|
|
90
|
+
const p = this.pos;
|
|
91
|
+
const rows = await this.ctx.exec.run((q) => q.unsafe(`select l.tx::text as tx, l.seq::text as seq, jsonb_build_object('id', l.id, 'rev', l.rev, 'class', l.class, 'tenant', l.tenant, `
|
|
92
|
+
+ `'owner', l.owner, 'links', l.links, 'data', l.data, 'tags', l.tags, 'at', l.at, 'author', l.author, 'agent', l.agent, `
|
|
93
|
+
+ `'op', l.op, 'reason', l.reason, 'moved', l.moved) as row `
|
|
94
|
+
+ `from ${this.ctx.S}.watch_read($1::text::xid8, $2::bigint, $3::text::timestamptz, $4::text[], $5::int, $6::text::pg_snapshot) l`, [p.tx, p.seq, p.at, this.ctx.classes, this.batch, p.snap ?? null]), { read: true });
|
|
95
|
+
for (const r of rows) {
|
|
96
|
+
const row = toRow(r.row);
|
|
97
|
+
const snap = keepSnap(p.snap, r.tx);
|
|
98
|
+
this.pos = { tx: r.tx, seq: r.seq, at: row.at, ...(snap ? { snap } : {}) };
|
|
99
|
+
this.buf.push({ cursor: encode(this.pos), row, id: row.id, class: row.class, op: row.op, rev: row.rev, at: row.at });
|
|
100
|
+
}
|
|
101
|
+
return rows.length;
|
|
102
|
+
}
|
|
103
|
+
async next() {
|
|
104
|
+
let empty = 0;
|
|
105
|
+
for (;;) {
|
|
106
|
+
if (this.failure)
|
|
107
|
+
throw this.failure;
|
|
108
|
+
if (this.buf.length)
|
|
109
|
+
return { value: this.buf.shift(), done: false };
|
|
110
|
+
if (this.closed)
|
|
111
|
+
return { value: undefined, done: true };
|
|
112
|
+
if (this.dirty) {
|
|
113
|
+
try {
|
|
114
|
+
const n = await this.fetch();
|
|
115
|
+
if (n) {
|
|
116
|
+
empty = 0;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
this.failure = e;
|
|
122
|
+
this.close();
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
// сигнал был, а событий ниже горизонта ещё нет: короткие повторы, затем — запасной опрос
|
|
126
|
+
if (++empty < 40) {
|
|
127
|
+
await sleep(Math.min(25 * empty, 250));
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
this.dirty = false;
|
|
131
|
+
empty = 0;
|
|
132
|
+
}
|
|
133
|
+
await new Promise((r) => {
|
|
134
|
+
this.wake = () => {
|
|
135
|
+
this.wake = null;
|
|
136
|
+
r();
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Отставание, мс: сколько ждёт самое старое событие, которое уже зафиксировано, но ещё не ушло за
|
|
143
|
+
* горизонт (его держит более старая открытая транзакция); 0 — ждать нечего.
|
|
144
|
+
*/
|
|
145
|
+
async lag() {
|
|
146
|
+
const p = this.pos ?? (await (this.start ?? this.head()));
|
|
147
|
+
const [r] = await this.ctx.exec.run((q) => q.unsafe(`select coalesce(extract(epoch from clock_timestamp() - min(l.at)) * 1000, 0)::float8 as ms from ${this.ctx.S}.log l `
|
|
148
|
+
+ `where (l.tx, l.seq) > ($1::text::xid8, $2::bigint) and l.tx >= pg_snapshot_xmin(pg_current_snapshot())`
|
|
149
|
+
+ `${this.ctx.classes ? ' and l.class = any ($3::text[])' : ''}`, (this.ctx.classes ? [p.tx, p.seq, this.ctx.classes] : [p.tx, p.seq])), { read: true });
|
|
150
|
+
return Number(r?.ms ?? 0);
|
|
151
|
+
}
|
|
152
|
+
close() {
|
|
153
|
+
if (this.closed)
|
|
154
|
+
return;
|
|
155
|
+
this.closed = true;
|
|
156
|
+
this.off?.();
|
|
157
|
+
if (this.timer)
|
|
158
|
+
clearInterval(this.timer);
|
|
159
|
+
this.wake?.();
|
|
160
|
+
}
|
|
161
|
+
async return() {
|
|
162
|
+
this.close();
|
|
163
|
+
return { value: undefined, done: true };
|
|
164
|
+
}
|
|
165
|
+
[Symbol.asyncIterator]() {
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
}
|
package/dist/write.d.ts
CHANGED
|
@@ -1,82 +1,125 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}[]);
|
|
1
|
+
import type { ClassInfo, Registry } from './registry.js';
|
|
2
|
+
import { type AggSpec, type BuiltQuery, type ReadMode, type Step } from './sql.js';
|
|
3
|
+
import type { Tx } from './tx.js';
|
|
4
|
+
import type { ChainMods, Row } from './types.js';
|
|
5
|
+
/** Метка приращения; Symbol.for — маркер узнаёт и вторая копия пакета в процессе. */
|
|
6
|
+
export declare const INC: unique symbol;
|
|
7
|
+
export interface IncMarker {
|
|
8
|
+
readonly [INC]: true;
|
|
9
|
+
readonly by: number;
|
|
10
|
+
readonly start?: number;
|
|
12
11
|
}
|
|
13
|
-
interface RawRow {
|
|
14
|
-
partition: string;
|
|
15
|
-
id: string;
|
|
16
|
-
class: string;
|
|
17
|
-
data: Record<string, unknown>;
|
|
18
|
-
links: Record<string, string> | null;
|
|
19
|
-
tags: string[] | null;
|
|
20
|
-
account: string;
|
|
21
|
-
owner: string;
|
|
22
|
-
updated: string | Date;
|
|
23
|
-
deleted: string | Date | null;
|
|
24
|
-
}
|
|
25
|
-
export declare function toRow(r: RawRow): Row;
|
|
26
|
-
/** Прочитать актуальные строки по цепочке. */
|
|
27
|
-
export declare function readRows(ctx: Ctx, steps: Step[], mods?: ChainMods): Promise<Row[]>;
|
|
28
|
-
/**
|
|
29
|
-
* Deep-merge патча в базу: меняются ТОЛЬКО указанные листья.
|
|
30
|
-
* Вложенные plain-объекты сливаются рекурсивно; массивы/скаляры/null — заменяются.
|
|
31
|
-
*/
|
|
32
|
-
export declare function deepMerge(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
33
|
-
/**
|
|
34
|
-
* .create(data) — «чтобы сущность существовала»:
|
|
35
|
-
* - id не задан → INSERT (id по IdGen класса; links = концы из пути + слоты);
|
|
36
|
-
* - id известен (Класс(id) / data.id / вычислен v5): есть → новая версия (deep-merge),
|
|
37
|
-
* нет → INSERT с этим id (идемпотентный create, REST-PUT семантика);
|
|
38
|
-
* - фильтр-объект / pivot — ошибка: create не ищет, это update().
|
|
39
|
-
*/
|
|
40
|
-
export declare function createOp(ctx: Ctx, steps: Step[], mods: ChainMods, data: Record<string, unknown>): Promise<Row[]>;
|
|
41
|
-
/**
|
|
42
|
-
* .update(data?) — новая версия КАЖДОГО найденного путём (deep-merge листьев);
|
|
43
|
-
* цели: Класс() ≡ Класс({}) — все в границах контекста, id/фильтр/pivot — как в чтении.
|
|
44
|
-
* Не найдено → [] — update НИКОГДА не создаёт.
|
|
45
|
-
*/
|
|
46
|
-
export declare function updateOp(ctx: Ctx, steps: Step[], mods: ChainMods, data: Record<string, unknown>): Promise<Row[]>;
|
|
47
12
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
13
|
+
* Приращение в update: `update({ balance: inc(-300) })` — величина прибавляется в базе, внутри
|
|
14
|
+
* оператора, над свежей строкой (функция inc). Поля нет — ошибка; `{ start }` — начать с него.
|
|
50
15
|
*/
|
|
51
|
-
export declare function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
16
|
+
export declare function inc(by: number, opts?: {
|
|
17
|
+
start?: number;
|
|
18
|
+
}): IncMarker;
|
|
19
|
+
export declare const isInc: (v: unknown) => v is IncMarker;
|
|
20
|
+
export type PlanOp = {
|
|
21
|
+
kind: 'create';
|
|
22
|
+
data: Record<string, unknown>;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'update';
|
|
25
|
+
data: Record<string, unknown>;
|
|
26
|
+
rev?: number;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'upsert';
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'delete';
|
|
32
|
+
confirm: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'anonymize';
|
|
35
|
+
fields: string[];
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'reclass';
|
|
38
|
+
cls: string;
|
|
39
|
+
data?: Record<string, unknown>;
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'restore';
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'purge';
|
|
44
|
+
confirm: boolean;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'rekey';
|
|
47
|
+
data: Record<string, unknown>;
|
|
48
|
+
};
|
|
49
|
+
/** Значение слота: id, список id, строка ответа или ленивая цепочка (в той же транзакции). */
|
|
50
|
+
export type SlotValue = string | string[] | {
|
|
51
|
+
id: string;
|
|
52
|
+
} | null | {
|
|
53
|
+
plan: Step[];
|
|
54
|
+
mods: ChainMods;
|
|
55
|
+
};
|
|
56
|
+
export interface Slot {
|
|
57
|
+
/** Роль (конец) записываемого класса. */
|
|
58
|
+
role: string;
|
|
59
|
+
op: 'set' | 'unset' | 'add' | 'remove';
|
|
60
|
+
value: SlotValue;
|
|
61
|
+
}
|
|
62
|
+
/** Строка ответа записи (результат upsert — $upsert, превью удаления — $action — поля Row). */
|
|
63
|
+
export type WriteRow = Row;
|
|
64
|
+
export interface IncSpec {
|
|
65
|
+
path: string[];
|
|
66
|
+
by: number;
|
|
67
|
+
start?: number;
|
|
68
|
+
}
|
|
69
|
+
/** Патч update → остаток для merge и список приращений. Недопустимое — invalid_data до отправки. */
|
|
70
|
+
export declare function splitPatch(patch: Record<string, unknown>): {
|
|
71
|
+
rest: Record<string, unknown>;
|
|
72
|
+
incs: IncSpec[];
|
|
73
|
+
};
|
|
74
|
+
/** Всё, что проверяется без базы, — до отправки первого оператора (§2.5). */
|
|
75
|
+
export declare function precheckPlan(reg: Registry, steps: Step[]): void;
|
|
76
|
+
export interface WriteCtx {
|
|
77
|
+
reg: Registry;
|
|
78
|
+
schema: string;
|
|
79
|
+
q: Tx;
|
|
80
|
+
/** Арендатор сессии: для id «уже существует». */
|
|
81
|
+
tenant: string | null;
|
|
82
|
+
/** Владелец новых строк по умолчанию (db.as(…, { owner })). */
|
|
83
|
+
owner?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Чтение в транзакции плана — тот же построитель, что у цепочек. */
|
|
86
|
+
export declare function readIn<T>(w: WriteCtx, steps: Step[], mods: ChainMods, mode: ReadMode, agg?: AggSpec): Promise<{
|
|
87
|
+
q: BuiltQuery;
|
|
88
|
+
res: T[];
|
|
89
|
+
}>;
|
|
90
|
+
/** Роль слота: имя роли записываемого класса или класс, который принимает ровно одна роль. */
|
|
91
|
+
export declare function slotRole(reg: Registry, owner: ClassInfo, name: string): string;
|
|
92
|
+
interface Segment {
|
|
93
|
+
/** Шаги сегмента; последний несёт глагол. */
|
|
94
|
+
steps: Step[];
|
|
95
|
+
}
|
|
96
|
+
/** Разрезать план: сегменты (…шаги + шаг с глаголом) и читающий хвост после последнего глагола. */
|
|
97
|
+
export declare function splitPlan(steps: Step[]): {
|
|
98
|
+
segments: Segment[];
|
|
99
|
+
tail: Step[];
|
|
100
|
+
};
|
|
101
|
+
/** Результат плана: строки последнего глагола или чтение хвоста от них. */
|
|
102
|
+
export type PlanResult = {
|
|
103
|
+
kind: 'rows';
|
|
104
|
+
rows: WriteRow[];
|
|
105
|
+
key: string;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'read';
|
|
108
|
+
q: BuiltQuery | null;
|
|
109
|
+
res: unknown[];
|
|
110
|
+
};
|
|
59
111
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* confirm: false → dry-превью (что сотрётся, БД цела); true → снос, возвращает снесённое ($deleted).
|
|
64
|
-
* gate: DELETE-право на класс цели (aclWrite) — поддерево уже было DELETE-авторизовано при мягком удалении.
|
|
65
|
-
* В отличие от .delete() (tombstone, обратимо) — необратимо, историю НЕ сохраняет.
|
|
112
|
+
* Исполнить план в транзакции w.q: сегменты по порядку, продолжение — от результата предыдущего
|
|
113
|
+
* глагола (create и upsert — по строке, остальные — от всех строк сразу); хвост — чтение от
|
|
114
|
+
* результата последнего глагола тем же построителем, что у цепочек.
|
|
66
115
|
*/
|
|
67
|
-
export declare function
|
|
68
|
-
export type PlanMode = 'rows' | 'ids' | 'count' | 'paths' | 'versions' | 'agg';
|
|
116
|
+
export declare function execPlan(w: WriteCtx, steps: Step[], mods: ChainMods, mode: ReadMode, agg?: AggSpec): Promise<PlanResult>;
|
|
69
117
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
118
|
+
* Очередь планов одной транзакцией. Подряд идущие простые вставки одного класса с ключом — одна
|
|
119
|
+
* многострочная вставка, подряд идущие upsert — тоже (повтор id режет группу: один оператор не
|
|
120
|
+
* меняет строку дважды); результат сопоставляется со входом по вычисленному id. Вставки класса без
|
|
121
|
+
* ключа уходят однострочными операторами одним конвейером: порядок строк в returning
|
|
122
|
+
* многострочной вставки не гарантирован, сопоставить не по чему.
|
|
75
123
|
*/
|
|
76
|
-
export declare function
|
|
77
|
-
/** План в очереди батча; steps обновляется по мере роста цепочки. */
|
|
78
|
-
export interface BatchPlan {
|
|
79
|
-
steps: Step[];
|
|
80
|
-
}
|
|
81
|
-
export declare function executeBatch(ctx: Ctx, queue: BatchPlan[]): Promise<Row[][]>;
|
|
124
|
+
export declare function execBatch(w: WriteCtx, plans: Step[][]): Promise<WriteRow[][]>;
|
|
82
125
|
export {};
|