@verdeect/kit 0.0.0-stage → 1.0.0-rc.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/CHANGELOG.md +132 -0
- package/README.md +804 -2
- package/alerts/verdeect-kit.rules.yml +133 -0
- package/dist/cli/commands/crypto.d.ts +9 -0
- package/dist/cli/commands/crypto.js +87 -0
- package/dist/cli/commands/db.d.ts +11 -0
- package/dist/cli/commands/db.js +151 -0
- package/dist/cli/commands/openapi.d.ts +9 -0
- package/dist/cli/commands/openapi.js +144 -0
- package/dist/cli/commands/rabbitmq.d.ts +11 -0
- package/dist/cli/commands/rabbitmq.js +124 -0
- package/dist/cli/commands/redis.d.ts +11 -0
- package/dist/cli/commands/redis.js +119 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +31 -0
- package/dist/config/cli-schemas.d.ts +29 -0
- package/dist/config/cli-schemas.js +15 -0
- package/dist/config/define-env.d.ts +21 -0
- package/dist/config/define-env.js +43 -0
- package/dist/config/fragments.d.ts +105 -0
- package/dist/config/fragments.js +86 -0
- package/dist/config/helpers.d.ts +26 -0
- package/dist/config/helpers.js +62 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +7 -0
- package/dist/config/kit-config.module.d.ts +20 -0
- package/dist/config/kit-config.module.js +23 -0
- package/dist/config/parse-env.d.ts +14 -0
- package/dist/config/parse-env.js +28 -0
- package/dist/crypto/cipher.d.ts +61 -0
- package/dist/crypto/cipher.js +136 -0
- package/dist/crypto/index.d.ts +2 -0
- package/dist/crypto/index.js +2 -0
- package/dist/database/batches.d.ts +42 -0
- package/dist/database/batches.js +49 -0
- package/dist/database/db.d.ts +93 -0
- package/dist/database/db.js +234 -0
- package/dist/database/if-newer.d.ts +9 -0
- package/dist/database/if-newer.js +15 -0
- package/dist/database/index.d.ts +17 -0
- package/dist/database/index.js +13 -0
- package/dist/database/kit-database.module.d.ts +47 -0
- package/dist/database/kit-database.module.js +181 -0
- package/dist/database/migrations.d.ts +42 -0
- package/dist/database/migrations.js +142 -0
- package/dist/database/pg.d.ts +20 -0
- package/dist/database/pg.js +19 -0
- package/dist/database/prepare-database.d.ts +39 -0
- package/dist/database/prepare-database.js +75 -0
- package/dist/database/run-exclusive.d.ts +19 -0
- package/dist/database/run-exclusive.js +62 -0
- package/dist/database/tenant.d.ts +34 -0
- package/dist/database/tenant.js +28 -0
- package/dist/http/api/api.decorator.d.ts +18 -0
- package/dist/http/api/api.decorator.js +9 -0
- package/dist/http/api/openapi-document.d.ts +40 -0
- package/dist/http/api/openapi-document.js +452 -0
- package/dist/http/bootstrap-service.d.ts +33 -0
- package/dist/http/bootstrap-service.js +107 -0
- package/dist/http/create-http-app.d.ts +38 -0
- package/dist/http/create-http-app.js +137 -0
- package/dist/http/errors.d.ts +118 -0
- package/dist/http/errors.js +94 -0
- package/dist/http/index.d.ts +12 -0
- package/dist/http/index.js +8 -0
- package/dist/http/kit-exception.filter.d.ts +18 -0
- package/dist/http/kit-exception.filter.js +151 -0
- package/dist/http/kit-http-core.module.d.ts +19 -0
- package/dist/http/kit-http-core.module.js +122 -0
- package/dist/http/pagination.d.ts +30 -0
- package/dist/http/pagination.js +73 -0
- package/dist/http/zod-validation.pipe.d.ts +15 -0
- package/dist/http/zod-validation.pipe.js +47 -0
- package/dist/idempotency/idempotency.interceptor.d.ts +55 -0
- package/dist/idempotency/idempotency.interceptor.js +317 -0
- package/dist/idempotency/idempotent.decorator.d.ts +7 -0
- package/dist/idempotency/idempotent.decorator.js +16 -0
- package/dist/idempotency/index.d.ts +7 -0
- package/dist/idempotency/index.js +6 -0
- package/dist/idempotency/kit-idempotency.module.d.ts +47 -0
- package/dist/idempotency/kit-idempotency.module.js +173 -0
- package/dist/idempotency/peer.d.ts +1 -0
- package/dist/idempotency/peer.js +4 -0
- package/dist/idempotency/table.d.ts +203 -0
- package/dist/idempotency/table.js +86 -0
- package/dist/internal/cleanup-result.d.ts +7 -0
- package/dist/internal/cleanup-result.js +12 -0
- package/dist/internal/ids.d.ts +13 -0
- package/dist/internal/ids.js +41 -0
- package/dist/internal/metadata.d.ts +12 -0
- package/dist/internal/metadata.js +3 -0
- package/dist/internal/outbox-policy.d.ts +8 -0
- package/dist/internal/outbox-policy.js +10 -0
- package/dist/internal/peer.d.ts +16 -0
- package/dist/internal/peer.js +56 -0
- package/dist/internal/pg-errors.d.ts +21 -0
- package/dist/internal/pg-errors.js +115 -0
- package/dist/internal/preparsed-env.d.ts +4 -0
- package/dist/internal/preparsed-env.js +14 -0
- package/dist/internal/rabbitmq-definitions.d.ts +85 -0
- package/dist/internal/rabbitmq-definitions.js +73 -0
- package/dist/internal/redis-acl.d.ts +21 -0
- package/dist/internal/redis-acl.js +30 -0
- package/dist/internal/service-name.d.ts +4 -0
- package/dist/internal/service-name.js +10 -0
- package/dist/internal/sql-identifier.d.ts +5 -0
- package/dist/internal/sql-identifier.js +12 -0
- package/dist/internal/tokens.d.ts +10 -0
- package/dist/internal/tokens.js +12 -0
- package/dist/internal/tx-context.d.ts +5 -0
- package/dist/internal/tx-context.js +9 -0
- package/dist/messaging/cleanup.d.ts +49 -0
- package/dist/messaging/cleanup.js +129 -0
- package/dist/messaging/config.d.ts +43 -0
- package/dist/messaging/config.js +8 -0
- package/dist/messaging/define-event.d.ts +29 -0
- package/dist/messaging/define-event.js +38 -0
- package/dist/messaging/envelope.d.ts +66 -0
- package/dist/messaging/envelope.js +49 -0
- package/dist/messaging/errors.d.ts +45 -0
- package/dist/messaging/errors.js +63 -0
- package/dist/messaging/inbox.d.ts +4 -0
- package/dist/messaging/inbox.js +9 -0
- package/dist/messaging/index.d.ts +13 -0
- package/dist/messaging/index.js +10 -0
- package/dist/messaging/kit-messaging.module.d.ts +39 -0
- package/dist/messaging/kit-messaging.module.js +309 -0
- package/dist/messaging/outbox-relay.d.ts +67 -0
- package/dist/messaging/outbox-relay.js +260 -0
- package/dist/messaging/outbox-store.d.ts +75 -0
- package/dist/messaging/outbox-store.js +126 -0
- package/dist/messaging/peer.d.ts +1 -0
- package/dist/messaging/peer.js +5 -0
- package/dist/messaging/rabbit-connection.d.ts +144 -0
- package/dist/messaging/rabbit-connection.js +472 -0
- package/dist/messaging/redelivery.d.ts +6 -0
- package/dist/messaging/redelivery.js +17 -0
- package/dist/messaging/subscriber.d.ts +102 -0
- package/dist/messaging/subscriber.js +412 -0
- package/dist/messaging/tables.d.ts +315 -0
- package/dist/messaging/tables.js +72 -0
- package/dist/messaging/topology.d.ts +56 -0
- package/dist/messaging/topology.js +56 -0
- package/dist/observability/fail-startup.d.ts +14 -0
- package/dist/observability/fail-startup.js +51 -0
- package/dist/observability/health-registry.d.ts +34 -0
- package/dist/observability/health-registry.js +81 -0
- package/dist/observability/index.d.ts +16 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/lifecycle.d.ts +52 -0
- package/dist/observability/lifecycle.js +151 -0
- package/dist/observability/logger-options.d.ts +54 -0
- package/dist/observability/logger-options.js +91 -0
- package/dist/observability/metrics-server.d.ts +33 -0
- package/dist/observability/metrics-server.js +108 -0
- package/dist/observability/metrics.d.ts +30 -0
- package/dist/observability/metrics.js +53 -0
- package/dist/observability/redaction.d.ts +23 -0
- package/dist/observability/redaction.js +65 -0
- package/dist/observability/sanitize-error.d.ts +47 -0
- package/dist/observability/sanitize-error.js +194 -0
- package/dist/observability/sentry.d.ts +64 -0
- package/dist/observability/sentry.js +233 -0
- package/dist/redis/channels.d.ts +34 -0
- package/dist/redis/channels.js +128 -0
- package/dist/redis/client.d.ts +42 -0
- package/dist/redis/client.js +85 -0
- package/dist/redis/index.d.ts +7 -0
- package/dist/redis/index.js +7 -0
- package/dist/redis/kit-redis.module.d.ts +18 -0
- package/dist/redis/kit-redis.module.js +131 -0
- package/dist/redis/peer.d.ts +1 -0
- package/dist/redis/peer.js +4 -0
- package/dist/redis/rate-limiter.d.ts +49 -0
- package/dist/redis/rate-limiter.js +139 -0
- package/dist/testing/count-queries.d.ts +30 -0
- package/dist/testing/count-queries.js +58 -0
- package/dist/testing/create-test-app.d.ts +26 -0
- package/dist/testing/create-test-app.js +32 -0
- package/dist/testing/expect-matches-api.d.ts +8 -0
- package/dist/testing/expect-matches-api.js +49 -0
- package/dist/testing/expect-problem.d.ts +24 -0
- package/dist/testing/expect-problem.js +23 -0
- package/dist/testing/images.d.ts +10 -0
- package/dist/testing/images.js +14 -0
- package/dist/testing/index.d.ts +23 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/launch-command.d.ts +52 -0
- package/dist/testing/launch-command.js +510 -0
- package/dist/testing/outbox-coverage.d.ts +26 -0
- package/dist/testing/outbox-coverage.js +98 -0
- package/dist/testing/rabbitmq-admin.d.ts +81 -0
- package/dist/testing/rabbitmq-admin.js +146 -0
- package/dist/testing/redis-worker.d.ts +10 -0
- package/dist/testing/redis-worker.js +15 -0
- package/dist/testing/repo-conventions.d.ts +10 -0
- package/dist/testing/repo-conventions.js +524 -0
- package/dist/testing/repo-files.d.ts +113 -0
- package/dist/testing/repo-files.js +401 -0
- package/dist/testing/scratch-database.d.ts +19 -0
- package/dist/testing/scratch-database.js +75 -0
- package/dist/testing/start-test-stack.d.ts +85 -0
- package/dist/testing/start-test-stack.js +342 -0
- package/dist/testing/tcp-proxy.d.ts +40 -0
- package/dist/testing/tcp-proxy.js +164 -0
- package/dist/testing/tenant-isolation.d.ts +31 -0
- package/dist/testing/tenant-isolation.js +205 -0
- package/dist/testing/test-events.d.ts +37 -0
- package/dist/testing/test-events.js +150 -0
- package/dist/testing/timeout-scale.d.ts +4 -0
- package/dist/testing/timeout-scale.js +17 -0
- package/dist/testing/timing-guard.d.ts +22 -0
- package/dist/testing/timing-guard.js +54 -0
- package/dist/testing/worker-scope.d.ts +31 -0
- package/dist/testing/worker-scope.js +68 -0
- package/package.json +177 -4
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Db (prd.md §3.3, AC-4.1–AC-4.5, AC-4.16, UC-5). Эталон — O db.ts + pool-client.ts (withPoolClient, новый Drizzle на
|
|
2
|
+
// клиент, afterCommit через WeakMap). Из I — отслеживание пачек для остановки (batchRuns). По PRD: read без тенанта —
|
|
3
|
+
// TenantRequiredError, readGlobal — пул чтения без транзакции, afterCommit в read-only — AfterCommitInReadOnlyError,
|
|
4
|
+
// setLocal. Срок пула учитывает сам пул (kit-database.module.ts, KT-19 п. 3).
|
|
5
|
+
import { Logger } from '@nestjs/common';
|
|
6
|
+
import { sql } from 'drizzle-orm';
|
|
7
|
+
import { isConnectionError } from '../internal/pg-errors.js';
|
|
8
|
+
import { bindTxContext } from '../internal/tx-context.js';
|
|
9
|
+
import { sanitizeError } from '../observability/sanitize-error.js';
|
|
10
|
+
import { captureError } from '../observability/sentry.js';
|
|
11
|
+
import { TENANT_SETTING } from './tenant.js';
|
|
12
|
+
/** `Db.read` без тенанта: чтение без компании — только `Db.readGlobal` (строгий RLS, P0). */
|
|
13
|
+
export class TenantRequiredError extends Error {
|
|
14
|
+
name = 'TenantRequiredError';
|
|
15
|
+
constructor() {
|
|
16
|
+
super('Db.read: нет тенанта в DbContext — для чтения без компании используйте Db.readGlobal');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** `Db.afterCommit` в read-only транзакции или на `readGlobal`: задачи после `COMMIT` там невозможны. */
|
|
20
|
+
export class AfterCommitInReadOnlyError extends Error {
|
|
21
|
+
name = 'AfterCommitInReadOnlyError';
|
|
22
|
+
constructor() {
|
|
23
|
+
super('Db.afterCommit: read-only транзакция или readGlobal — задача после COMMIT невозможна');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `Db.afterCommit` с дескриптором, который не является транзакцией этого `Db` (другой `Db`, `DRIZZLE`, вложенная
|
|
28
|
+
* `tx.transaction()`): задача никогда бы не выполнилась — ошибка вместо молчаливой потери.
|
|
29
|
+
*/
|
|
30
|
+
export class AfterCommitOutsideTransactionError extends Error {
|
|
31
|
+
name = 'AfterCommitOutsideTransactionError';
|
|
32
|
+
constructor() {
|
|
33
|
+
super('Db.afterCommit: нужна транзакция этого Db (Db.transaction) — не вложенная и не другого Db');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Имя переменной `Db.setLocal`. */
|
|
37
|
+
const LOCAL_NAME = /^app\.[a-z_]{1,48}$/;
|
|
38
|
+
/** Служебные переменные пакета: задаются только `DbContext` в `transaction`/`read`, не `setLocal` (KT-22 п. 1). */
|
|
39
|
+
const RESERVED_LOCALS = new Set([TENANT_SETTING]);
|
|
40
|
+
const internals = new WeakMap();
|
|
41
|
+
function internalsOf(db) {
|
|
42
|
+
const value = internals.get(db);
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
throw new TypeError('Db: объект не создан конструктором Db');
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
/** Основной пул `Db` (для `runExclusive`). */
|
|
49
|
+
export function mainPoolOf(db) {
|
|
50
|
+
return internalsOf(db).main;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Транзакция `Db` (тенант, `afterCommit`) на уже выданном клиенте — например, на соединении блокировки `runExclusive`,
|
|
54
|
+
* без второго соединения пула (KT-22 п. 4). Клиент не возвращается в пул: им владеет вызывающий.
|
|
55
|
+
*/
|
|
56
|
+
export function transactionOn(db, client, ctx, fn) {
|
|
57
|
+
return internalsOf(db).runOn(client, ctx, fn);
|
|
58
|
+
}
|
|
59
|
+
/** Идёт остановка `Db` (шаг 3): новые пачки не начинаются. */
|
|
60
|
+
export function isStopping(db) {
|
|
61
|
+
return internalsOf(db).stopping;
|
|
62
|
+
}
|
|
63
|
+
/** Регистрирует проход пачек: `beforeApplicationShutdown` дожидается его. */
|
|
64
|
+
export function trackBatchRun(db, run) {
|
|
65
|
+
const { batchRuns } = internalsOf(db);
|
|
66
|
+
batchRuns.add(run);
|
|
67
|
+
const forget = () => void batchRuns.delete(run);
|
|
68
|
+
run.then(forget, forget);
|
|
69
|
+
return run;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Выделенный клиент пула на одну единицу работы; возвращается в пул всегда. Обрыв (событие `error` клиента или
|
|
73
|
+
* ошибка соединения из `work`) — `release(err)`: pg-pool уничтожает клиента, пул не исчерпывается (AC-4.4). drizzle-orm
|
|
74
|
+
* 0.45.4 выполняет BEGIN вне try/finally своей transaction() — поэтому клиент берётся здесь, а не Drizzle над пулом.
|
|
75
|
+
*/
|
|
76
|
+
export async function withPoolClient(pool, work, onBroken) {
|
|
77
|
+
const client = await pool.connect();
|
|
78
|
+
let broken;
|
|
79
|
+
const onError = (error) => {
|
|
80
|
+
broken ??= error;
|
|
81
|
+
};
|
|
82
|
+
client.on('error', onError);
|
|
83
|
+
try {
|
|
84
|
+
return await work(client);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (broken === undefined && isConnectionError(error)) {
|
|
88
|
+
broken = error instanceof Error ? error : new Error('соединение БД разорвано');
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
client.removeListener('error', onError);
|
|
94
|
+
client.release(broken);
|
|
95
|
+
if (broken !== undefined) {
|
|
96
|
+
onBroken?.(broken);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Работа сервиса с БД под строгим RLS (§3.3): один `set_config('app.tenant', …, true)` на транзакцию, чтение без
|
|
102
|
+
* тенанта — только `readGlobal`, задачи после `COMMIT`, своя переменная `app.*`. Собирается `KitDatabaseModule`.
|
|
103
|
+
*/
|
|
104
|
+
export class Db {
|
|
105
|
+
logger = new Logger('Db');
|
|
106
|
+
afterCommitTasks = new WeakMap();
|
|
107
|
+
transactions = new WeakSet();
|
|
108
|
+
readOnly = new WeakSet();
|
|
109
|
+
drizzle;
|
|
110
|
+
readPool;
|
|
111
|
+
reader;
|
|
112
|
+
/** @param options пулы (с учётом срока — KitDatabaseModule) и драйвер Drizzle. */
|
|
113
|
+
constructor(options) {
|
|
114
|
+
this.drizzle = options.drizzle;
|
|
115
|
+
this.readPool = options.read;
|
|
116
|
+
this.reader = options.drizzle(options.read);
|
|
117
|
+
this.readOnly.add(this.reader);
|
|
118
|
+
internals.set(this, {
|
|
119
|
+
main: options.main,
|
|
120
|
+
runOn: (client, ctx, fn) => this.run(ctx, fn, false, client),
|
|
121
|
+
batchRuns: new Set(),
|
|
122
|
+
stopping: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Транзакция чтения и записи на основном пуле с тенантом `ctx.tenant` (`null` — строки компаний не видны). */
|
|
126
|
+
transaction(ctx, fn) {
|
|
127
|
+
return this.run(ctx, fn, false);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Read-only транзакция на пуле чтения (`statement_timeout` 5 с) с тенантом.
|
|
131
|
+
* @throws {TenantRequiredError} в `ctx` нет тенанта (до подключения).
|
|
132
|
+
*/
|
|
133
|
+
read(ctx, fn) {
|
|
134
|
+
if (ctx.tenant === null || ctx.tenant === '') {
|
|
135
|
+
return Promise.reject(new TenantRequiredError());
|
|
136
|
+
}
|
|
137
|
+
return this.run(ctx, fn, true);
|
|
138
|
+
}
|
|
139
|
+
/** Чтение без компании (глобальные таблицы): пул чтения без транзакции и без `set_config`. */
|
|
140
|
+
async readGlobal(fn) {
|
|
141
|
+
return fn(this.reader);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Задача после `COMMIT` транзакции `tx` — по порядку, до возврата результата `transaction`; при откате не
|
|
145
|
+
* выполняется; ошибка задачи — `error`-лог и GlitchTip, не пробрасывается. Только для того, что можно потерять.
|
|
146
|
+
* @throws {AfterCommitInReadOnlyError} `tx` — транзакция `read` или дескриптор `readGlobal`.
|
|
147
|
+
* @throws {AfterCommitOutsideTransactionError} `tx` — не транзакция этого `Db` (другой `Db`, вложенная).
|
|
148
|
+
*/
|
|
149
|
+
afterCommit(tx, task) {
|
|
150
|
+
if (this.readOnly.has(tx)) {
|
|
151
|
+
throw new AfterCommitInReadOnlyError();
|
|
152
|
+
}
|
|
153
|
+
if (!this.transactions.has(tx)) {
|
|
154
|
+
throw new AfterCommitOutsideTransactionError();
|
|
155
|
+
}
|
|
156
|
+
const tasks = this.afterCommitTasks.get(tx) ?? [];
|
|
157
|
+
tasks.push(task);
|
|
158
|
+
this.afterCommitTasks.set(tx, tasks);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Переменная транзакции `app.*` (`set_config(name, value, true)`), имя и значение — параметрами (AC-4.16).
|
|
162
|
+
* Служебная `app.tenant` запрещена: тенант задаётся только `DbContext` в `transaction`/`read`.
|
|
163
|
+
* @throws {TypeError} имя вне `^app\.[a-z_]{1,48}$`, служебное имя пакета или `tx` — не транзакция `Db`.
|
|
164
|
+
*/
|
|
165
|
+
async setLocal(tx, name, value) {
|
|
166
|
+
if (!LOCAL_NAME.test(name)) {
|
|
167
|
+
throw new TypeError('Db.setLocal: имя вне ^app\\.[a-z_]{1,48}$');
|
|
168
|
+
}
|
|
169
|
+
if (RESERVED_LOCALS.has(name)) {
|
|
170
|
+
throw new TypeError(`Db.setLocal: ${name} — служебная переменная пакета; тенант задаётся только DbContext в Db.transaction или Db.read`);
|
|
171
|
+
}
|
|
172
|
+
if (!this.transactions.has(tx)) {
|
|
173
|
+
throw new TypeError('Db.setLocal: нужна транзакция Db.transaction или Db.read');
|
|
174
|
+
}
|
|
175
|
+
await tx.execute(sql `select set_config(${name}, ${value}, true)`);
|
|
176
|
+
}
|
|
177
|
+
/** Шаг 3 остановки: новые пачки `deleteInBatches` не начинаются, идущие дорабатывают. */
|
|
178
|
+
async beforeApplicationShutdown() {
|
|
179
|
+
const state = internalsOf(this);
|
|
180
|
+
state.stopping = true;
|
|
181
|
+
this.logger.debug({ batches: state.batchRuns.size }, 'Db: остановка, ожидание пачек');
|
|
182
|
+
await Promise.allSettled([...state.batchRuns]);
|
|
183
|
+
}
|
|
184
|
+
async run(ctx, fn, readOnly, held) {
|
|
185
|
+
const pool = readOnly ? this.readPool : mainPoolOf(this);
|
|
186
|
+
let current;
|
|
187
|
+
// ponytail: экземпляр Drizzle на транзакцию (без схемы — микросекунды); кэш — если покажет профиль.
|
|
188
|
+
const onClient = (client) => this.drizzle(client).transaction(async (tx) => {
|
|
189
|
+
current = tx;
|
|
190
|
+
this.transactions.add(tx);
|
|
191
|
+
bindTxContext(tx, ctx);
|
|
192
|
+
if (readOnly) {
|
|
193
|
+
this.readOnly.add(tx);
|
|
194
|
+
}
|
|
195
|
+
await tx.execute(sql `select set_config('app.tenant', ${ctx.tenant ?? ''}, true)`);
|
|
196
|
+
return fn(tx);
|
|
197
|
+
}, { accessMode: readOnly ? 'read only' : 'read write' });
|
|
198
|
+
try {
|
|
199
|
+
const result = held === undefined
|
|
200
|
+
? await withPoolClient(pool, onClient, (error) => this.logger.debug({ traceId: ctx.traceId, err: sanitizeError(error) }, 'Db: соединение сломано, клиент уничтожен'))
|
|
201
|
+
: await onClient(held);
|
|
202
|
+
await this.runAfterCommit(current, ctx.traceId);
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (current !== undefined) {
|
|
207
|
+
this.afterCommitTasks.delete(current);
|
|
208
|
+
}
|
|
209
|
+
this.logger.debug({
|
|
210
|
+
tenant: ctx.tenant,
|
|
211
|
+
traceId: ctx.traceId,
|
|
212
|
+
readOnly,
|
|
213
|
+
err: sanitizeError(error),
|
|
214
|
+
}, 'Db: транзакция откатилась');
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async runAfterCommit(tx, traceId) {
|
|
219
|
+
const tasks = tx === undefined ? undefined : this.afterCommitTasks.get(tx);
|
|
220
|
+
if (tx === undefined || tasks === undefined) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.afterCommitTasks.delete(tx);
|
|
224
|
+
for (const task of tasks) {
|
|
225
|
+
try {
|
|
226
|
+
await task();
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
this.logger.error({ traceId, err: sanitizeError(error) }, 'Db.afterCommit: задача упала');
|
|
230
|
+
captureError(error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SQL } from 'drizzle-orm';
|
|
2
|
+
import { type PgColumn } from 'drizzle-orm/pg-core';
|
|
3
|
+
/** Колонка времени последнего применённого события: `timestamp with time zone`, nullable. */
|
|
4
|
+
export declare function occurredAtColumn(name: string): import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Условие `WHERE` обновления: `(<column> IS NULL OR <column> < occurredAt)`. `rowCount` 0 — нормальный итог для
|
|
7
|
+
* устаревшего события.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ifNewer(column: PgColumn, occurredAt: Date): SQL;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// occurredAtColumn, ifNewer (prd.md §3.3, AC-4.13, UC-6): порядок по occurredAt — помощник обработчика, не поведение
|
|
2
|
+
// подписчика (inventory §4 п. 9). Файл без pg.
|
|
3
|
+
import { sql } from 'drizzle-orm';
|
|
4
|
+
import { timestamp } from 'drizzle-orm/pg-core';
|
|
5
|
+
/** Колонка времени последнего применённого события: `timestamp with time zone`, nullable. */
|
|
6
|
+
export function occurredAtColumn(name) {
|
|
7
|
+
return timestamp(name, { withTimezone: true, mode: 'date' });
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Условие `WHERE` обновления: `(<column> IS NULL OR <column> < occurredAt)`. `rowCount` 0 — нормальный итог для
|
|
11
|
+
* устаревшего события.
|
|
12
|
+
*/
|
|
13
|
+
export function ifNewer(column, occurredAt) {
|
|
14
|
+
return sql `(${column} IS NULL OR ${column} < ${occurredAt})`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import './pg.js';
|
|
2
|
+
export { deleteInBatches } from './batches.js';
|
|
3
|
+
export type { KitDeleteInBatchesOptions, KitDeleteInBatchesResult } from './batches.js';
|
|
4
|
+
export { AfterCommitInReadOnlyError, AfterCommitOutsideTransactionError, Db, TenantRequiredError, } from './db.js';
|
|
5
|
+
export type { AfterCommitTask, DbReader, DbTx } from './db.js';
|
|
6
|
+
export { ifNewer, occurredAtColumn } from './if-newer.js';
|
|
7
|
+
export { DRIZZLE, DRIZZLE_READ, KitDatabaseModule } from './kit-database.module.js';
|
|
8
|
+
export type { KitDatabaseModuleOptions } from './kit-database.module.js';
|
|
9
|
+
export { runMigrations } from './migrations.js';
|
|
10
|
+
export type { KitRunMigrationsOptions } from './migrations.js';
|
|
11
|
+
export { prepareDatabase } from './prepare-database.js';
|
|
12
|
+
export type { KitPrepareDatabaseOptions, KitPrepareDatabaseResult } from './prepare-database.js';
|
|
13
|
+
export { runExclusive } from './run-exclusive.js';
|
|
14
|
+
export { tenantColumn, tenantPolicy } from './tenant.js';
|
|
15
|
+
export type { DbContext } from './tenant.js';
|
|
16
|
+
export { newId, ulid } from '../internal/ids.js';
|
|
17
|
+
export { isConnectionError, isPoolTimeout, isUniqueViolation } from '../internal/pg-errors.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Точка входа @verdeect/kit/database (prd.md §3.3, E4): явный список экспортов. Первый импорт — ./pg.js: без pg ошибка
|
|
2
|
+
// AC-1.2 при импорте подпути, раньше остальных модулей (requirePeer синхронно, без top-level await — П15).
|
|
3
|
+
import './pg.js';
|
|
4
|
+
export { deleteInBatches } from './batches.js';
|
|
5
|
+
export { AfterCommitInReadOnlyError, AfterCommitOutsideTransactionError, Db, TenantRequiredError, } from './db.js';
|
|
6
|
+
export { ifNewer, occurredAtColumn } from './if-newer.js';
|
|
7
|
+
export { DRIZZLE, DRIZZLE_READ, KitDatabaseModule } from './kit-database.module.js';
|
|
8
|
+
export { runMigrations } from './migrations.js';
|
|
9
|
+
export { prepareDatabase } from './prepare-database.js';
|
|
10
|
+
export { runExclusive } from './run-exclusive.js';
|
|
11
|
+
export { tenantColumn, tenantPolicy } from './tenant.js';
|
|
12
|
+
export { newId, ulid } from '../internal/ids.js';
|
|
13
|
+
export { isConnectionError, isPoolTimeout, isUniqueViolation } from '../internal/pg-errors.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type DynamicModule, type OnApplicationShutdown, type OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import type { KitDatabaseEnv } from '../config/fragments.js';
|
|
3
|
+
import { HealthRegistry } from '../observability/health-registry.js';
|
|
4
|
+
import type { KitMetrics } from '../observability/metrics.js';
|
|
5
|
+
import { type Pool } from './pg.js';
|
|
6
|
+
/** Drizzle основного пула со схемой сервиса — запросы вне `Db` (`drizzle(pool, {schema})`). */
|
|
7
|
+
export declare const DRIZZLE: unique symbol;
|
|
8
|
+
/** Drizzle пула чтения со схемой сервиса (read-only, `statement_timeout` 5 с). */
|
|
9
|
+
export declare const DRIZZLE_READ: unique symbol;
|
|
10
|
+
/**
|
|
11
|
+
* Параметры старта соединений пула чтения (эталон O): одиночный запрос — read-only транзакция, срок 5 с, `app.tenant`
|
|
12
|
+
* пуст с подключения.
|
|
13
|
+
*/
|
|
14
|
+
export declare const READ_POOL_OPTIONS = "-c default_transaction_read_only=on -c statement_timeout=5000 -c app.tenant=";
|
|
15
|
+
/** Пулы `/database`. */
|
|
16
|
+
export interface KitPools {
|
|
17
|
+
/** Основной пул. */
|
|
18
|
+
readonly main: Pool;
|
|
19
|
+
/** Пул чтения. */
|
|
20
|
+
readonly read: Pool;
|
|
21
|
+
}
|
|
22
|
+
/** Опции `KitDatabaseModule.forRoot`. */
|
|
23
|
+
export interface KitDatabaseModuleOptions {
|
|
24
|
+
/** Объект схемы Drizzle сервиса (`import * as schema`) — для `DRIZZLE` и `DRIZZLE_READ`. */
|
|
25
|
+
readonly schema: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/** Основной пул и пул чтения на `DATABASE_URL` с учётом срока; адрес не логируется. */
|
|
28
|
+
export declare function createPools(env: KitDatabaseEnv, options?: {
|
|
29
|
+
readonly metrics?: KitMetrics;
|
|
30
|
+
}): Promise<KitPools>;
|
|
31
|
+
/**
|
|
32
|
+
* Глобальный модуль БД сервиса (§3.3): `Db`, `DRIZZLE`, `DRIZZLE_READ`; пробы `database` и `database-read` (критичные,
|
|
33
|
+
* при `createHttpApp`), метрика срока пула; пулы закрываются на шаге 4 (`onApplicationShutdown`). Окружение —
|
|
34
|
+
* `envDatabase` из `KitConfigModule`. Соединений на реплику — `2 × DB_POOL_MAX`.
|
|
35
|
+
*/
|
|
36
|
+
export declare class KitDatabaseModule implements OnModuleInit, OnApplicationShutdown {
|
|
37
|
+
private readonly pools;
|
|
38
|
+
private readonly health?;
|
|
39
|
+
private readonly logger;
|
|
40
|
+
constructor(pools: KitPools, health?: HealthRegistry | undefined);
|
|
41
|
+
/** Модуль БД со схемой Drizzle сервиса. */
|
|
42
|
+
static forRoot(options: KitDatabaseModuleOptions): DynamicModule;
|
|
43
|
+
/** Пробы `/health` обоих пулов — `select 1` (без `Db.read`: ему нужен тенант). */
|
|
44
|
+
onModuleInit(): void;
|
|
45
|
+
/** Шаг 4 остановки: закрытие пулов после HTTP и фоновых задач. */
|
|
46
|
+
onApplicationShutdown(): Promise<void>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var KitDatabaseModule_1;
|
|
14
|
+
// KitDatabaseModule.forRoot({schema}) (prd.md §3.3, AC-4.4, AC-4.5, UC-8). Эталон — O drizzle.provider.ts +
|
|
15
|
+
// database.module.ts: пулы main/read, READ_POOL_OPTIONS, обработчики error/connect, проба database, onApplicationShutdown
|
|
16
|
+
// — шаг 4. По PRD и KT-19 п. 3: срок пула учитывается на уровне пула (обёртка connect экземпляра) — метрика с меткой
|
|
17
|
+
// pool, captureThrottled и метка ошибки tagPoolTimeout; пробы database и database-read; KitLifecycle не используется.
|
|
18
|
+
import { Inject, Logger, Module, Optional, } from '@nestjs/common';
|
|
19
|
+
import { ConfigService } from '@nestjs/config';
|
|
20
|
+
import { isPoolTimeout, tagPoolTimeout } from '../internal/pg-errors.js';
|
|
21
|
+
import { KIT_METRICS } from '../internal/tokens.js';
|
|
22
|
+
import { HealthRegistry } from '../observability/health-registry.js';
|
|
23
|
+
import { sanitizeError } from '../observability/sanitize-error.js';
|
|
24
|
+
import { captureThrottled } from '../observability/sentry.js';
|
|
25
|
+
import { Db } from './db.js';
|
|
26
|
+
import { loadDrizzle, loadPg } from './pg.js';
|
|
27
|
+
/** Drizzle основного пула со схемой сервиса — запросы вне `Db` (`drizzle(pool, {schema})`). */
|
|
28
|
+
export const DRIZZLE = Symbol('DRIZZLE');
|
|
29
|
+
/** Drizzle пула чтения со схемой сервиса (read-only, `statement_timeout` 5 с). */
|
|
30
|
+
export const DRIZZLE_READ = Symbol('DRIZZLE_READ');
|
|
31
|
+
/** Пулы основной и чтения (внутренний токен). */
|
|
32
|
+
const KIT_PG_POOLS = Symbol('KIT_PG_POOLS');
|
|
33
|
+
/** Окружение модуля (внутренний токен). */
|
|
34
|
+
const KIT_DATABASE_ENV = Symbol('KIT_DATABASE_ENV');
|
|
35
|
+
/** Предел запроса пула чтения, мс. */
|
|
36
|
+
const READ_STATEMENT_TIMEOUT_MS = 5000;
|
|
37
|
+
/**
|
|
38
|
+
* Параметры старта соединений пула чтения (эталон O): одиночный запрос — read-only транзакция, срок 5 с, `app.tenant`
|
|
39
|
+
* пуст с подключения.
|
|
40
|
+
*/
|
|
41
|
+
export const READ_POOL_OPTIONS = `-c default_transaction_read_only=on -c statement_timeout=${READ_STATEMENT_TIMEOUT_MS} -c app.tenant=`;
|
|
42
|
+
const logger = new Logger('KitDatabase');
|
|
43
|
+
/**
|
|
44
|
+
* Учёт срока пула (AC-4.5, KT-19 п. 3): обёртка `connect` экземпляра — через неё идут и транзакции `Db`, и
|
|
45
|
+
* `pool.query` (`DRIZZLE`, `readGlobal`). Срок помечается именем пула один раз: метрика, `captureThrottled`, `warn`.
|
|
46
|
+
*/
|
|
47
|
+
function instrumentPoolTimeouts(pool, name, metrics) {
|
|
48
|
+
const report = (error) => {
|
|
49
|
+
if (!isPoolTimeout(error) || !tagPoolTimeout(error, name)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
metrics?.dbPoolWaitTimeouts.inc({ pool: name });
|
|
53
|
+
captureThrottled(`db-pool-timeout:${name}`, error);
|
|
54
|
+
logger.warn({ pool: name }, 'пул БД: истёк срок ожидания клиента');
|
|
55
|
+
};
|
|
56
|
+
const original = pool.connect.bind(pool);
|
|
57
|
+
const connect = (callback) => {
|
|
58
|
+
if (typeof callback === 'function') {
|
|
59
|
+
return original((error, ...rest) => {
|
|
60
|
+
if (error) {
|
|
61
|
+
report(error);
|
|
62
|
+
}
|
|
63
|
+
callback(error, ...rest);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return original().catch((error) => {
|
|
67
|
+
report(error);
|
|
68
|
+
throw error;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
// boundary: pg-pool connect перегружен (промис и обратный вызов) — обёртка сохраняет оба вида.
|
|
72
|
+
pool.connect = connect;
|
|
73
|
+
}
|
|
74
|
+
/** Основной пул и пул чтения на `DATABASE_URL` с учётом срока; адрес не логируется. */
|
|
75
|
+
export async function createPools(env, options = {}) {
|
|
76
|
+
const pg = await loadPg();
|
|
77
|
+
const create = (name) => {
|
|
78
|
+
const pool = new pg.Pool({
|
|
79
|
+
connectionString: env.DATABASE_URL,
|
|
80
|
+
max: env.DB_POOL_MAX,
|
|
81
|
+
connectionTimeoutMillis: env.DB_POOL_ACQUIRE_TIMEOUT_MS,
|
|
82
|
+
...(name === 'read' ? { options: READ_POOL_OPTIONS } : {}),
|
|
83
|
+
});
|
|
84
|
+
const onError = (error) => logger.warn({ pool: name, err: sanitizeError(error) }, 'пул БД: ошибка простаивающего клиента');
|
|
85
|
+
pool.on('error', onError);
|
|
86
|
+
// pg-pool снимает свой обработчик 'error' с выданного клиента: без своего обрыв уронил бы процесс.
|
|
87
|
+
pool.on('connect', (client) => void client.on('error', onError));
|
|
88
|
+
instrumentPoolTimeouts(pool, name, options.metrics);
|
|
89
|
+
logger.debug({ pool: name, max: env.DB_POOL_MAX }, 'пул БД: создан');
|
|
90
|
+
return pool;
|
|
91
|
+
};
|
|
92
|
+
return { main: create('main'), read: create('read') };
|
|
93
|
+
}
|
|
94
|
+
function databaseEnv(config) {
|
|
95
|
+
if (config === undefined) {
|
|
96
|
+
throw new Error('KitDatabaseModule: модуль сервиса не импортирует KitConfigModule.forRoot({schema}) с envDatabase');
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
DATABASE_URL: config.getOrThrow('DATABASE_URL'),
|
|
100
|
+
DB_POOL_MAX: config.getOrThrow('DB_POOL_MAX'),
|
|
101
|
+
DB_POOL_ACQUIRE_TIMEOUT_MS: config.getOrThrow('DB_POOL_ACQUIRE_TIMEOUT_MS'),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Глобальный модуль БД сервиса (§3.3): `Db`, `DRIZZLE`, `DRIZZLE_READ`; пробы `database` и `database-read` (критичные,
|
|
106
|
+
* при `createHttpApp`), метрика срока пула; пулы закрываются на шаге 4 (`onApplicationShutdown`). Окружение —
|
|
107
|
+
* `envDatabase` из `KitConfigModule`. Соединений на реплику — `2 × DB_POOL_MAX`.
|
|
108
|
+
*/
|
|
109
|
+
let KitDatabaseModule = KitDatabaseModule_1 = class KitDatabaseModule {
|
|
110
|
+
pools;
|
|
111
|
+
health;
|
|
112
|
+
logger = new Logger(KitDatabaseModule_1.name);
|
|
113
|
+
constructor(pools, health) {
|
|
114
|
+
this.pools = pools;
|
|
115
|
+
this.health = health;
|
|
116
|
+
}
|
|
117
|
+
/** Модуль БД со схемой Drizzle сервиса. */
|
|
118
|
+
static forRoot(options) {
|
|
119
|
+
const withSchema = async (pool) => (await loadDrizzle()).drizzle(pool, { schema: options.schema });
|
|
120
|
+
return {
|
|
121
|
+
module: KitDatabaseModule_1,
|
|
122
|
+
global: true,
|
|
123
|
+
providers: [
|
|
124
|
+
{
|
|
125
|
+
provide: KIT_DATABASE_ENV,
|
|
126
|
+
useFactory: databaseEnv,
|
|
127
|
+
inject: [{ token: ConfigService, optional: true }],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
provide: KIT_PG_POOLS,
|
|
131
|
+
useFactory: (env, metrics) => createPools(env, { metrics }),
|
|
132
|
+
inject: [KIT_DATABASE_ENV, { token: KIT_METRICS, optional: true }],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
provide: DRIZZLE,
|
|
136
|
+
useFactory: (pools) => withSchema(pools.main),
|
|
137
|
+
inject: [KIT_PG_POOLS],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
provide: DRIZZLE_READ,
|
|
141
|
+
useFactory: (pools) => withSchema(pools.read),
|
|
142
|
+
inject: [KIT_PG_POOLS],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
provide: Db,
|
|
146
|
+
useFactory: async (pools) => new Db({ ...pools, drizzle: (await loadDrizzle()).drizzle }),
|
|
147
|
+
inject: [KIT_PG_POOLS],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
exports: [Db, DRIZZLE, DRIZZLE_READ],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Пробы `/health` обоих пулов — `select 1` (без `Db.read`: ему нужен тенант). */
|
|
154
|
+
onModuleInit() {
|
|
155
|
+
if (this.health === undefined) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const probe = (pool) => async () => {
|
|
159
|
+
await pool.query('select 1');
|
|
160
|
+
return true;
|
|
161
|
+
};
|
|
162
|
+
this.health.register('database', probe(this.pools.main), { critical: true });
|
|
163
|
+
this.health.register('database-read', probe(this.pools.read), { critical: true });
|
|
164
|
+
}
|
|
165
|
+
/** Шаг 4 остановки: закрытие пулов после HTTP и фоновых задач. */
|
|
166
|
+
async onApplicationShutdown() {
|
|
167
|
+
const { main, read } = this.pools;
|
|
168
|
+
this.logger.log({
|
|
169
|
+
main: { total: main.totalCount, idle: main.idleCount },
|
|
170
|
+
read: { total: read.totalCount, idle: read.idleCount },
|
|
171
|
+
}, 'пулы БД: закрытие');
|
|
172
|
+
await Promise.all([main.end(), read.end()]);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
KitDatabaseModule = KitDatabaseModule_1 = __decorate([
|
|
176
|
+
Module({}),
|
|
177
|
+
__param(0, Inject(KIT_PG_POOLS)),
|
|
178
|
+
__param(1, Optional()),
|
|
179
|
+
__metadata("design:paramtypes", [Object, HealthRegistry])
|
|
180
|
+
], KitDatabaseModule);
|
|
181
|
+
export { KitDatabaseModule };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Logger } from 'pino';
|
|
2
|
+
/** Таблица журнала миграций в схеме сервиса. */
|
|
3
|
+
export declare const MIGRATIONS_TABLE = "__migrations";
|
|
4
|
+
/** Неверное использование (нет журнала drizzle-kit в папке); CLI переводит в код `USAGE`. */
|
|
5
|
+
export declare class KitUsageError extends Error {
|
|
6
|
+
readonly name = "KitUsageError";
|
|
7
|
+
}
|
|
8
|
+
/** Параметры `runMigrations` (prd.md §3.3). */
|
|
9
|
+
export interface KitRunMigrationsOptions {
|
|
10
|
+
/** Адрес БД под ролью владельца `<svc>_owner`. */
|
|
11
|
+
readonly url: string;
|
|
12
|
+
/** Папка миграций drizzle-kit (с `meta/_journal.json`). */
|
|
13
|
+
readonly folder: string;
|
|
14
|
+
/** Схема сервиса — и схема журнала `<schema>.__migrations`. */
|
|
15
|
+
readonly schema: string;
|
|
16
|
+
}
|
|
17
|
+
/** Параметры `moveJournal`. */
|
|
18
|
+
export interface KitMoveJournalOptions {
|
|
19
|
+
/** Адрес БД под ролью владельца. */
|
|
20
|
+
readonly url: string;
|
|
21
|
+
/** Схема старого журнала `<from>.__drizzle_migrations` (обычно `drizzle`). */
|
|
22
|
+
readonly from: string;
|
|
23
|
+
/** Схема сервиса — журнал назначения `<to>.__migrations`. */
|
|
24
|
+
readonly to: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Применяет SQL-миграции drizzle-kit из `folder` под ролью владельца; журнал — `<schema>.__migrations`.
|
|
28
|
+
* Повтор — `{applied: 0}`.
|
|
29
|
+
* @throws {TypeError} схема вне формата имени сервиса.
|
|
30
|
+
* @throws {KitUsageError} в папке нет `meta/_journal.json`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runMigrations(options: KitRunMigrationsOptions, logger?: Logger): Promise<{
|
|
33
|
+
readonly applied: number;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Переносит журнал `<from>.__drizzle_migrations` в `<to>.__migrations` (строки, которых нет по `hash`), удаляет старый
|
|
37
|
+
* журнал и пустую схему `<from>` — в одной транзакции. Повтор — `{moved: 0}`.
|
|
38
|
+
* @throws {TypeError} схема вне формата или `from === to`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function moveJournal(options: KitMoveJournalOptions, logger?: Logger): Promise<{
|
|
41
|
+
readonly moved: number;
|
|
42
|
+
}>;
|