@verdeect/kit 0.0.0-stage → 1.0.0-rc.2
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 +138 -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,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { KitEvent } from './define-event.js';
|
|
3
|
+
/** Схема конверта события `01` §7.1 (не strict: новые поля конверта не ломают подписчиков). */
|
|
4
|
+
export declare const EnvelopeSchema: z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
type: z.ZodString;
|
|
7
|
+
version: z.ZodNumber;
|
|
8
|
+
occurredAt: z.ZodISODateTime;
|
|
9
|
+
tenant: z.ZodNullable<z.ZodString>;
|
|
10
|
+
actor: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
|
+
subject: z.ZodObject<{
|
|
12
|
+
kind: z.ZodString;
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
16
|
+
traceId: z.ZodNullable<z.ZodString>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
type TenantOf<E extends KitEvent> = [E['tenant']] extends ['none'] ? null : [E['tenant']] extends ['required'] ? string : string | null;
|
|
19
|
+
/** Конверт события `E`: `tenant` — `string` для `'required'`, `null` для `'none'`; `data` — выход схемы события. */
|
|
20
|
+
export type Envelope<E extends KitEvent = KitEvent> = Omit<z.output<typeof EnvelopeSchema>, 'type' | 'data' | 'tenant' | 'version'> & {
|
|
21
|
+
readonly type: E['type'];
|
|
22
|
+
readonly version: number;
|
|
23
|
+
readonly data: z.output<E['data']>;
|
|
24
|
+
readonly tenant: TenantOf<E>;
|
|
25
|
+
};
|
|
26
|
+
/** Поля строки outbox, из которых relay собирает конверт. */
|
|
27
|
+
export interface OutboxRowForEnvelope {
|
|
28
|
+
/** id события. */
|
|
29
|
+
readonly id: string;
|
|
30
|
+
/** Тип. */
|
|
31
|
+
readonly type: string;
|
|
32
|
+
/** Версия. */
|
|
33
|
+
readonly version: number;
|
|
34
|
+
/** Время события. */
|
|
35
|
+
readonly occurredAt: Date;
|
|
36
|
+
/** Тенант. */
|
|
37
|
+
readonly tenant: string | null;
|
|
38
|
+
/** Актор `DbContext`. */
|
|
39
|
+
readonly actor: Record<string, unknown> | null;
|
|
40
|
+
/** Вид субъекта — из `defineEvent.subject`. */
|
|
41
|
+
readonly subjectKind: string;
|
|
42
|
+
/** id субъекта. */
|
|
43
|
+
readonly subjectId: string;
|
|
44
|
+
/** Данные. */
|
|
45
|
+
readonly data: Record<string, unknown>;
|
|
46
|
+
/** traceId. */
|
|
47
|
+
readonly traceId: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** Конверт из строки outbox (relay). */
|
|
50
|
+
export declare function buildEnvelope(row: OutboxRowForEnvelope): Envelope;
|
|
51
|
+
/** Итог разбора тела сообщения: `fields` — верхнеуровневые имена полей с ошибкой, без значений. */
|
|
52
|
+
export type ParsedEnvelope = {
|
|
53
|
+
readonly ok: true;
|
|
54
|
+
readonly envelope: Envelope;
|
|
55
|
+
} | {
|
|
56
|
+
readonly ok: false;
|
|
57
|
+
readonly reason: 'not-json' | 'envelope';
|
|
58
|
+
readonly fields: readonly string[];
|
|
59
|
+
};
|
|
60
|
+
/** Разбор тела входящего сообщения по `EnvelopeSchema`. */
|
|
61
|
+
export declare function parseEnvelope(content: Buffer): ParsedEnvelope;
|
|
62
|
+
/** Тенант конверта нарушает признак события: `'required'` без тенанта или `'none'` с тенантом (AC-5.15). */
|
|
63
|
+
export declare function tenantViolation(event: KitEvent, envelope: {
|
|
64
|
+
readonly tenant: string | null;
|
|
65
|
+
}): boolean;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Конверт события 01 §7.1 (prd.md §3.4, AC-5.2, AC-5.7, AC-5.15). Эталон — O/P/I event-envelope.ts (схема одинаковая);
|
|
2
|
+
// по PRD все поля §7.1 обязательны, tenant/actor/traceId — nullable (П21), subject — {kind, id}.
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/** Схема конверта события `01` §7.1 (не strict: новые поля конверта не ломают подписчиков). */
|
|
5
|
+
export const EnvelopeSchema = z.object({
|
|
6
|
+
id: z.string().min(1),
|
|
7
|
+
type: z.string().min(1),
|
|
8
|
+
version: z.number().int().min(1),
|
|
9
|
+
occurredAt: z.iso.datetime({ offset: true }),
|
|
10
|
+
tenant: z.string().min(1).nullable(),
|
|
11
|
+
actor: z.record(z.string(), z.unknown()).nullable(),
|
|
12
|
+
subject: z.object({ kind: z.string().min(1), id: z.string().min(1) }),
|
|
13
|
+
data: z.record(z.string(), z.unknown()),
|
|
14
|
+
traceId: z.string().min(1).nullable(),
|
|
15
|
+
});
|
|
16
|
+
/** Конверт из строки outbox (relay). */
|
|
17
|
+
export function buildEnvelope(row) {
|
|
18
|
+
return {
|
|
19
|
+
id: row.id,
|
|
20
|
+
type: row.type,
|
|
21
|
+
version: row.version,
|
|
22
|
+
occurredAt: row.occurredAt.toISOString(),
|
|
23
|
+
tenant: row.tenant,
|
|
24
|
+
actor: row.actor,
|
|
25
|
+
subject: { kind: row.subjectKind, id: row.subjectId },
|
|
26
|
+
data: row.data,
|
|
27
|
+
traceId: row.traceId,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Разбор тела входящего сообщения по `EnvelopeSchema`. */
|
|
31
|
+
export function parseEnvelope(content) {
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = JSON.parse(content.toString('utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { ok: false, reason: 'not-json', fields: [] };
|
|
38
|
+
}
|
|
39
|
+
const result = EnvelopeSchema.safeParse(raw);
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
const fields = result.error.issues.map((issue) => issue.path.length === 0 ? '(root)' : String(issue.path[0]));
|
|
42
|
+
return { ok: false, reason: 'envelope', fields: [...new Set(fields)] };
|
|
43
|
+
}
|
|
44
|
+
return { ok: true, envelope: result.data };
|
|
45
|
+
}
|
|
46
|
+
/** Тенант конверта нарушает признак события: `'required'` без тенанта или `'none'` с тенантом (AC-5.15). */
|
|
47
|
+
export function tenantViolation(event, envelope) {
|
|
48
|
+
return event.tenant === 'required' ? envelope.tenant === null : envelope.tenant !== null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StartupFailure } from '../observability/fail-startup.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ожидаемый отказ обработки (например, зависимость временно недоступна): `warn` и повтор через `<svc>.retry` до 5 раз,
|
|
4
|
+
* затем DLQ с `x-dlq-reason` = `code`; в GlitchTip не отправляется. Сервис наследует класс со своим кодом.
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class ExpectedMessageError extends Error {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
/**
|
|
9
|
+
* @param code Код отказа SCREAMING_SNAKE — значение `x-dlq-reason` после исчерпания повторов.
|
|
10
|
+
* @param message Текст для лога, без ПДн.
|
|
11
|
+
* @throws {TypeError} `code` не SCREAMING_SNAKE.
|
|
12
|
+
*/
|
|
13
|
+
constructor(code: string, message: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Сообщение нельзя обработать никогда — сразу в `<svc>.dlq` без повторов, `x-dlq-reason` = `reason`. В `reason` —
|
|
17
|
+
* только пути полей и коды, без значений и ПДн; длиннее 200 символов обрезается.
|
|
18
|
+
*/
|
|
19
|
+
export declare class InvalidMessageError extends Error {
|
|
20
|
+
/** Код ошибки. */
|
|
21
|
+
readonly code = "INVALID_MESSAGE";
|
|
22
|
+
/** Причина отказа (≤ 200 символов) — значение `x-dlq-reason`. */
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
/** @param reason Причина: пути полей и коды, без значений. */
|
|
25
|
+
constructor(reason: string);
|
|
26
|
+
}
|
|
27
|
+
/** Коды отказа запуска /messaging. */
|
|
28
|
+
export type MessagingStartupCode = 'MESSAGING_TENANT_NOT_NULLABLE' | 'MESSAGING_RETRY_TTL_MISMATCH';
|
|
29
|
+
/**
|
|
30
|
+
* Отказ запуска `/messaging` с кодом: `MESSAGING_TENANT_NOT_NULLABLE` — событие `tenant: 'none'` при NOT NULL `tenant`
|
|
31
|
+
* outbox; `MESSAGING_RETRY_TTL_MISMATCH` — `<svc>.retry` объявлена с другим TTL. `failStartup` пишет `startupCode` и
|
|
32
|
+
* `startupDetails()` — только имена (тип события, таблица, очередь), без значений окружения.
|
|
33
|
+
*/
|
|
34
|
+
export declare class MessagingStartupError extends Error implements StartupFailure {
|
|
35
|
+
readonly startupCode: MessagingStartupCode;
|
|
36
|
+
private readonly details;
|
|
37
|
+
/**
|
|
38
|
+
* @param startupCode Код отказа.
|
|
39
|
+
* @param message Текст для лога.
|
|
40
|
+
* @param details Имена (тип события, таблица, очередь).
|
|
41
|
+
*/
|
|
42
|
+
constructor(startupCode: MessagingStartupCode, message: string, details: Readonly<Record<string, string>>);
|
|
43
|
+
/** Подробности отказа для лога: только имена. */
|
|
44
|
+
startupDetails(): Readonly<Record<string, string>>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const CODE = /^[A-Z][A-Z0-9_]*$/;
|
|
2
|
+
const MAX_REASON = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Ожидаемый отказ обработки (например, зависимость временно недоступна): `warn` и повтор через `<svc>.retry` до 5 раз,
|
|
5
|
+
* затем DLQ с `x-dlq-reason` = `code`; в GlitchTip не отправляется. Сервис наследует класс со своим кодом.
|
|
6
|
+
*/
|
|
7
|
+
export class ExpectedMessageError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
/**
|
|
10
|
+
* @param code Код отказа SCREAMING_SNAKE — значение `x-dlq-reason` после исчерпания повторов.
|
|
11
|
+
* @param message Текст для лога, без ПДн.
|
|
12
|
+
* @throws {TypeError} `code` не SCREAMING_SNAKE.
|
|
13
|
+
*/
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
if (!CODE.test(code)) {
|
|
18
|
+
throw new TypeError(`ExpectedMessageError: код «${code}» не SCREAMING_SNAKE`);
|
|
19
|
+
}
|
|
20
|
+
this.name = new.target.name;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Сообщение нельзя обработать никогда — сразу в `<svc>.dlq` без повторов, `x-dlq-reason` = `reason`. В `reason` —
|
|
25
|
+
* только пути полей и коды, без значений и ПДн; длиннее 200 символов обрезается.
|
|
26
|
+
*/
|
|
27
|
+
export class InvalidMessageError extends Error {
|
|
28
|
+
/** Код ошибки. */
|
|
29
|
+
code = 'INVALID_MESSAGE';
|
|
30
|
+
/** Причина отказа (≤ 200 символов) — значение `x-dlq-reason`. */
|
|
31
|
+
reason;
|
|
32
|
+
/** @param reason Причина: пути полей и коды, без значений. */
|
|
33
|
+
constructor(reason) {
|
|
34
|
+
const trimmed = reason.slice(0, MAX_REASON);
|
|
35
|
+
super(`сообщение отклонено: ${trimmed}`);
|
|
36
|
+
this.reason = trimmed;
|
|
37
|
+
this.name = 'InvalidMessageError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Отказ запуска `/messaging` с кодом: `MESSAGING_TENANT_NOT_NULLABLE` — событие `tenant: 'none'` при NOT NULL `tenant`
|
|
42
|
+
* outbox; `MESSAGING_RETRY_TTL_MISMATCH` — `<svc>.retry` объявлена с другим TTL. `failStartup` пишет `startupCode` и
|
|
43
|
+
* `startupDetails()` — только имена (тип события, таблица, очередь), без значений окружения.
|
|
44
|
+
*/
|
|
45
|
+
export class MessagingStartupError extends Error {
|
|
46
|
+
startupCode;
|
|
47
|
+
details;
|
|
48
|
+
/**
|
|
49
|
+
* @param startupCode Код отказа.
|
|
50
|
+
* @param message Текст для лога.
|
|
51
|
+
* @param details Имена (тип события, таблица, очередь).
|
|
52
|
+
*/
|
|
53
|
+
constructor(startupCode, message, details) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.startupCode = startupCode;
|
|
56
|
+
this.details = details;
|
|
57
|
+
this.name = 'MessagingStartupError';
|
|
58
|
+
}
|
|
59
|
+
/** Подробности отказа для лога: только имена. */
|
|
60
|
+
startupDetails() {
|
|
61
|
+
return this.details;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DbTx } from '../database/db.js';
|
|
2
|
+
import type { KitInboxTable } from './tables.js';
|
|
3
|
+
/** Занимает `id` события в транзакции обработчика; `false` — уже обработано (`duplicate`). */
|
|
4
|
+
export declare function claimInbox(tx: DbTx, table: KitInboxTable, id: string, type: string): Promise<boolean>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Занимает `id` события в транзакции обработчика; `false` — уже обработано (`duplicate`). */
|
|
2
|
+
export async function claimInbox(tx, table, id, type) {
|
|
3
|
+
const rows = await tx
|
|
4
|
+
.insert(table)
|
|
5
|
+
.values({ id, type })
|
|
6
|
+
.onConflictDoNothing()
|
|
7
|
+
.returning({ id: table.id });
|
|
8
|
+
return rows.length > 0;
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import './peer.js';
|
|
2
|
+
export { defineEvent } from './define-event.js';
|
|
3
|
+
export type { KitEvent, KitEventTenant } from './define-event.js';
|
|
4
|
+
export { EnvelopeSchema } from './envelope.js';
|
|
5
|
+
export type { Envelope } from './envelope.js';
|
|
6
|
+
export { ExpectedMessageError, InvalidMessageError, MessagingStartupError } from './errors.js';
|
|
7
|
+
export { KitMessagingModule } from './kit-messaging.module.js';
|
|
8
|
+
export { OutboxStore } from './outbox-store.js';
|
|
9
|
+
export { KitSubscriber } from './subscriber.js';
|
|
10
|
+
export type { KitEventHandler, KitReplicaHandler } from './subscriber.js';
|
|
11
|
+
export type { KitMessagingModuleOptions } from './kit-messaging.module.js';
|
|
12
|
+
export { createInboxTable, createOutboxTable } from './tables.js';
|
|
13
|
+
export type { KitInboxTable, KitOutboxTable, KitOutboxTableOptions } from './tables.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Точка входа @verdeect/kit/messaging (prd.md §3.4, E5): явный список экспортов. Первый импорт — ./peer.js: без amqplib
|
|
2
|
+
// или amqp-connection-manager ошибка AC-1.2 называет /messaging (requirePeer синхронно, без top-level await — П15).
|
|
3
|
+
import './peer.js';
|
|
4
|
+
export { defineEvent } from './define-event.js';
|
|
5
|
+
export { EnvelopeSchema } from './envelope.js';
|
|
6
|
+
export { ExpectedMessageError, InvalidMessageError, MessagingStartupError } from './errors.js';
|
|
7
|
+
export { KitMessagingModule } from './kit-messaging.module.js';
|
|
8
|
+
export { OutboxStore } from './outbox-store.js';
|
|
9
|
+
export { KitSubscriber } from './subscriber.js';
|
|
10
|
+
export { createInboxTable, createOutboxTable } from './tables.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { KitEvent } from './define-event.js';
|
|
3
|
+
import { type KitInboxTable, type KitOutboxTable } from './tables.js';
|
|
4
|
+
/** Опции `KitMessagingModule.forRoot`. */
|
|
5
|
+
export interface KitMessagingModuleOptions {
|
|
6
|
+
/** Имя экземпляра (`^[a-z][a-z0-9-]{1,31}$`): очереди `<svc>`, `<svc>.retry`, `<svc>.dlq`. */
|
|
7
|
+
readonly service: string;
|
|
8
|
+
/**
|
|
9
|
+
* Outbox: таблица `createOutboxTable(schema, {tenantNullable})` и все события, которые сервис публикует
|
|
10
|
+
* (`OutboxStore.add` события вне списка — `TypeError`). `tenantNullable` читается из таблицы.
|
|
11
|
+
*/
|
|
12
|
+
readonly outbox?: {
|
|
13
|
+
readonly table: KitOutboxTable;
|
|
14
|
+
readonly events: readonly KitEvent[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Подписчик; не задан — подписчика нет. `dedupe` по умолчанию `'inbox'` и требует `inbox: createInboxTable(schema)`;
|
|
18
|
+
* `'none'` — без БД и inbox (роль BFF).
|
|
19
|
+
*/
|
|
20
|
+
readonly subscriber?: {
|
|
21
|
+
/** Защита от повторов подписчика. */
|
|
22
|
+
readonly dedupe?: 'inbox' | 'none';
|
|
23
|
+
/** Таблица inbox. */
|
|
24
|
+
readonly inbox?: KitInboxTable;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Сообщения сервиса (§3.4): соединение RabbitMQ, проба `rabbitmq`, outbox и relay, подписчик с inbox, очистка.
|
|
29
|
+
* Окружение — `envBase` и `envMessaging` из `KitConfigModule`; outbox и inbox требуют `KitDatabaseModule` и `pg`.
|
|
30
|
+
*/
|
|
31
|
+
export declare class KitMessagingModule {
|
|
32
|
+
/**
|
|
33
|
+
* Модуль сообщений; отказ — до прослушивания порта (`bootstrapService` → `failStartup`).
|
|
34
|
+
* @throws {TypeError} имя сервиса вне формата, несогласованные опции или таблицы, дубликат события.
|
|
35
|
+
* @throws {MessagingStartupError} событие `tenant: 'none'` при NOT NULL `tenant` (AC-5.15).
|
|
36
|
+
* @throws {Error} outbox или `dedupe: 'inbox'` без `pg` (AC-1.2).
|
|
37
|
+
*/
|
|
38
|
+
static forRoot(options: KitMessagingModuleOptions): Promise<DynamicModule>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
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 KitMessagingModule_1;
|
|
14
|
+
// KitMessagingModule.forRoot (prd.md §3.4, AC-1.2, AC-5.15, AC-5.20; П1, П5, П11). Эталон — O messaging.module.ts
|
|
15
|
+
// (проба rabbitmq некритичная, соединение — шаг 4). pg — requirePeer только при outbox или dedupe: 'inbox', значения
|
|
16
|
+
// database — динамическим import() здесь же (/messaging грузится без pg). Остановка — хуки Nest: соединение —
|
|
17
|
+
// onApplicationShutdown (шаг 4), relay, подписчик, очистка — beforeApplicationShutdown (шаг 3); KitLifecycle не нужен.
|
|
18
|
+
import { Inject, Injectable, Module, Optional, } from '@nestjs/common';
|
|
19
|
+
import { ConfigService } from '@nestjs/config';
|
|
20
|
+
import { getTableConfig } from 'drizzle-orm/pg-core';
|
|
21
|
+
import { requirePeer } from '../internal/peer.js';
|
|
22
|
+
import { assertServiceName } from '../internal/service-name.js';
|
|
23
|
+
import { KIT_LOGGER, KIT_METRICS } from '../internal/tokens.js';
|
|
24
|
+
import { HealthRegistry } from '../observability/health-registry.js';
|
|
25
|
+
import { MessagingCleanup } from './cleanup.js';
|
|
26
|
+
import { MessagingStartupError } from './errors.js';
|
|
27
|
+
import { OutboxRelay } from './outbox-relay.js';
|
|
28
|
+
import { OutboxStore } from './outbox-store.js';
|
|
29
|
+
import { KitSubscriber } from './subscriber.js';
|
|
30
|
+
import { RabbitConnection, nestLog } from './rabbit-connection.js';
|
|
31
|
+
import { outboxTenantNullable } from './tables.js';
|
|
32
|
+
import { handlersDeadlineMs } from './topology.js';
|
|
33
|
+
import { KIT_MESSAGING_ENV, KIT_MESSAGING_LOG, KIT_MESSAGING_OPTIONS, KIT_RABBIT_CONNECTION, } from './config.js';
|
|
34
|
+
function messagingEnv(config) {
|
|
35
|
+
if (config === undefined) {
|
|
36
|
+
throw new Error('KitMessagingModule: модуль сервиса не импортирует KitConfigModule.forRoot({schema}) с envBase и envMessaging');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
RABBITMQ_URL: config.getOrThrow('RABBITMQ_URL'),
|
|
40
|
+
RABBITMQ_RETRY_SECONDS: config.getOrThrow('RABBITMQ_RETRY_SECONDS'),
|
|
41
|
+
REPLICA_ID: config.getOrThrow('REPLICA_ID'),
|
|
42
|
+
SHUTDOWN_TIMEOUT_MS: config.getOrThrow('SHUTDOWN_TIMEOUT_MS'),
|
|
43
|
+
SHUTDOWN_DRAIN_MS: config.getOrThrow('SHUTDOWN_DRAIN_MS'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function schemaOf(tables) {
|
|
47
|
+
const schemas = new Set();
|
|
48
|
+
for (const table of tables) {
|
|
49
|
+
if (table !== undefined) {
|
|
50
|
+
schemas.add(getTableConfig(table).schema);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (schemas.size === 0) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const [schema] = [...schemas];
|
|
57
|
+
if (schemas.size > 1 || schema === undefined) {
|
|
58
|
+
throw new TypeError('KitMessagingModule: таблицы outbox и inbox — одной схемы сервиса (createOutboxTable(pgSchema(…)))');
|
|
59
|
+
}
|
|
60
|
+
assertServiceName(schema, 'KitMessagingModule');
|
|
61
|
+
return schema;
|
|
62
|
+
}
|
|
63
|
+
function eventsOf(options, schema) {
|
|
64
|
+
const events = new Map();
|
|
65
|
+
const outbox = options.outbox;
|
|
66
|
+
if (outbox === undefined) {
|
|
67
|
+
return events;
|
|
68
|
+
}
|
|
69
|
+
const nullable = outboxTenantNullable(outbox.table);
|
|
70
|
+
for (const event of outbox.events) {
|
|
71
|
+
if (events.has(event.type)) {
|
|
72
|
+
throw new TypeError(`KitMessagingModule: событие ${event.type} указано дважды`);
|
|
73
|
+
}
|
|
74
|
+
if (event.tenant === 'none' && !nullable) {
|
|
75
|
+
throw new MessagingStartupError('MESSAGING_TENANT_NOT_NULLABLE', `KitMessagingModule: событие ${event.type} без компании (tenant: 'none') требует createOutboxTable(схема, {tenantNullable: true})`, { event: event.type, table: `${schema ?? ''}.outbox` });
|
|
76
|
+
}
|
|
77
|
+
events.set(event.type, event);
|
|
78
|
+
}
|
|
79
|
+
return events;
|
|
80
|
+
}
|
|
81
|
+
/** Старт соединения, проба `/health` и шаг 4 остановки (внутренний). */
|
|
82
|
+
let MessagingLifecycle = class MessagingLifecycle {
|
|
83
|
+
connection;
|
|
84
|
+
config;
|
|
85
|
+
log;
|
|
86
|
+
health;
|
|
87
|
+
constructor(connection, config, log, health) {
|
|
88
|
+
this.connection = connection;
|
|
89
|
+
this.config = config;
|
|
90
|
+
this.log = log;
|
|
91
|
+
this.health = health;
|
|
92
|
+
}
|
|
93
|
+
/** Соединение в фоне; проба `rabbitmq` некритичная (П11). */
|
|
94
|
+
async onModuleInit() {
|
|
95
|
+
await this.connection.start();
|
|
96
|
+
this.health?.register('rabbitmq', () => Promise.resolve(this.connection.isConnected() && this.connection.topologyReady()), { critical: false });
|
|
97
|
+
this.log.debug({
|
|
98
|
+
service: this.config.service,
|
|
99
|
+
outbox: this.config.outbox !== undefined,
|
|
100
|
+
subscriber: this.config.subscriber ? this.config.dedupe : 'off',
|
|
101
|
+
}, 'messaging: модуль собран');
|
|
102
|
+
}
|
|
103
|
+
/** Шаг 4 остановки: соединение после relay и подписчика. */
|
|
104
|
+
async onApplicationShutdown() {
|
|
105
|
+
await this.connection.onApplicationShutdown();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
MessagingLifecycle = __decorate([
|
|
109
|
+
Injectable(),
|
|
110
|
+
__param(0, Inject(KIT_RABBIT_CONNECTION)),
|
|
111
|
+
__param(1, Inject(KIT_MESSAGING_OPTIONS)),
|
|
112
|
+
__param(2, Inject(KIT_MESSAGING_LOG)),
|
|
113
|
+
__param(3, Optional()),
|
|
114
|
+
__metadata("design:paramtypes", [RabbitConnection, Object, Object, HealthRegistry])
|
|
115
|
+
], MessagingLifecycle);
|
|
116
|
+
/** Relay и `OutboxStore` при `outbox`: один объект — фабрика relay строит store, store будит relay. */
|
|
117
|
+
function outboxProviders(config) {
|
|
118
|
+
const { outbox, database } = config;
|
|
119
|
+
if (outbox === undefined || database === undefined) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
provide: OutboxRelay,
|
|
125
|
+
useFactory: (db, connection, log, metrics) => {
|
|
126
|
+
const store = new OutboxStore({
|
|
127
|
+
db,
|
|
128
|
+
table: outbox,
|
|
129
|
+
events: config.events,
|
|
130
|
+
wake: () => {
|
|
131
|
+
relay.wake();
|
|
132
|
+
},
|
|
133
|
+
log,
|
|
134
|
+
});
|
|
135
|
+
const relay = new OutboxRelay({
|
|
136
|
+
db,
|
|
137
|
+
store,
|
|
138
|
+
connection,
|
|
139
|
+
events: config.events,
|
|
140
|
+
metrics,
|
|
141
|
+
log,
|
|
142
|
+
});
|
|
143
|
+
relayStores.set(relay, store);
|
|
144
|
+
return relay;
|
|
145
|
+
},
|
|
146
|
+
inject: [
|
|
147
|
+
database.Db,
|
|
148
|
+
KIT_RABBIT_CONNECTION,
|
|
149
|
+
KIT_MESSAGING_LOG,
|
|
150
|
+
{ token: KIT_METRICS, optional: true },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
provide: OutboxStore,
|
|
155
|
+
useFactory: (relay) => {
|
|
156
|
+
const store = relayStores.get(relay);
|
|
157
|
+
if (store === undefined) {
|
|
158
|
+
throw new Error('KitMessagingModule: OutboxStore без relay');
|
|
159
|
+
}
|
|
160
|
+
return store;
|
|
161
|
+
},
|
|
162
|
+
inject: [OutboxRelay],
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
/** Подписчик при `subscriber`; `Db` — только при `dedupe: 'inbox'`. */
|
|
167
|
+
function subscriberProviders(config) {
|
|
168
|
+
if (!config.subscriber) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const withDb = config.dedupe === 'inbox' && config.database !== undefined;
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
provide: KitSubscriber,
|
|
175
|
+
useFactory: (env, connection, log, metrics, db) => new KitSubscriber({
|
|
176
|
+
service: config.service,
|
|
177
|
+
replica: env.REPLICA_ID,
|
|
178
|
+
dedupe: config.dedupe,
|
|
179
|
+
...(config.inbox === undefined ? {} : { inbox: config.inbox }),
|
|
180
|
+
...(db === undefined ? {} : { db }),
|
|
181
|
+
connection,
|
|
182
|
+
retrySeconds: env.RABBITMQ_RETRY_SECONDS,
|
|
183
|
+
metrics,
|
|
184
|
+
log,
|
|
185
|
+
}),
|
|
186
|
+
inject: [
|
|
187
|
+
KIT_MESSAGING_ENV,
|
|
188
|
+
KIT_RABBIT_CONNECTION,
|
|
189
|
+
KIT_MESSAGING_LOG,
|
|
190
|
+
{ token: KIT_METRICS, optional: true },
|
|
191
|
+
...(withDb && config.database !== undefined ? [config.database.Db] : []),
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
/** Очистка при outbox или inbox (BFF без БД — без очистки). */
|
|
197
|
+
function cleanupProviders(config) {
|
|
198
|
+
const { database, schema } = config;
|
|
199
|
+
if (database === undefined || schema === undefined) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
if (config.outbox === undefined && config.inbox === undefined) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
return [
|
|
206
|
+
{
|
|
207
|
+
provide: MessagingCleanup,
|
|
208
|
+
useFactory: (db, log, metrics) => new MessagingCleanup({
|
|
209
|
+
db,
|
|
210
|
+
database: {
|
|
211
|
+
runExclusive: database.runExclusive,
|
|
212
|
+
deleteInBatches: database.deleteInBatches,
|
|
213
|
+
},
|
|
214
|
+
schema,
|
|
215
|
+
tables: {
|
|
216
|
+
...(config.outbox === undefined ? {} : { outbox: config.outbox }),
|
|
217
|
+
...(config.inbox === undefined ? {} : { inbox: config.inbox }),
|
|
218
|
+
},
|
|
219
|
+
metrics,
|
|
220
|
+
log,
|
|
221
|
+
}),
|
|
222
|
+
inject: [database.Db, KIT_MESSAGING_LOG, { token: KIT_METRICS, optional: true }],
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
/** Store, построенный фабрикой relay. */
|
|
227
|
+
const relayStores = new WeakMap();
|
|
228
|
+
/**
|
|
229
|
+
* Сообщения сервиса (§3.4): соединение RabbitMQ, проба `rabbitmq`, outbox и relay, подписчик с inbox, очистка.
|
|
230
|
+
* Окружение — `envBase` и `envMessaging` из `KitConfigModule`; outbox и inbox требуют `KitDatabaseModule` и `pg`.
|
|
231
|
+
*/
|
|
232
|
+
let KitMessagingModule = KitMessagingModule_1 = class KitMessagingModule {
|
|
233
|
+
/**
|
|
234
|
+
* Модуль сообщений; отказ — до прослушивания порта (`bootstrapService` → `failStartup`).
|
|
235
|
+
* @throws {TypeError} имя сервиса вне формата, несогласованные опции или таблицы, дубликат события.
|
|
236
|
+
* @throws {MessagingStartupError} событие `tenant: 'none'` при NOT NULL `tenant` (AC-5.15).
|
|
237
|
+
* @throws {Error} outbox или `dedupe: 'inbox'` без `pg` (AC-1.2).
|
|
238
|
+
*/
|
|
239
|
+
static async forRoot(options) {
|
|
240
|
+
assertServiceName(options.service, 'KitMessagingModule');
|
|
241
|
+
const dedupe = options.subscriber?.dedupe ?? 'inbox';
|
|
242
|
+
const subscriber = options.subscriber !== undefined;
|
|
243
|
+
const needsDb = options.outbox !== undefined || (subscriber && dedupe === 'inbox');
|
|
244
|
+
if (subscriber && dedupe === 'inbox' && options.subscriber?.inbox === undefined) {
|
|
245
|
+
throw new TypeError("KitMessagingModule: subscriber с dedupe: 'inbox' требует inbox: createInboxTable(schema)");
|
|
246
|
+
}
|
|
247
|
+
if (dedupe === 'none' && options.subscriber?.inbox !== undefined) {
|
|
248
|
+
throw new TypeError("KitMessagingModule: inbox не используется при dedupe: 'none'");
|
|
249
|
+
}
|
|
250
|
+
const schema = schemaOf([options.outbox?.table, options.subscriber?.inbox]);
|
|
251
|
+
const events = eventsOf(options, schema);
|
|
252
|
+
let database;
|
|
253
|
+
if (needsDb) {
|
|
254
|
+
requirePeer('pg', 'messaging');
|
|
255
|
+
database = await import('../database/index.js');
|
|
256
|
+
}
|
|
257
|
+
const config = {
|
|
258
|
+
service: options.service,
|
|
259
|
+
schema,
|
|
260
|
+
dedupe,
|
|
261
|
+
subscriber,
|
|
262
|
+
events,
|
|
263
|
+
outbox: options.outbox?.table,
|
|
264
|
+
inbox: options.subscriber?.inbox,
|
|
265
|
+
database,
|
|
266
|
+
};
|
|
267
|
+
const outbox = outboxProviders(config);
|
|
268
|
+
const subscriberList = subscriberProviders(config);
|
|
269
|
+
return {
|
|
270
|
+
module: KitMessagingModule_1,
|
|
271
|
+
global: true,
|
|
272
|
+
providers: [
|
|
273
|
+
{ provide: KIT_MESSAGING_OPTIONS, useValue: config },
|
|
274
|
+
{
|
|
275
|
+
provide: KIT_MESSAGING_ENV,
|
|
276
|
+
useFactory: messagingEnv,
|
|
277
|
+
inject: [{ token: ConfigService, optional: true }],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
provide: KIT_MESSAGING_LOG,
|
|
281
|
+
useFactory: (logger) => logger ?? nestLog('KitMessaging'),
|
|
282
|
+
inject: [{ token: KIT_LOGGER, optional: true }],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
provide: KIT_RABBIT_CONNECTION,
|
|
286
|
+
useFactory: (env, log) => new RabbitConnection({
|
|
287
|
+
url: env.RABBITMQ_URL,
|
|
288
|
+
retrySeconds: env.RABBITMQ_RETRY_SECONDS,
|
|
289
|
+
deadlineMs: handlersDeadlineMs(env),
|
|
290
|
+
log,
|
|
291
|
+
}),
|
|
292
|
+
inject: [KIT_MESSAGING_ENV, KIT_MESSAGING_LOG],
|
|
293
|
+
},
|
|
294
|
+
MessagingLifecycle,
|
|
295
|
+
...outbox,
|
|
296
|
+
...subscriberList,
|
|
297
|
+
...cleanupProviders(config),
|
|
298
|
+
],
|
|
299
|
+
exports: [
|
|
300
|
+
...(outbox.length > 0 ? [OutboxStore] : []),
|
|
301
|
+
...(subscriberList.length > 0 ? [KitSubscriber] : []),
|
|
302
|
+
],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
KitMessagingModule = KitMessagingModule_1 = __decorate([
|
|
307
|
+
Module({})
|
|
308
|
+
], KitMessagingModule);
|
|
309
|
+
export { KitMessagingModule };
|