@quanticjs/core 1.1.1 → 2.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/dist/QuanticCoreModule.d.ts +4 -0
- package/dist/QuanticCoreModule.js +53 -0
- package/dist/cqrs/behaviors/LogBehavior.d.ts +4 -1
- package/dist/cqrs/behaviors/LogBehavior.js +3 -0
- package/dist/cqrs/behaviors/TransactionalBehavior.d.ts +4 -11
- package/dist/cqrs/behaviors/TransactionalBehavior.js +3 -12
- package/dist/cqrs/behaviors/ValidationBehavior.d.ts +4 -1
- package/dist/cqrs/behaviors/ValidationBehavior.js +3 -0
- package/dist/cqrs/pipeline/QuanticCommandBus.d.ts +3 -28
- package/dist/cqrs/pipeline/QuanticCommandBus.js +11 -59
- package/dist/cqrs/pipeline/QuanticQueryBus.d.ts +3 -19
- package/dist/cqrs/pipeline/QuanticQueryBus.js +11 -38
- package/dist/cqrs/pipeline/behavior-order.d.ts +11 -0
- package/dist/cqrs/pipeline/behavior-order.js +14 -0
- package/dist/cqrs/pipeline/types.d.ts +8 -0
- package/dist/cqrs/pipeline/types.js +4 -0
- package/dist/index.d.ts +6 -22
- package/dist/index.js +13 -44
- package/package.json +26 -80
- package/dist/cqrs/PipelineExecutor.d.ts +0 -31
- package/dist/cqrs/PipelineExecutor.js +0 -81
- package/dist/cqrs/behaviors/CacheBehavior.d.ts +0 -9
- package/dist/cqrs/behaviors/CacheBehavior.js +0 -68
- package/dist/cqrs/behaviors/DistributedLockBehavior.d.ts +0 -12
- package/dist/cqrs/behaviors/DistributedLockBehavior.js +0 -90
- package/dist/cqrs/behaviors/FeatureFlagBehavior.d.ts +0 -8
- package/dist/cqrs/behaviors/FeatureFlagBehavior.js +0 -58
- package/dist/cqrs/behaviors/InvalidateCacheBehavior.d.ts +0 -9
- package/dist/cqrs/behaviors/InvalidateCacheBehavior.js +0 -61
- package/dist/cqrs/behaviors/PerformanceBehavior.d.ts +0 -12
- package/dist/cqrs/behaviors/PerformanceBehavior.js +0 -56
- package/dist/cqrs/behaviors/WorkflowBehavior.d.ts +0 -8
- package/dist/cqrs/behaviors/WorkflowBehavior.js +0 -61
- package/dist/events/DomainEvent.d.ts +0 -14
- package/dist/events/DomainEvent.js +0 -27
- package/dist/events/OutboxEvent.entity.d.ts +0 -18
- package/dist/events/OutboxEvent.entity.js +0 -87
- package/dist/events/OutboxPublisherService.d.ts +0 -14
- package/dist/events/OutboxPublisherService.js +0 -104
- package/dist/events/RedisStreamConsumer.d.ts +0 -43
- package/dist/events/RedisStreamConsumer.js +0 -158
- package/dist/events/RedisStreamPublisher.d.ts +0 -9
- package/dist/events/RedisStreamPublisher.js +0 -60
- package/dist/metrics/MetricsController.d.ts +0 -7
- package/dist/metrics/MetricsController.js +0 -42
- package/dist/metrics/MetricsService.d.ts +0 -13
- package/dist/metrics/MetricsService.js +0 -58
- package/dist/redis/redis.module.d.ts +0 -8
- package/dist/redis/redis.module.js +0 -49
- package/dist/shared-kernel.module.d.ts +0 -13
- package/dist/shared-kernel.module.js +0 -87
- package/dist/testing/TestingModuleFactory.d.ts +0 -23
- package/dist/testing/TestingModuleFactory.js +0 -63
- package/dist/testing/index.d.ts +0 -2
- package/dist/testing/index.js +0 -7
- package/dist/unleash/initial-flags.d.ts +0 -7
- package/dist/unleash/initial-flags.js +0 -9
- package/dist/unleash/unleash.module.d.ts +0 -9
- package/dist/unleash/unleash.module.js +0 -47
- package/src/bootstrap/bootstrapService.ts +0 -72
- package/src/cqrs/behaviors/CacheBehavior.spec.ts +0 -63
- package/src/cqrs/behaviors/CacheBehavior.ts +0 -54
- package/src/cqrs/behaviors/DistributedLockBehavior.ts +0 -88
- package/src/cqrs/behaviors/FeatureFlagBehavior.ts +0 -46
- package/src/cqrs/behaviors/InvalidateCacheBehavior.spec.ts +0 -89
- package/src/cqrs/behaviors/InvalidateCacheBehavior.ts +0 -50
- package/src/cqrs/behaviors/LogBehavior.spec.ts +0 -55
- package/src/cqrs/behaviors/LogBehavior.ts +0 -121
- package/src/cqrs/behaviors/PerformanceBehavior.spec.ts +0 -48
- package/src/cqrs/behaviors/PerformanceBehavior.ts +0 -43
- package/src/cqrs/behaviors/TransactionalBehavior.ts +0 -64
- package/src/cqrs/behaviors/ValidationBehavior.spec.ts +0 -114
- package/src/cqrs/behaviors/ValidationBehavior.ts +0 -29
- package/src/cqrs/behaviors/WorkflowBehavior.spec.ts +0 -97
- package/src/cqrs/behaviors/WorkflowBehavior.ts +0 -62
- package/src/cqrs/constants.ts +0 -2
- package/src/cqrs/decorators/Cache.decorator.ts +0 -24
- package/src/cqrs/decorators/DistributedLock.decorator.ts +0 -34
- package/src/cqrs/decorators/FeatureFlag.decorator.ts +0 -23
- package/src/cqrs/decorators/InvalidateCache.decorator.spec.ts +0 -20
- package/src/cqrs/decorators/InvalidateCache.decorator.ts +0 -17
- package/src/cqrs/decorators/IsolatedTransaction.decorator.ts +0 -24
- package/src/cqrs/decorators/Log.decorator.ts +0 -22
- package/src/cqrs/decorators/Validate.decorator.ts +0 -39
- package/src/cqrs/decorators/Workflow.decorator.ts +0 -22
- package/src/cqrs/interfaces/WorkflowEngine.ts +0 -19
- package/src/cqrs/pipeline/QuanticCommandBus.ts +0 -69
- package/src/cqrs/pipeline/QuanticQueryBus.ts +0 -56
- package/src/cqrs/pipeline/runPipeline.ts +0 -22
- package/src/cqrs/transaction/TransactionContext.ts +0 -26
- package/src/cqrs/transaction/getTransactionalRepo.ts +0 -23
- package/src/cqrs/validation/ICommandValidator.ts +0 -55
- package/src/entities/BaseEntity.ts +0 -16
- package/src/entities/TenantBaseEntity.ts +0 -7
- package/src/events/DomainEvent.ts +0 -27
- package/src/events/OutboxEvent.entity.ts +0 -56
- package/src/events/OutboxPublisherService.ts +0 -94
- package/src/events/RedisStreamConsumer.ts +0 -172
- package/src/events/RedisStreamPublisher.ts +0 -54
- package/src/filters/GlobalExceptionFilter.ts +0 -125
- package/src/guards/JwtAuthGuard.ts +0 -29
- package/src/guards/JwtStrategy.ts +0 -41
- package/src/guards/RolesGuard.ts +0 -39
- package/src/index.ts +0 -118
- package/src/interceptors/ResultInterceptor.ts +0 -93
- package/src/lifecycle/GracefulShutdownService.ts +0 -77
- package/src/logging/pino-config.ts +0 -80
- package/src/metrics/MetricsController.ts +0 -17
- package/src/metrics/MetricsService.ts +0 -55
- package/src/middleware/CorrelationIdMiddleware.ts +0 -27
- package/src/middleware/CorrelationStore.ts +0 -13
- package/src/middleware/TenantContextMiddleware.ts +0 -21
- package/src/middleware/TenantStore.ts +0 -11
- package/src/redis/redis.module.ts +0 -41
- package/src/resilience/CircuitBreakerFactory.ts +0 -33
- package/src/result/Result.ts +0 -66
- package/src/shared-kernel.module.ts +0 -87
- package/src/subscribers/TenantSubscriber.ts +0 -47
- package/src/testing/TestingModuleFactory.ts +0 -78
- package/src/testing/index.ts +0 -2
- package/src/testing/mocks.ts +0 -59
- package/src/unleash/unleash.module.ts +0 -45
- package/tsconfig.json +0 -22
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.PerformanceBehavior = void 0;
|
|
16
|
-
const common_1 = require("@nestjs/common");
|
|
17
|
-
const MetricsService_1 = require("../../metrics/MetricsService");
|
|
18
|
-
const SLOW_THRESHOLD_MS = 500;
|
|
19
|
-
/**
|
|
20
|
-
* PerformanceBehavior logs a warning when a handler exceeds 500ms
|
|
21
|
-
* and records handler duration in Prometheus histogram.
|
|
22
|
-
*/
|
|
23
|
-
let PerformanceBehavior = class PerformanceBehavior {
|
|
24
|
-
metrics;
|
|
25
|
-
logger = new common_1.Logger('PerformanceBehavior');
|
|
26
|
-
constructor(metrics) {
|
|
27
|
-
this.metrics = metrics;
|
|
28
|
-
}
|
|
29
|
-
async execute(command, next) {
|
|
30
|
-
const handlerName = command.constructor.name;
|
|
31
|
-
const startTime = Date.now();
|
|
32
|
-
const result = await next();
|
|
33
|
-
const durationMs = Date.now() - startTime;
|
|
34
|
-
const durationSec = durationMs / 1000;
|
|
35
|
-
const resultLabel = result.isSuccess ? 'success' : 'failure';
|
|
36
|
-
// Record metrics
|
|
37
|
-
this.metrics?.handlerDuration
|
|
38
|
-
.labels(handlerName, resultLabel)
|
|
39
|
-
.observe(durationSec);
|
|
40
|
-
// Warn on slow handlers
|
|
41
|
-
if (durationMs > SLOW_THRESHOLD_MS) {
|
|
42
|
-
this.logger.warn({
|
|
43
|
-
msg: `Slow handler detected: ${handlerName} took ${durationMs}ms`,
|
|
44
|
-
handler: handlerName,
|
|
45
|
-
durationMs,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
exports.PerformanceBehavior = PerformanceBehavior;
|
|
52
|
-
exports.PerformanceBehavior = PerformanceBehavior = __decorate([
|
|
53
|
-
(0, common_1.Injectable)(),
|
|
54
|
-
__param(0, (0, common_1.Optional)()),
|
|
55
|
-
__metadata("design:paramtypes", [MetricsService_1.MetricsService])
|
|
56
|
-
], PerformanceBehavior);
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { WorkflowEngine } from '../interfaces/WorkflowEngine';
|
|
2
|
-
import { Result } from '../../result/Result';
|
|
3
|
-
export declare class WorkflowBehavior {
|
|
4
|
-
private readonly engine?;
|
|
5
|
-
private readonly logger;
|
|
6
|
-
constructor(engine?: WorkflowEngine | undefined);
|
|
7
|
-
execute<T>(command: object, next: () => Promise<Result<T>>): Promise<Result<T>>;
|
|
8
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
|
-
var WorkflowBehavior_1;
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.WorkflowBehavior = void 0;
|
|
17
|
-
const common_1 = require("@nestjs/common");
|
|
18
|
-
const Workflow_decorator_1 = require("../decorators/Workflow.decorator");
|
|
19
|
-
const WorkflowEngine_1 = require("../interfaces/WorkflowEngine");
|
|
20
|
-
const Result_1 = require("../../result/Result");
|
|
21
|
-
let WorkflowBehavior = WorkflowBehavior_1 = class WorkflowBehavior {
|
|
22
|
-
engine;
|
|
23
|
-
logger = new common_1.Logger(WorkflowBehavior_1.name);
|
|
24
|
-
constructor(engine) {
|
|
25
|
-
this.engine = engine;
|
|
26
|
-
}
|
|
27
|
-
async execute(command, next) {
|
|
28
|
-
const metadata = (0, Workflow_decorator_1.getWorkflowMetadata)(command.constructor);
|
|
29
|
-
if (!metadata) {
|
|
30
|
-
return next();
|
|
31
|
-
}
|
|
32
|
-
if (!this.engine) {
|
|
33
|
-
this.logger.debug('No WorkflowEngine provided, skipping workflow behavior');
|
|
34
|
-
return next();
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const result = await this.engine.startProcess(metadata.processDefinitionId, command, { commandType: command.constructor.name });
|
|
38
|
-
return Result_1.Result.success(result);
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
const fallback = metadata.fallback ?? 'throw';
|
|
42
|
-
this.logger.warn(`Workflow start failed for "${metadata.processDefinitionId}", applying fallback: ${fallback}`, error.message);
|
|
43
|
-
switch (fallback) {
|
|
44
|
-
case 'skip':
|
|
45
|
-
return next();
|
|
46
|
-
case 'queue':
|
|
47
|
-
return Result_1.Result.failure(Result_1.ErrorType.InternalError, `Workflow queuing not available without companion package: ${error.message}`);
|
|
48
|
-
case 'throw':
|
|
49
|
-
default:
|
|
50
|
-
return Result_1.Result.failure(Result_1.ErrorType.InternalError, `Workflow start failed: ${error.message}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
exports.WorkflowBehavior = WorkflowBehavior;
|
|
56
|
-
exports.WorkflowBehavior = WorkflowBehavior = WorkflowBehavior_1 = __decorate([
|
|
57
|
-
(0, common_1.Injectable)(),
|
|
58
|
-
__param(0, (0, common_1.Optional)()),
|
|
59
|
-
__param(0, (0, common_1.Inject)(WorkflowEngine_1.WORKFLOW_ENGINE)),
|
|
60
|
-
__metadata("design:paramtypes", [Object])
|
|
61
|
-
], WorkflowBehavior);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface DomainEventPayload {
|
|
2
|
-
[key: string]: unknown;
|
|
3
|
-
}
|
|
4
|
-
export declare class DomainEvent {
|
|
5
|
-
readonly eventType: string;
|
|
6
|
-
readonly aggregateId: string;
|
|
7
|
-
readonly payload: DomainEventPayload;
|
|
8
|
-
readonly organizationId?: string | undefined;
|
|
9
|
-
readonly eventId: string;
|
|
10
|
-
readonly occurredAt: Date;
|
|
11
|
-
constructor(eventType: string, aggregateId: string, payload: DomainEventPayload, organizationId?: string | undefined);
|
|
12
|
-
/** The Redis Stream key this event should be published to */
|
|
13
|
-
get streamKey(): string;
|
|
14
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DomainEvent = void 0;
|
|
4
|
-
const uuid_1 = require("uuid");
|
|
5
|
-
class DomainEvent {
|
|
6
|
-
eventType;
|
|
7
|
-
aggregateId;
|
|
8
|
-
payload;
|
|
9
|
-
organizationId;
|
|
10
|
-
eventId;
|
|
11
|
-
occurredAt;
|
|
12
|
-
constructor(eventType, aggregateId, payload, organizationId) {
|
|
13
|
-
this.eventType = eventType;
|
|
14
|
-
this.aggregateId = aggregateId;
|
|
15
|
-
this.payload = payload;
|
|
16
|
-
this.organizationId = organizationId;
|
|
17
|
-
this.eventId = (0, uuid_1.v4)();
|
|
18
|
-
this.occurredAt = new Date();
|
|
19
|
-
}
|
|
20
|
-
/** The Redis Stream key this event should be published to */
|
|
21
|
-
get streamKey() {
|
|
22
|
-
// e.g. 'build.completed' → 'arex:events:builds'
|
|
23
|
-
const category = this.eventType.split('.')[0];
|
|
24
|
-
return `arex:events:${category}s`;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
exports.DomainEvent = DomainEvent;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export declare enum OutboxEventStatus {
|
|
2
|
-
Pending = "Pending",
|
|
3
|
-
Published = "Published",
|
|
4
|
-
Failed = "Failed"
|
|
5
|
-
}
|
|
6
|
-
export declare class OutboxEvent {
|
|
7
|
-
id: string;
|
|
8
|
-
eventType: string;
|
|
9
|
-
aggregateId: string;
|
|
10
|
-
streamKey: string;
|
|
11
|
-
payload: Record<string, unknown>;
|
|
12
|
-
organizationId: string | null;
|
|
13
|
-
status: OutboxEventStatus;
|
|
14
|
-
publishAttempts: number;
|
|
15
|
-
lastError: string | null;
|
|
16
|
-
createdAt: Date;
|
|
17
|
-
publishedAt: Date | null;
|
|
18
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.OutboxEvent = exports.OutboxEventStatus = void 0;
|
|
13
|
-
const typeorm_1 = require("typeorm");
|
|
14
|
-
var OutboxEventStatus;
|
|
15
|
-
(function (OutboxEventStatus) {
|
|
16
|
-
OutboxEventStatus["Pending"] = "Pending";
|
|
17
|
-
OutboxEventStatus["Published"] = "Published";
|
|
18
|
-
OutboxEventStatus["Failed"] = "Failed";
|
|
19
|
-
})(OutboxEventStatus || (exports.OutboxEventStatus = OutboxEventStatus = {}));
|
|
20
|
-
let OutboxEvent = class OutboxEvent {
|
|
21
|
-
id;
|
|
22
|
-
eventType;
|
|
23
|
-
aggregateId;
|
|
24
|
-
streamKey;
|
|
25
|
-
payload;
|
|
26
|
-
organizationId;
|
|
27
|
-
status;
|
|
28
|
-
publishAttempts;
|
|
29
|
-
lastError;
|
|
30
|
-
createdAt;
|
|
31
|
-
publishedAt;
|
|
32
|
-
};
|
|
33
|
-
exports.OutboxEvent = OutboxEvent;
|
|
34
|
-
__decorate([
|
|
35
|
-
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
36
|
-
__metadata("design:type", String)
|
|
37
|
-
], OutboxEvent.prototype, "id", void 0);
|
|
38
|
-
__decorate([
|
|
39
|
-
(0, typeorm_1.Column)({ type: 'varchar' }),
|
|
40
|
-
__metadata("design:type", String)
|
|
41
|
-
], OutboxEvent.prototype, "eventType", void 0);
|
|
42
|
-
__decorate([
|
|
43
|
-
(0, typeorm_1.Column)({ type: 'varchar' }),
|
|
44
|
-
__metadata("design:type", String)
|
|
45
|
-
], OutboxEvent.prototype, "aggregateId", void 0);
|
|
46
|
-
__decorate([
|
|
47
|
-
(0, typeorm_1.Column)({ type: 'varchar' }),
|
|
48
|
-
__metadata("design:type", String)
|
|
49
|
-
], OutboxEvent.prototype, "streamKey", void 0);
|
|
50
|
-
__decorate([
|
|
51
|
-
(0, typeorm_1.Column)({ type: 'jsonb' }),
|
|
52
|
-
__metadata("design:type", Object)
|
|
53
|
-
], OutboxEvent.prototype, "payload", void 0);
|
|
54
|
-
__decorate([
|
|
55
|
-
(0, typeorm_1.Column)({ type: 'uuid', nullable: true }),
|
|
56
|
-
__metadata("design:type", Object)
|
|
57
|
-
], OutboxEvent.prototype, "organizationId", void 0);
|
|
58
|
-
__decorate([
|
|
59
|
-
(0, typeorm_1.Column)({
|
|
60
|
-
type: 'enum',
|
|
61
|
-
enum: OutboxEventStatus,
|
|
62
|
-
default: OutboxEventStatus.Pending,
|
|
63
|
-
}),
|
|
64
|
-
__metadata("design:type", String)
|
|
65
|
-
], OutboxEvent.prototype, "status", void 0);
|
|
66
|
-
__decorate([
|
|
67
|
-
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
68
|
-
__metadata("design:type", Number)
|
|
69
|
-
], OutboxEvent.prototype, "publishAttempts", void 0);
|
|
70
|
-
__decorate([
|
|
71
|
-
(0, typeorm_1.Column)({ type: 'varchar', nullable: true }),
|
|
72
|
-
__metadata("design:type", Object)
|
|
73
|
-
], OutboxEvent.prototype, "lastError", void 0);
|
|
74
|
-
__decorate([
|
|
75
|
-
(0, typeorm_1.CreateDateColumn)({ type: 'timestamptz' }),
|
|
76
|
-
__metadata("design:type", Date)
|
|
77
|
-
], OutboxEvent.prototype, "createdAt", void 0);
|
|
78
|
-
__decorate([
|
|
79
|
-
(0, typeorm_1.Column)({ type: 'timestamptz', nullable: true }),
|
|
80
|
-
__metadata("design:type", Object)
|
|
81
|
-
], OutboxEvent.prototype, "publishedAt", void 0);
|
|
82
|
-
exports.OutboxEvent = OutboxEvent = __decorate([
|
|
83
|
-
(0, typeorm_1.Entity)('outbox_events'),
|
|
84
|
-
(0, typeorm_1.Index)('idx_outbox_pending', ['status', 'createdAt'], {
|
|
85
|
-
where: `"status" = 'Pending'`,
|
|
86
|
-
})
|
|
87
|
-
], OutboxEvent);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import { DataSource } from 'typeorm';
|
|
3
|
-
import { RedisStreamPublisher } from './RedisStreamPublisher';
|
|
4
|
-
export declare class OutboxPublisherService implements OnModuleInit, OnModuleDestroy {
|
|
5
|
-
private readonly dataSource;
|
|
6
|
-
private readonly redisPublisher;
|
|
7
|
-
private readonly logger;
|
|
8
|
-
private timer;
|
|
9
|
-
private processing;
|
|
10
|
-
constructor(dataSource: DataSource, redisPublisher: RedisStreamPublisher);
|
|
11
|
-
onModuleInit(): void;
|
|
12
|
-
onModuleDestroy(): void;
|
|
13
|
-
private pollAndPublish;
|
|
14
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var OutboxPublisherService_1;
|
|
12
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.OutboxPublisherService = void 0;
|
|
14
|
-
const common_1 = require("@nestjs/common");
|
|
15
|
-
const typeorm_1 = require("typeorm");
|
|
16
|
-
const OutboxEvent_entity_1 = require("./OutboxEvent.entity");
|
|
17
|
-
const RedisStreamPublisher_1 = require("./RedisStreamPublisher");
|
|
18
|
-
const POLL_INTERVAL_MS = 100;
|
|
19
|
-
const BATCH_SIZE = 50;
|
|
20
|
-
const MAX_PUBLISH_ATTEMPTS = 5;
|
|
21
|
-
const DLQ_STREAM = 'arex:events:dlq';
|
|
22
|
-
let OutboxPublisherService = OutboxPublisherService_1 = class OutboxPublisherService {
|
|
23
|
-
dataSource;
|
|
24
|
-
redisPublisher;
|
|
25
|
-
logger = new common_1.Logger(OutboxPublisherService_1.name);
|
|
26
|
-
timer = null;
|
|
27
|
-
processing = false;
|
|
28
|
-
constructor(dataSource, redisPublisher) {
|
|
29
|
-
this.dataSource = dataSource;
|
|
30
|
-
this.redisPublisher = redisPublisher;
|
|
31
|
-
}
|
|
32
|
-
onModuleInit() {
|
|
33
|
-
this.timer = setInterval(() => this.pollAndPublish(), POLL_INTERVAL_MS);
|
|
34
|
-
this.logger.log(`Outbox publisher started (${POLL_INTERVAL_MS}ms poll interval)`);
|
|
35
|
-
}
|
|
36
|
-
onModuleDestroy() {
|
|
37
|
-
if (this.timer) {
|
|
38
|
-
clearInterval(this.timer);
|
|
39
|
-
this.timer = null;
|
|
40
|
-
}
|
|
41
|
-
this.logger.log('Outbox publisher stopped');
|
|
42
|
-
}
|
|
43
|
-
async pollAndPublish() {
|
|
44
|
-
if (this.processing)
|
|
45
|
-
return;
|
|
46
|
-
this.processing = true;
|
|
47
|
-
try {
|
|
48
|
-
const repo = this.dataSource.getRepository(OutboxEvent_entity_1.OutboxEvent);
|
|
49
|
-
const events = await repo.find({
|
|
50
|
-
where: { status: OutboxEvent_entity_1.OutboxEventStatus.Pending },
|
|
51
|
-
order: { createdAt: 'ASC' },
|
|
52
|
-
take: BATCH_SIZE,
|
|
53
|
-
});
|
|
54
|
-
for (const event of events) {
|
|
55
|
-
try {
|
|
56
|
-
await this.redisPublisher.publishToStream(event.streamKey, {
|
|
57
|
-
eventId: event.id,
|
|
58
|
-
eventType: event.eventType,
|
|
59
|
-
aggregateId: event.aggregateId,
|
|
60
|
-
organizationId: event.organizationId || '',
|
|
61
|
-
payload: JSON.stringify(event.payload),
|
|
62
|
-
occurredAt: event.createdAt.toISOString(),
|
|
63
|
-
});
|
|
64
|
-
event.status = OutboxEvent_entity_1.OutboxEventStatus.Published;
|
|
65
|
-
event.publishedAt = new Date();
|
|
66
|
-
await repo.save(event);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
event.publishAttempts += 1;
|
|
70
|
-
event.lastError = error.message;
|
|
71
|
-
if (event.publishAttempts >= MAX_PUBLISH_ATTEMPTS) {
|
|
72
|
-
event.status = OutboxEvent_entity_1.OutboxEventStatus.Failed;
|
|
73
|
-
this.logger.error(`Event ${event.id} exceeded max attempts, routing to DLQ`);
|
|
74
|
-
try {
|
|
75
|
-
await this.redisPublisher.publishToStream(DLQ_STREAM, {
|
|
76
|
-
eventId: event.id,
|
|
77
|
-
eventType: event.eventType,
|
|
78
|
-
aggregateId: event.aggregateId,
|
|
79
|
-
error: event.lastError || 'unknown',
|
|
80
|
-
payload: JSON.stringify(event.payload),
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
this.logger.error(`Failed to route event ${event.id} to DLQ`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
await repo.save(event);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
this.logger.error('Outbox poll cycle failed', error.stack);
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
this.processing = false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
exports.OutboxPublisherService = OutboxPublisherService;
|
|
100
|
-
exports.OutboxPublisherService = OutboxPublisherService = OutboxPublisherService_1 = __decorate([
|
|
101
|
-
(0, common_1.Injectable)(),
|
|
102
|
-
__metadata("design:paramtypes", [typeorm_1.DataSource,
|
|
103
|
-
RedisStreamPublisher_1.RedisStreamPublisher])
|
|
104
|
-
], OutboxPublisherService);
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import type { Redis } from 'ioredis';
|
|
3
|
-
/**
|
|
4
|
-
* Abstract base class for Redis Stream consumer groups.
|
|
5
|
-
*
|
|
6
|
-
* Subclasses declare `streamKey`, `consumerGroup`, `consumerName`
|
|
7
|
-
* and implement `handleMessage(fields)`.
|
|
8
|
-
*
|
|
9
|
-
* Lifecycle:
|
|
10
|
-
* - OnModuleInit: creates consumer group (idempotent), starts read loop
|
|
11
|
-
* - OnModuleDestroy: stops the read loop gracefully, disconnects dedicated client
|
|
12
|
-
*
|
|
13
|
-
* Connection strategy:
|
|
14
|
-
* - Shared REDIS_CLIENT: used for non-blocking ops (XGROUP CREATE, XACK)
|
|
15
|
-
* - Dedicated cloned connection: used ONLY for blocking XREADGROUP BLOCK calls
|
|
16
|
-
* - This prevents blocking consumers from starving non-blocking callers
|
|
17
|
-
* (cache, distributed locks, publishers) that share the main REDIS_CLIENT.
|
|
18
|
-
*
|
|
19
|
-
* Features:
|
|
20
|
-
* - Auto-creates consumer group via XGROUP CREATE ... MKSTREAM
|
|
21
|
-
* - Recovers pending (unacked) messages on startup before reading new ones
|
|
22
|
-
* - XREADGROUP BLOCK for efficient new-message polling (on dedicated connection)
|
|
23
|
-
* - XACK after successful processing (on shared connection)
|
|
24
|
-
* - Graceful shutdown (finishes in-flight message, disconnects dedicated client)
|
|
25
|
-
*/
|
|
26
|
-
export declare abstract class RedisStreamConsumer implements OnModuleInit, OnModuleDestroy {
|
|
27
|
-
protected readonly redis?: Redis | undefined;
|
|
28
|
-
protected readonly logger: Logger;
|
|
29
|
-
abstract readonly streamKey: string;
|
|
30
|
-
abstract readonly consumerGroup: string;
|
|
31
|
-
abstract readonly consumerName: string;
|
|
32
|
-
/** Override to filter by eventType. Return true to process. Default: all messages. */
|
|
33
|
-
protected shouldHandle(_fields: Record<string, string>): boolean;
|
|
34
|
-
abstract handleMessage(fields: Record<string, string>): Promise<void>;
|
|
35
|
-
private running;
|
|
36
|
-
/** Dedicated connection for blocking XREADGROUP — cloned from shared client on init */
|
|
37
|
-
private blockingClient?;
|
|
38
|
-
constructor(redis?: Redis | undefined);
|
|
39
|
-
onModuleInit(): Promise<void>;
|
|
40
|
-
onModuleDestroy(): Promise<void>;
|
|
41
|
-
private consumeLoop;
|
|
42
|
-
private readMessages;
|
|
43
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.RedisStreamConsumer = void 0;
|
|
16
|
-
const common_1 = require("@nestjs/common");
|
|
17
|
-
const constants_1 = require("../cqrs/constants");
|
|
18
|
-
/**
|
|
19
|
-
* Abstract base class for Redis Stream consumer groups.
|
|
20
|
-
*
|
|
21
|
-
* Subclasses declare `streamKey`, `consumerGroup`, `consumerName`
|
|
22
|
-
* and implement `handleMessage(fields)`.
|
|
23
|
-
*
|
|
24
|
-
* Lifecycle:
|
|
25
|
-
* - OnModuleInit: creates consumer group (idempotent), starts read loop
|
|
26
|
-
* - OnModuleDestroy: stops the read loop gracefully, disconnects dedicated client
|
|
27
|
-
*
|
|
28
|
-
* Connection strategy:
|
|
29
|
-
* - Shared REDIS_CLIENT: used for non-blocking ops (XGROUP CREATE, XACK)
|
|
30
|
-
* - Dedicated cloned connection: used ONLY for blocking XREADGROUP BLOCK calls
|
|
31
|
-
* - This prevents blocking consumers from starving non-blocking callers
|
|
32
|
-
* (cache, distributed locks, publishers) that share the main REDIS_CLIENT.
|
|
33
|
-
*
|
|
34
|
-
* Features:
|
|
35
|
-
* - Auto-creates consumer group via XGROUP CREATE ... MKSTREAM
|
|
36
|
-
* - Recovers pending (unacked) messages on startup before reading new ones
|
|
37
|
-
* - XREADGROUP BLOCK for efficient new-message polling (on dedicated connection)
|
|
38
|
-
* - XACK after successful processing (on shared connection)
|
|
39
|
-
* - Graceful shutdown (finishes in-flight message, disconnects dedicated client)
|
|
40
|
-
*/
|
|
41
|
-
let RedisStreamConsumer = class RedisStreamConsumer {
|
|
42
|
-
redis;
|
|
43
|
-
logger = new common_1.Logger(this.constructor.name);
|
|
44
|
-
/** Override to filter by eventType. Return true to process. Default: all messages. */
|
|
45
|
-
shouldHandle(_fields) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
running = false;
|
|
49
|
-
/** Dedicated connection for blocking XREADGROUP — cloned from shared client on init */
|
|
50
|
-
blockingClient;
|
|
51
|
-
constructor(redis) {
|
|
52
|
-
this.redis = redis;
|
|
53
|
-
}
|
|
54
|
-
async onModuleInit() {
|
|
55
|
-
if (!this.redis) {
|
|
56
|
-
this.logger.warn('Redis client not available — consumer disabled');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
// Clone a dedicated connection for blocking reads.
|
|
60
|
-
// This prevents XREADGROUP BLOCK from starving cache/lock/publish ops on the shared client.
|
|
61
|
-
this.blockingClient = this.redis.duplicate();
|
|
62
|
-
this.blockingClient.on('error', (err) => this.logger.error(`Blocking client error on ${this.streamKey}: ${err.message}`));
|
|
63
|
-
// Create consumer group (idempotent) — uses shared client (non-blocking)
|
|
64
|
-
try {
|
|
65
|
-
await this.redis.xgroup('CREATE', this.streamKey, this.consumerGroup, '0', 'MKSTREAM');
|
|
66
|
-
this.logger.log(`Created consumer group "${this.consumerGroup}" on "${this.streamKey}"`);
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
if (!err.message?.includes('BUSYGROUP')) {
|
|
70
|
-
throw err;
|
|
71
|
-
}
|
|
72
|
-
// Group already exists — fine
|
|
73
|
-
}
|
|
74
|
-
this.running = true;
|
|
75
|
-
// Process pending messages first, then new ones
|
|
76
|
-
// Run in background so NestJS startup isn't blocked
|
|
77
|
-
void this.consumeLoop();
|
|
78
|
-
}
|
|
79
|
-
async onModuleDestroy() {
|
|
80
|
-
this.running = false;
|
|
81
|
-
// Disconnect the dedicated blocking client
|
|
82
|
-
if (this.blockingClient) {
|
|
83
|
-
this.blockingClient.disconnect();
|
|
84
|
-
this.blockingClient = undefined;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async consumeLoop() {
|
|
88
|
-
// Phase 1: recover pending (unacked) messages
|
|
89
|
-
await this.readMessages('0');
|
|
90
|
-
// Phase 2: read new messages
|
|
91
|
-
while (this.running) {
|
|
92
|
-
await this.readMessages('>');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async readMessages(id) {
|
|
96
|
-
if (!this.redis || !this.blockingClient || !this.running)
|
|
97
|
-
return;
|
|
98
|
-
try {
|
|
99
|
-
const blockMs = id === '>' ? 5000 : undefined;
|
|
100
|
-
const args = [
|
|
101
|
-
'GROUP',
|
|
102
|
-
this.consumerGroup,
|
|
103
|
-
this.consumerName,
|
|
104
|
-
'COUNT',
|
|
105
|
-
'10',
|
|
106
|
-
];
|
|
107
|
-
if (blockMs !== undefined) {
|
|
108
|
-
args.push('BLOCK', blockMs);
|
|
109
|
-
}
|
|
110
|
-
args.push('STREAMS', this.streamKey, id);
|
|
111
|
-
// Use dedicated blocking client for XREADGROUP — never the shared REDIS_CLIENT
|
|
112
|
-
const results = await this.blockingClient.xreadgroup(...args);
|
|
113
|
-
if (!results || results.length === 0) {
|
|
114
|
-
if (id === '0')
|
|
115
|
-
return; // No pending — switch to new messages
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const [, entries] = results[0];
|
|
119
|
-
if (entries.length === 0 && id === '0') {
|
|
120
|
-
return; // All pending recovered
|
|
121
|
-
}
|
|
122
|
-
for (const [messageId, fieldArray] of entries) {
|
|
123
|
-
// Parse flat field array into Record
|
|
124
|
-
const fields = {};
|
|
125
|
-
for (let i = 0; i < fieldArray.length; i += 2) {
|
|
126
|
-
fields[fieldArray[i]] = fieldArray[i + 1];
|
|
127
|
-
}
|
|
128
|
-
if (!this.shouldHandle(fields)) {
|
|
129
|
-
// Ack and skip — uses shared client (non-blocking)
|
|
130
|
-
await this.redis.xack(this.streamKey, this.consumerGroup, messageId);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
await this.handleMessage(fields);
|
|
135
|
-
// XACK on shared client — non-blocking
|
|
136
|
-
await this.redis.xack(this.streamKey, this.consumerGroup, messageId);
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
this.logger.error(`Failed to process message ${messageId} on ${this.streamKey}: ${err.message}`, err.stack);
|
|
140
|
-
// Don't ack — message stays pending for retry on next startup
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
catch (err) {
|
|
145
|
-
if (!this.running)
|
|
146
|
-
return; // Shutdown in progress
|
|
147
|
-
this.logger.error(`Consumer read error on ${this.streamKey}: ${err.message}`, err.stack);
|
|
148
|
-
// Back off before retrying
|
|
149
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
exports.RedisStreamConsumer = RedisStreamConsumer;
|
|
154
|
-
exports.RedisStreamConsumer = RedisStreamConsumer = __decorate([
|
|
155
|
-
__param(0, (0, common_1.Optional)()),
|
|
156
|
-
__param(0, (0, common_1.Inject)(constants_1.REDIS_CLIENT)),
|
|
157
|
-
__metadata("design:paramtypes", [Function])
|
|
158
|
-
], RedisStreamConsumer);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Redis } from 'ioredis';
|
|
2
|
-
import { DomainEvent } from './DomainEvent';
|
|
3
|
-
export declare class RedisStreamPublisher {
|
|
4
|
-
private readonly redis?;
|
|
5
|
-
private readonly logger;
|
|
6
|
-
constructor(redis?: Redis | undefined);
|
|
7
|
-
publish(event: DomainEvent): Promise<void>;
|
|
8
|
-
publishToStream(streamKey: string, fields: Record<string, string>): Promise<string | null>;
|
|
9
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
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;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
|
-
var RedisStreamPublisher_1;
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.RedisStreamPublisher = void 0;
|
|
17
|
-
const common_1 = require("@nestjs/common");
|
|
18
|
-
const constants_1 = require("../cqrs/constants");
|
|
19
|
-
const MAX_STREAM_LENGTH = 10000;
|
|
20
|
-
let RedisStreamPublisher = RedisStreamPublisher_1 = class RedisStreamPublisher {
|
|
21
|
-
redis;
|
|
22
|
-
logger = new common_1.Logger(RedisStreamPublisher_1.name);
|
|
23
|
-
constructor(redis) {
|
|
24
|
-
this.redis = redis;
|
|
25
|
-
}
|
|
26
|
-
async publish(event) {
|
|
27
|
-
if (!this.redis) {
|
|
28
|
-
this.logger.warn('Redis client not available, skipping event publish');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
await this.publishToStream(event.streamKey, {
|
|
32
|
-
eventId: event.eventId,
|
|
33
|
-
eventType: event.eventType,
|
|
34
|
-
aggregateId: event.aggregateId,
|
|
35
|
-
organizationId: event.organizationId || '',
|
|
36
|
-
payload: JSON.stringify(event.payload),
|
|
37
|
-
occurredAt: event.occurredAt.toISOString(),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
async publishToStream(streamKey, fields) {
|
|
41
|
-
if (!this.redis)
|
|
42
|
-
return null;
|
|
43
|
-
try {
|
|
44
|
-
const messageId = await this.redis.xadd(streamKey, 'MAXLEN', '~', String(MAX_STREAM_LENGTH), '*', ...Object.entries(fields).flat());
|
|
45
|
-
this.logger.debug(`Published to ${streamKey}: ${messageId}`);
|
|
46
|
-
return messageId;
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
this.logger.error(`Failed to publish to ${streamKey}`, error.stack);
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
exports.RedisStreamPublisher = RedisStreamPublisher;
|
|
55
|
-
exports.RedisStreamPublisher = RedisStreamPublisher = RedisStreamPublisher_1 = __decorate([
|
|
56
|
-
(0, common_1.Injectable)(),
|
|
57
|
-
__param(0, (0, common_1.Optional)()),
|
|
58
|
-
__param(0, (0, common_1.Inject)(constants_1.REDIS_CLIENT)),
|
|
59
|
-
__metadata("design:paramtypes", [Function])
|
|
60
|
-
], RedisStreamPublisher);
|