@quanticjs/events-redis 6.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/QuanticEventsRedisModule.d.ts +4 -0
- package/dist/QuanticEventsRedisModule.js +37 -0
- package/dist/RedisStreamConsumer.d.ts +20 -0
- package/dist/RedisStreamConsumer.js +144 -0
- package/dist/RedisStreamPublisher.d.ts +11 -0
- package/dist/RedisStreamPublisher.js +70 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +9 -0
- package/package.json +34 -0
|
@@ -0,0 +1,37 @@
|
|
|
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 QuanticEventsRedisModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.QuanticEventsRedisModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const events_core_1 = require("@quanticjs/events-core");
|
|
13
|
+
const RedisStreamPublisher_1 = require("./RedisStreamPublisher");
|
|
14
|
+
let QuanticEventsRedisModule = QuanticEventsRedisModule_1 = class QuanticEventsRedisModule {
|
|
15
|
+
static forRoot() {
|
|
16
|
+
return {
|
|
17
|
+
module: QuanticEventsRedisModule_1,
|
|
18
|
+
imports: [events_core_1.QuanticEventsCoreModule.forRoot()],
|
|
19
|
+
providers: [
|
|
20
|
+
RedisStreamPublisher_1.RedisStreamPublisher,
|
|
21
|
+
{
|
|
22
|
+
provide: events_core_1.EVENT_PUBLISHER,
|
|
23
|
+
useExisting: RedisStreamPublisher_1.RedisStreamPublisher,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
exports: [
|
|
27
|
+
RedisStreamPublisher_1.RedisStreamPublisher,
|
|
28
|
+
events_core_1.EVENT_PUBLISHER,
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.QuanticEventsRedisModule = QuanticEventsRedisModule;
|
|
34
|
+
exports.QuanticEventsRedisModule = QuanticEventsRedisModule = QuanticEventsRedisModule_1 = __decorate([
|
|
35
|
+
(0, common_1.Global)(),
|
|
36
|
+
(0, common_1.Module)({})
|
|
37
|
+
], QuanticEventsRedisModule);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export declare abstract class RedisStreamConsumer implements OnModuleInit, OnModuleDestroy {
|
|
4
|
+
protected readonly redis?: Redis | undefined;
|
|
5
|
+
protected readonly logger: Logger;
|
|
6
|
+
abstract readonly streamKey: string;
|
|
7
|
+
abstract readonly consumerGroup: string;
|
|
8
|
+
abstract readonly consumerName: string;
|
|
9
|
+
protected shouldHandle(_fields: Record<string, string>): boolean;
|
|
10
|
+
abstract handleMessage(fields: Record<string, string>): Promise<void>;
|
|
11
|
+
private readonly tracer;
|
|
12
|
+
private running;
|
|
13
|
+
private blockingClient?;
|
|
14
|
+
constructor(redis?: Redis | undefined);
|
|
15
|
+
onModuleInit(): Promise<void>;
|
|
16
|
+
onModuleDestroy(): Promise<void>;
|
|
17
|
+
private processWithSpan;
|
|
18
|
+
private consumeLoop;
|
|
19
|
+
private readMessages;
|
|
20
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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 core_1 = require("@quanticjs/core");
|
|
18
|
+
let RedisStreamConsumer = class RedisStreamConsumer {
|
|
19
|
+
redis;
|
|
20
|
+
logger = new common_1.Logger(this.constructor.name);
|
|
21
|
+
shouldHandle(_fields) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
tracer = (0, core_1.getTracer)('@quanticjs/events');
|
|
25
|
+
running = false;
|
|
26
|
+
blockingClient;
|
|
27
|
+
constructor(redis) {
|
|
28
|
+
this.redis = redis;
|
|
29
|
+
}
|
|
30
|
+
async onModuleInit() {
|
|
31
|
+
if (!this.redis) {
|
|
32
|
+
this.logger.warn('Redis client not available — consumer disabled');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.blockingClient = this.redis.duplicate();
|
|
36
|
+
this.blockingClient.on('error', (err) => this.logger.error(`Blocking client error on ${this.streamKey}: ${err.message}`));
|
|
37
|
+
try {
|
|
38
|
+
await this.redis.xgroup('CREATE', this.streamKey, this.consumerGroup, '0', 'MKSTREAM');
|
|
39
|
+
this.logger.log(`Created consumer group "${this.consumerGroup}" on "${this.streamKey}"`);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (!err.message?.includes('BUSYGROUP')) {
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
this.running = true;
|
|
47
|
+
void this.consumeLoop();
|
|
48
|
+
}
|
|
49
|
+
async onModuleDestroy() {
|
|
50
|
+
this.running = false;
|
|
51
|
+
if (this.blockingClient) {
|
|
52
|
+
this.blockingClient.disconnect();
|
|
53
|
+
this.blockingClient = undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async processWithSpan(messageId, fields) {
|
|
57
|
+
if (!this.tracer) {
|
|
58
|
+
return this.handleMessage(fields);
|
|
59
|
+
}
|
|
60
|
+
const spanName = `${this.streamKey} process`;
|
|
61
|
+
return this.tracer.startActiveSpan(spanName, async (span) => {
|
|
62
|
+
span.setAttribute('messaging.system', 'redis_stream');
|
|
63
|
+
span.setAttribute('messaging.destination', this.streamKey);
|
|
64
|
+
span.setAttribute('messaging.message_id', messageId);
|
|
65
|
+
if (fields.eventType)
|
|
66
|
+
span.setAttribute('messaging.event_type', fields.eventType);
|
|
67
|
+
try {
|
|
68
|
+
await this.handleMessage(fields);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
72
|
+
span.setStatus({ code: core_1.SPAN_STATUS_ERROR, message: err.message });
|
|
73
|
+
span.recordException(err);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
span.end();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async consumeLoop() {
|
|
82
|
+
await this.readMessages('0');
|
|
83
|
+
while (this.running) {
|
|
84
|
+
await this.readMessages('>');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async readMessages(id) {
|
|
88
|
+
if (!this.redis || !this.blockingClient || !this.running)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
const blockMs = id === '>' ? 5000 : undefined;
|
|
92
|
+
const args = [
|
|
93
|
+
'GROUP',
|
|
94
|
+
this.consumerGroup,
|
|
95
|
+
this.consumerName,
|
|
96
|
+
'COUNT',
|
|
97
|
+
'10',
|
|
98
|
+
];
|
|
99
|
+
if (blockMs !== undefined) {
|
|
100
|
+
args.push('BLOCK', blockMs);
|
|
101
|
+
}
|
|
102
|
+
args.push('STREAMS', this.streamKey, id);
|
|
103
|
+
const results = await this.blockingClient.xreadgroup(...args);
|
|
104
|
+
if (!results || results.length === 0) {
|
|
105
|
+
if (id === '0')
|
|
106
|
+
return;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const [, entries] = results[0];
|
|
110
|
+
if (entries.length === 0 && id === '0') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const [messageId, fieldArray] of entries) {
|
|
114
|
+
const fields = {};
|
|
115
|
+
for (let i = 0; i < fieldArray.length; i += 2) {
|
|
116
|
+
fields[fieldArray[i]] = fieldArray[i + 1];
|
|
117
|
+
}
|
|
118
|
+
if (!this.shouldHandle(fields)) {
|
|
119
|
+
await this.redis.xack(this.streamKey, this.consumerGroup, messageId);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
await (0, core_1.withExtractedContext)(fields, () => this.processWithSpan(messageId, fields));
|
|
124
|
+
await this.redis.xack(this.streamKey, this.consumerGroup, messageId);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
this.logger.error(`Failed to process message ${messageId} on ${this.streamKey}: ${err.message}`, err.stack);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (!this.running)
|
|
133
|
+
return;
|
|
134
|
+
this.logger.error(`Consumer read error on ${this.streamKey}: ${err.message}`, err.stack);
|
|
135
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
exports.RedisStreamConsumer = RedisStreamConsumer;
|
|
140
|
+
exports.RedisStreamConsumer = RedisStreamConsumer = __decorate([
|
|
141
|
+
__param(0, (0, common_1.Optional)()),
|
|
142
|
+
__param(0, (0, common_1.Inject)(core_1.REDIS_CLIENT)),
|
|
143
|
+
__metadata("design:paramtypes", [Function])
|
|
144
|
+
], RedisStreamConsumer);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IEventPublisher, DomainEvent } from '@quanticjs/events-core';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export declare class RedisStreamPublisher implements IEventPublisher {
|
|
4
|
+
private readonly redis?;
|
|
5
|
+
private readonly logger;
|
|
6
|
+
constructor(redis?: Redis | undefined);
|
|
7
|
+
publish(event: DomainEvent): Promise<void>;
|
|
8
|
+
publishToDestination(destination: string, fields: Record<string, string>): Promise<string | null>;
|
|
9
|
+
publishToStream(streamKey: string, fields: Record<string, string>): Promise<string | null>;
|
|
10
|
+
private topicToStreamKey;
|
|
11
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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 core_1 = require("@quanticjs/core");
|
|
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
|
+
const fields = {
|
|
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
|
+
(0, core_1.injectTraceContext)(fields);
|
|
40
|
+
const streamKey = this.topicToStreamKey(event.topic);
|
|
41
|
+
await this.publishToStream(streamKey, fields);
|
|
42
|
+
}
|
|
43
|
+
async publishToDestination(destination, fields) {
|
|
44
|
+
const streamKey = this.topicToStreamKey(destination);
|
|
45
|
+
return this.publishToStream(streamKey, fields);
|
|
46
|
+
}
|
|
47
|
+
async publishToStream(streamKey, fields) {
|
|
48
|
+
if (!this.redis)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const messageId = await this.redis.xadd(streamKey, 'MAXLEN', '~', String(MAX_STREAM_LENGTH), '*', ...Object.entries(fields).flat());
|
|
52
|
+
this.logger.debug(`Published to ${streamKey}: ${messageId}`);
|
|
53
|
+
return messageId;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.logger.error(`Failed to publish to ${streamKey}`, error.stack);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
topicToStreamKey(topic) {
|
|
61
|
+
return topic.replace(/\./g, ':');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
exports.RedisStreamPublisher = RedisStreamPublisher;
|
|
65
|
+
exports.RedisStreamPublisher = RedisStreamPublisher = RedisStreamPublisher_1 = __decorate([
|
|
66
|
+
(0, common_1.Injectable)(),
|
|
67
|
+
__param(0, (0, common_1.Optional)()),
|
|
68
|
+
__param(0, (0, common_1.Inject)(core_1.REDIS_CLIENT)),
|
|
69
|
+
__metadata("design:paramtypes", [Function])
|
|
70
|
+
], RedisStreamPublisher);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuanticEventsRedisModule = exports.RedisStreamConsumer = exports.RedisStreamPublisher = void 0;
|
|
4
|
+
var RedisStreamPublisher_1 = require("./RedisStreamPublisher");
|
|
5
|
+
Object.defineProperty(exports, "RedisStreamPublisher", { enumerable: true, get: function () { return RedisStreamPublisher_1.RedisStreamPublisher; } });
|
|
6
|
+
var RedisStreamConsumer_1 = require("./RedisStreamConsumer");
|
|
7
|
+
Object.defineProperty(exports, "RedisStreamConsumer", { enumerable: true, get: function () { return RedisStreamConsumer_1.RedisStreamConsumer; } });
|
|
8
|
+
var QuanticEventsRedisModule_1 = require("./QuanticEventsRedisModule");
|
|
9
|
+
Object.defineProperty(exports, "QuanticEventsRedisModule", { enumerable: true, get: function () { return QuanticEventsRedisModule_1.QuanticEventsRedisModule; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quanticjs/events-redis",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Redis Streams transport for @quanticjs/events-core — RedisStreamPublisher, RedisStreamConsumer",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org",
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"test": "jest --passWithNoTests",
|
|
18
|
+
"clean": "rm -rf dist"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@quanticjs/core": "^6.0.0",
|
|
22
|
+
"@quanticjs/events-core": "^6.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@nestjs/common": "^11.0.0",
|
|
26
|
+
"@opentelemetry/api": "^1.9.0",
|
|
27
|
+
"ioredis": "^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@opentelemetry/api": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|