@vortenixinnovations/flagra-common-module 1.0.58 → 1.0.60

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.
@@ -0,0 +1,11 @@
1
+ import { Subjects } from "../base/subjects.js";
2
+ export interface AccountCreatedEvent {
3
+ subject: Subjects.AccountCreated;
4
+ data: {
5
+ id: string;
6
+ fullname: string;
7
+ role: string;
8
+ email: string;
9
+ password: string;
10
+ };
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { Subjects } from "../base/subjects.js";
2
+ export interface AccountVerifyEvent {
3
+ subject: Subjects.AccountVerify;
4
+ data: {
5
+ id: string;
6
+ first: string;
7
+ email: string;
8
+ otp: number;
9
+ };
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { Subjects } from "./subjects.js";
2
+ export interface Event {
3
+ subject: Subjects;
4
+ data: any;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { JetStreamClient, JetStreamManager, JsMsg, NatsConnection, ConsumerConfig } from "nats";
2
+ import { Subjects } from "./subjects.js";
3
+ interface Event {
4
+ subject: Subjects;
5
+ data: any;
6
+ }
7
+ export declare abstract class Listener<T extends Event> {
8
+ protected nc: NatsConnection;
9
+ abstract subject: T["subject"];
10
+ abstract queueGroupName: string;
11
+ abstract onMessage(data: T["data"], msg: JsMsg): void;
12
+ protected client: JetStreamClient;
13
+ protected manager: JetStreamManager;
14
+ protected ackWait: number;
15
+ constructor(nc: NatsConnection);
16
+ init(): Promise<void>;
17
+ subscriptionOptions(): Partial<ConsumerConfig>;
18
+ listen(): Promise<void>;
19
+ private parseMessage;
20
+ }
21
+ export {};
@@ -0,0 +1,75 @@
1
+ import { AckPolicy, StorageType, DeliverPolicy, } from "nats";
2
+ import { logger } from "../../util/logger/index.js";
3
+ export class Listener {
4
+ nc;
5
+ client;
6
+ manager;
7
+ ackWait = 30_000_000_000; // 30 seconds in nanoseconds
8
+ constructor(nc) {
9
+ this.nc = nc;
10
+ this.client = nc.jetstream();
11
+ }
12
+ async init() {
13
+ this.manager = await this.nc.jetstreamManager();
14
+ }
15
+ subscriptionOptions() {
16
+ return {
17
+ durable_name: this.queueGroupName,
18
+ ack_policy: AckPolicy.Explicit,
19
+ deliver_policy: DeliverPolicy.All,
20
+ ack_wait: this.ackWait,
21
+ max_ack_pending: 100,
22
+ max_deliver: 5,
23
+ };
24
+ }
25
+ async listen() {
26
+ if (!this.manager)
27
+ await this.init();
28
+ const streamName = `STREAM_${this.subject}`;
29
+ const durableName = this.queueGroupName;
30
+ // Ensure the stream exists
31
+ try {
32
+ await this.manager.streams.info(streamName);
33
+ }
34
+ catch {
35
+ logger.info(`Creating stream: ${streamName}`);
36
+ await this.manager.streams.add({
37
+ name: streamName,
38
+ subjects: [this.subject],
39
+ storage: StorageType.File,
40
+ });
41
+ }
42
+ // Ensure the consumer (durable queue group) exists
43
+ try {
44
+ await this.manager.consumers.info(streamName, durableName);
45
+ }
46
+ catch {
47
+ logger.info(`Creating durable consumer: ${durableName}`);
48
+ await this.manager.consumers.add(streamName, this.subscriptionOptions());
49
+ }
50
+ logger.info(`Listening for subject "${this.subject}" with queue group "${durableName}"`);
51
+ const consumer = await this.client.consumers.get(streamName, durableName);
52
+ // Use next() in a loop - simplest and works reliably
53
+ while (true) {
54
+ try {
55
+ const msg = await consumer.next({ expires: 30000 });
56
+ if (msg) {
57
+ const parsedData = this.parseMessage(msg);
58
+ this.onMessage(parsedData, msg);
59
+ }
60
+ }
61
+ catch (err) {
62
+ // Handle timeout or other errors
63
+ if (err.code !== "408") {
64
+ // 408 is timeout, which is normal
65
+ logger.info("Error getting message:", err);
66
+ await new Promise((resolve) => setTimeout(resolve, 1000));
67
+ }
68
+ }
69
+ }
70
+ }
71
+ parseMessage(msg) {
72
+ const data = msg.data;
73
+ return JSON.parse(new TextDecoder().decode(data));
74
+ }
75
+ }
@@ -0,0 +1,9 @@
1
+ import { NatsConnection } from "nats";
2
+ import { Event } from "./base-event.js";
3
+ export declare abstract class Publisher<T extends Event> {
4
+ abstract subject: T["subject"];
5
+ protected client: NatsConnection;
6
+ private sc;
7
+ constructor(client: NatsConnection);
8
+ publish(data: T["data"]): Promise<void>;
9
+ }
@@ -0,0 +1,15 @@
1
+ import { StringCodec } from "nats";
2
+ import { logger } from "../../util/logger/index.js";
3
+ export class Publisher {
4
+ client;
5
+ sc = StringCodec();
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ async publish(data) {
10
+ const encoded = this.sc.encode(JSON.stringify(data));
11
+ this.client.publish(this.subject, encoded);
12
+ await this.client.flush();
13
+ logger.info(`Event published to [${this.subject}]: ${JSON.stringify(data)}`);
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ import { NatsConnection } from "nats";
2
+ import { RequestEvent } from "./request-event.js";
3
+ export declare abstract class RequestListener<T extends RequestEvent> {
4
+ abstract subject: T["subject"];
5
+ abstract queueGroupName: string;
6
+ protected client: NatsConnection;
7
+ private sc;
8
+ constructor(client: NatsConnection);
9
+ abstract onRequest(data: T["request"]): Promise<T["response"]>;
10
+ listen(): Promise<void>;
11
+ }
@@ -0,0 +1,25 @@
1
+ import { StringCodec } from "nats";
2
+ import { logger } from "../../util/logger/index.js";
3
+ export class RequestListener {
4
+ client;
5
+ sc = StringCodec();
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ async listen() {
10
+ const opts = { queue: this.queueGroupName };
11
+ const sub = this.client.subscribe(this.subject, opts);
12
+ logger.info(`Listening for RPC requests on subject [${this.subject}] in queue [${this.queueGroupName}]`);
13
+ for await (const msg of sub) {
14
+ try {
15
+ const data = JSON.parse(this.sc.decode(msg.data));
16
+ const response = await this.onRequest(data);
17
+ msg.respond(this.sc.encode(JSON.stringify(response)));
18
+ }
19
+ catch (err) {
20
+ logger.error(`Failed to process RPC [${this.subject}]: ${err.message}`);
21
+ msg.respond(this.sc.encode(JSON.stringify({ error: err.message })));
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ import { NatsConnection } from "nats";
2
+ import { RequestEvent } from "./request-event.js";
3
+ export declare abstract class RequestPublisher<T extends RequestEvent> {
4
+ abstract subject: T["subject"];
5
+ protected client: NatsConnection;
6
+ private sc;
7
+ constructor(client: NatsConnection);
8
+ send(data: T["request"], timeout?: number): Promise<T["response"]>;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { StringCodec } from "nats";
2
+ import { logger } from "../../util/logger/index.js";
3
+ export class RequestPublisher {
4
+ client;
5
+ sc = StringCodec();
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ async send(data, timeout = 5000) {
10
+ const encoded = this.sc.encode(JSON.stringify(data));
11
+ try {
12
+ const msg = await this.client.request(this.subject, encoded, { timeout });
13
+ const response = JSON.parse(this.sc.decode(msg.data));
14
+ logger.info(`RPC response received for [${this.subject}]`);
15
+ return response;
16
+ }
17
+ catch (err) {
18
+ logger.error(`RPC request to [${this.subject}] failed: ${err.message}`);
19
+ throw err;
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,6 @@
1
+ import { Subjects } from "./subjects.js";
2
+ export interface RequestEvent {
3
+ subject: Subjects;
4
+ request: any;
5
+ response: any;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare enum Subjects {
2
+ AccountVerify = "account:verify",
3
+ AccountCreated = "account:created"
4
+ }
@@ -0,0 +1,5 @@
1
+ export var Subjects;
2
+ (function (Subjects) {
3
+ Subjects["AccountVerify"] = "account:verify";
4
+ Subjects["AccountCreated"] = "account:created";
5
+ })(Subjects || (Subjects = {}));
package/build/index.d.ts CHANGED
@@ -1 +1,9 @@
1
1
  export * from "./util/index.js";
2
+ export * from "./events/base/base-event.js";
3
+ export * from "./events/base/base-listener.js";
4
+ export * from "./events/base/base-publisher.js";
5
+ export * from "./events/base/subjects.js";
6
+ export * from "./events/account/account-created-event.js";
7
+ export * from "./events/base/request-event.js";
8
+ export * from "./events/base/base-request-publisher.js";
9
+ export * from "./events/base/base-request-listener.js";
package/build/index.js CHANGED
@@ -1 +1,9 @@
1
1
  export * from "./util/index.js";
2
+ export * from "./events/base/base-event.js";
3
+ export * from "./events/base/base-listener.js";
4
+ export * from "./events/base/base-publisher.js";
5
+ export * from "./events/base/subjects.js";
6
+ export * from "./events/account/account-created-event.js";
7
+ export * from "./events/base/request-event.js";
8
+ export * from "./events/base/base-request-publisher.js";
9
+ export * from "./events/base/base-request-listener.js";
@@ -0,0 +1,3 @@
1
+ import pino from "pino";
2
+ export declare const logger: pino.Logger<never, boolean>;
3
+ export declare const httpLogger: import("pino-http").HttpLogger<import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, never>;
@@ -0,0 +1,39 @@
1
+ import pino from "pino";
2
+ import { pinoHttp } from "pino-http";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from 'url';
6
+ import dotenv from "dotenv";
7
+ dotenv.config();
8
+ const isProd = process.env.APP_ENV == "production";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ // Ensure logs directory exists
12
+ const logDir = path.resolve(__dirname, "../logs");
13
+ if (isProd && !fs.existsSync(logDir)) {
14
+ fs.mkdirSync(logDir, { recursive: true });
15
+ }
16
+ const destination = isProd
17
+ ? pino.destination({ dest: path.join(logDir, "app.log"), sync: false }) // non-blocking file write
18
+ : undefined;
19
+ export const logger = pino({
20
+ level: "info",
21
+ timestamp: () => `,"time":"${new Date().toISOString()}"`,
22
+ ...(isProd
23
+ ? {} // No transport for production — raw JSON
24
+ : {
25
+ transport: {
26
+ target: "pino-pretty",
27
+ options: {
28
+ colorize: true,
29
+ translateTime: "SYS:standard",
30
+ ignore: "pid,hostname",
31
+ },
32
+ },
33
+ }),
34
+ }, destination // Only used in production
35
+ );
36
+ // HTTP request logger for Express
37
+ export const httpLogger = pinoHttp({
38
+ logger,
39
+ });
package/package.json CHANGED
@@ -1,47 +1,35 @@
1
1
  {
2
2
  "name": "@vortenixinnovations/flagra-common-module",
3
- "version": "1.0.58",
4
- "description": "common module of flagra",
3
+ "version": "1.0.60",
4
+ "description": "flagra.com common module",
5
+ "type": "module",
5
6
  "main": "./build/index.js",
6
7
  "types": "./build/index.d.ts",
7
8
  "files": [
8
- "/build/**/*"
9
+ "build/**/*"
9
10
  ],
10
- "publishConfig": {
11
- "access": "public"
12
- },
13
- "exports": {
14
- ".": {
15
- "types": "./build/index.d.ts",
16
- "import": "./build/index.js",
17
- "require": "./build/index.js",
18
- "default": "./build/index.js"
19
- },
20
- "./package.json": "./package.json"
21
- },
22
11
  "scripts": {
23
- "clean": "del-cli ./build/*",
12
+ "clean": "del ./build/*",
24
13
  "build": "npm run clean && tsc",
25
14
  "release": "npm version patch && npm run build && npm publish"
26
15
  },
16
+ "keywords": [],
17
+ "author": "midhunx01",
18
+ "license": "ISC",
19
+ "devDependencies": {
20
+ "@types/node": "^24.9.1",
21
+ "del-cli": "^5.1.0",
22
+ "typescript": "^5.6.2"
23
+ },
27
24
  "dependencies": {
28
- "@sinclair/typebox": "^0.34.41",
29
- "ajv": "^8.17.1",
30
- "ajv-errors": "^3.0.0",
31
- "ajv-formats": "^3.0.1",
32
- "class-validator": "^0.14.2",
25
+ "@types/express": "^5.0.5",
33
26
  "dotenv": "^17.2.3",
34
27
  "express": "^5.1.0",
35
- "pino": "^10.0.0",
28
+ "nats": "^2.29.3",
29
+ "node": "^22.21.0",
30
+ "node-nats-streaming": "^0.3.2",
31
+ "pino": "^10.1.0",
36
32
  "pino-http": "^11.0.0",
37
- "pino-pretty": "^13.0.0"
38
- },
39
- "devDependencies": {
40
- "@types/express": "^5.0.3",
41
- "@types/node": "^24.7.0",
42
- "del-cli": "^7.0.0",
43
- "typescript": "^5.9.3"
44
- },
45
- "author": "midhunx01",
46
- "license": "ISC"
33
+ "pino-pretty": "^13.1.2"
34
+ }
47
35
  }