diff --git a/.pnp.cjs b/.pnp.cjs index d2ba91bb..31774170 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -74,6 +74,10 @@ const RAW_RUNTIME_STATE = "name": "@atls/nestjs-hydra",\ "reference": "workspace:packages/nestjs-hydra"\ },\ + {\ + "name": "@atls/nestjs-kafka",\ + "reference": "workspace:packages/nestjs-kafka"\ + },\ {\ "name": "@atls/nestjs-keto",\ "reference": "workspace:packages/nestjs-keto"\ @@ -131,6 +135,7 @@ const RAW_RUNTIME_STATE = ["@atls/nestjs-grpc-playground", ["virtual:51dcb3fe3f47cfb6224dcab5ae8c7e50b5682560d9d6abdf449cb9fd665402b311244dcf35352838e6165320fb5bb25ae45a36f802c2a93ab14064614bee36b9#workspace:packages/nestjs-grpc-playground", "workspace:packages/nestjs-grpc-playground"]],\ ["@atls/nestjs-grpc-reflection", ["virtual:1b645f34e708f7901d1bd6b5b2d1038edc84294a49890304b13123db78ac85a009762dcf36af72cc3f76303827188f19bee713c279b8ca82cc7dee72e3732570#workspace:packages/nestjs-grpc-reflection", "virtual:51dcb3fe3f47cfb6224dcab5ae8c7e50b5682560d9d6abdf449cb9fd665402b311244dcf35352838e6165320fb5bb25ae45a36f802c2a93ab14064614bee36b9#workspace:packages/nestjs-grpc-reflection", "virtual:77887786a24289fa840c9acd370d634accbe79bcf317ecf5401844ffff73b8a593879dd9cce463873637e6414a631dfdb1a2473704bf332d823bcfffac8c2469#workspace:packages/nestjs-grpc-reflection", "virtual:80857f29dff653ed1b21e1b78c415c79278e1a3708fec8563133922c0b3b287990dd6895d3d76575acec38e5f7e5fa8985f270e2d46a7eb121f744d431d0761f#workspace:packages/nestjs-grpc-reflection", "workspace:packages/nestjs-grpc-reflection"]],\ ["@atls/nestjs-hydra", ["workspace:packages/nestjs-hydra"]],\ + ["@atls/nestjs-kafka", ["workspace:packages/nestjs-kafka"]],\ ["@atls/nestjs-keto", ["workspace:packages/nestjs-keto"]],\ ["@atls/nestjs-kratos", ["workspace:packages/nestjs-kratos"]],\ ["@atls/nestjs-logger", ["workspace:packages/nestjs-logger"]],\ @@ -1134,6 +1139,22 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@atls/nestjs-kafka", [\ + ["workspace:packages/nestjs-kafka", {\ + "packageLocation": "./packages/nestjs-kafka/",\ + "packageDependencies": [\ + ["@atls/nestjs-kafka", "workspace:packages/nestjs-kafka"],\ + ["@atls/logger", "npm:0.0.2"],\ + ["@nestjs/common", "virtual:a83c01fc4cba5ca49a56474f02d0e2a42b67fd84218e0ed8446c312a68b5dcda210438f3221534d1b20254ee4cad2d7a7704d91b0774cb67fd2dcafbea6e550e#npm:10.4.3"],\ + ["@nestjs/core", "virtual:a83c01fc4cba5ca49a56474f02d0e2a42b67fd84218e0ed8446c312a68b5dcda210438f3221534d1b20254ee4cad2d7a7704d91b0774cb67fd2dcafbea6e550e#npm:10.4.3"],\ + ["camelcase", "npm:8.0.0"],\ + ["kafkajs", "npm:2.2.4"],\ + ["reflect-metadata", "npm:0.1.14"],\ + ["rxjs", "npm:7.8.1"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@atls/nestjs-keto", [\ ["workspace:packages/nestjs-keto", {\ "packageLocation": "./packages/nestjs-keto/",\ @@ -13055,6 +13076,13 @@ const RAW_RUNTIME_STATE = ["camelcase", "npm:6.3.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:8.0.0", {\ + "packageLocation": "../.yarn/berry/cache/camelcase-npm-8.0.0-a3fa03dcc3-10c0.zip/node_modules/camelcase/",\ + "packageDependencies": [\ + ["camelcase", "npm:8.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["caniuse-lite", [\ @@ -18079,6 +18107,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["kafkajs", [\ + ["npm:2.2.4", {\ + "packageLocation": "../.yarn/berry/cache/kafkajs-npm-2.2.4-8837bee290-10c0.zip/node_modules/kafkajs/",\ + "packageDependencies": [\ + ["kafkajs", "npm:2.2.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["keyv", [\ ["npm:4.5.4", {\ "packageLocation": "../.yarn/berry/cache/keyv-npm-4.5.4-4c8e2cf7f7-10c0.zip/node_modules/keyv/",\ diff --git a/packages/nestjs-kafka/logger/index.ts b/packages/nestjs-kafka/logger/index.ts new file mode 100644 index 00000000..8d2f7a3c --- /dev/null +++ b/packages/nestjs-kafka/logger/index.ts @@ -0,0 +1 @@ +export * from './kafka-log.creator.js' diff --git a/packages/nestjs-kafka/logger/kafka-log.creator.ts b/packages/nestjs-kafka/logger/kafka-log.creator.ts new file mode 100644 index 00000000..f605cedc --- /dev/null +++ b/packages/nestjs-kafka/logger/kafka-log.creator.ts @@ -0,0 +1,25 @@ +import type { LogEntry } from 'kafkajs' + +import { Logger } from '@atls/logger' +import { logLevel } from 'kafkajs' +import camelcase from 'camelcase' + +export const kafkaLogCreator = (): ((logEntry: LogEntry) => void) => { + const kafkaLogger = new Logger('kafka') + + return ({ namespace, level, log: { message, ...extra } }: LogEntry): void => { + const logger = namespace + ? kafkaLogger.child(camelcase(namespace, { pascalCase: true })) + : kafkaLogger + + if (level === logLevel.ERROR || level === logLevel.NOTHING) { + logger.error(message, extra) + } else if (level === logLevel.WARN) { + logger.warn(message, extra) + } else if (level === logLevel.INFO) { + logger.info(message, extra) + } else if (level === logLevel.DEBUG) { + logger.debug(message, extra) + } + } +} diff --git a/packages/nestjs-kafka/package.json b/packages/nestjs-kafka/package.json new file mode 100644 index 00000000..3fdbdc77 --- /dev/null +++ b/packages/nestjs-kafka/package.json @@ -0,0 +1,48 @@ +{ + "name": "@atls/nestjs-kafka", + "version": "0.0.0", + "license": "BSD-3-Clause", + "type": "module", + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts" + }, + "main": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "yarn library build", + "prepack": "yarn run build", + "postpack": "rm -rf dist" + }, + "dependencies": { + "@atls/logger": "^0.0.2", + "camelcase": "^8.0.0", + "kafkajs": "^2.2.4" + }, + "devDependencies": { + "@nestjs/common": "^10.0.5", + "@nestjs/core": "^10.0.5", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^10", + "@nestjs/core": "^10", + "reflect-metadata": "^0.1", + "rxjs": "^7" + }, + "publishConfig": { + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "dist/index.js", + "typings": "dist/index.d.ts" + } +} diff --git a/packages/nestjs-kafka/src/index.ts b/packages/nestjs-kafka/src/index.ts new file mode 100644 index 00000000..408d807f --- /dev/null +++ b/packages/nestjs-kafka/src/index.ts @@ -0,0 +1,3 @@ +export * from 'kafkajs' + +export * from './module/index.js' diff --git a/packages/nestjs-kafka/src/module/index.ts b/packages/nestjs-kafka/src/module/index.ts new file mode 100644 index 00000000..44719983 --- /dev/null +++ b/packages/nestjs-kafka/src/module/index.ts @@ -0,0 +1,4 @@ +export * from './kafka.module.constants.js' +export * from './kafka.config-factory.js' +export * from './kafka.factory.js' +export * from './kafka.module.js' diff --git a/packages/nestjs-kafka/src/module/kafka.config-factory.ts b/packages/nestjs-kafka/src/module/kafka.config-factory.ts new file mode 100644 index 00000000..612a37f8 --- /dev/null +++ b/packages/nestjs-kafka/src/module/kafka.config-factory.ts @@ -0,0 +1,26 @@ +import type { KafkaConfig } from 'kafkajs' + +import { Inject } from '@nestjs/common' +import { Injectable } from '@nestjs/common' + +import { KAFKA_MODULE_OPTIONS_CLIENT_ID } from './kafka.module.constants.js' +import { KAFKA_MODULE_OPTIONS_BROKERS } from './kafka.module.constants.js' + +@Injectable() +export class KafkaConfigFactory { + constructor( + @Inject(KAFKA_MODULE_OPTIONS_CLIENT_ID) + private readonly clientId: string, + @Inject(KAFKA_MODULE_OPTIONS_BROKERS) + private readonly brokers: Array + ) {} + + createKafkaOptions(): KafkaConfig { + return { + clientId: this.clientId || process.env.KAFKA_CLIENT_ID, + brokers: + this.brokers || + (process.env.KAFKA_BROKERS ? process.env.KAFKA_BROKERS.split(',') : ['localhost:29092']), + } + } +} diff --git a/packages/nestjs-kafka/src/module/kafka.factory.ts b/packages/nestjs-kafka/src/module/kafka.factory.ts new file mode 100644 index 00000000..9cdbedc9 --- /dev/null +++ b/packages/nestjs-kafka/src/module/kafka.factory.ts @@ -0,0 +1,20 @@ +import type { KafkaConfig } from 'kafkajs' + +import { Injectable } from '@nestjs/common' +import { Kafka } from 'kafkajs' + +import { KafkaConfigFactory } from './kafka.config-factory.js' +import { kafkaLogCreator } from '../logger/index.js' + +@Injectable() +export class KafkaFactory { + constructor(private readonly configFactory: KafkaConfigFactory) {} + + create(options: Partial = {}): Kafka { + return new Kafka({ + logCreator: kafkaLogCreator, + ...this.configFactory.createKafkaOptions(), + ...options, + }) + } +} diff --git a/packages/nestjs-kafka/src/module/kafka.module.constants.ts b/packages/nestjs-kafka/src/module/kafka.module.constants.ts new file mode 100644 index 00000000..84471477 --- /dev/null +++ b/packages/nestjs-kafka/src/module/kafka.module.constants.ts @@ -0,0 +1,3 @@ +export const KAFKA_MODULE_OPTIONS_GROUP_ID = Symbol('kafka-module-options-group-id') +export const KAFKA_MODULE_OPTIONS_CLIENT_ID = Symbol('kafka-module-options-client-id') +export const KAFKA_MODULE_OPTIONS_BROKERS = Symbol('kafka-module-options-brokers') diff --git a/packages/nestjs-kafka/src/module/kafka.module.ts b/packages/nestjs-kafka/src/module/kafka.module.ts new file mode 100644 index 00000000..28921f3a --- /dev/null +++ b/packages/nestjs-kafka/src/module/kafka.module.ts @@ -0,0 +1,31 @@ +import type { DynamicModule } from '@nestjs/common' +import type { KafkaConfig } from 'kafkajs' + +import { Module } from '@nestjs/common' + +import { KAFKA_MODULE_OPTIONS_CLIENT_ID } from './kafka.module.constants.js' +import { KAFKA_MODULE_OPTIONS_BROKERS } from './kafka.module.constants.js' +import { KafkaConfigFactory } from './kafka.config-factory.js' +import { KafkaFactory } from './kafka.factory.js' + +@Module({}) +export class KafkaModule { + static register(options: Partial = {}): DynamicModule { + return { + module: KafkaModule, + providers: [ + KafkaConfigFactory, + KafkaFactory, + { + provide: KAFKA_MODULE_OPTIONS_BROKERS, + useValue: options.brokers, + }, + { + provide: KAFKA_MODULE_OPTIONS_CLIENT_ID, + useValue: options.clientId, + }, + ], + exports: [KafkaConfigFactory, KafkaFactory], + } + } +} diff --git a/yarn.lock b/yarn.lock index 6159dcc8..8dc8b090 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,7 +283,7 @@ __metadata: languageName: node linkType: hard -"@atls/logger@npm:0.0.2": +"@atls/logger@npm:0.0.2, @atls/logger@npm:^0.0.2": version: 0.0.2 resolution: "@atls/logger@npm:0.0.2" dependencies: @@ -621,6 +621,25 @@ __metadata: languageName: unknown linkType: soft +"@atls/nestjs-kafka@workspace:packages/nestjs-kafka": + version: 0.0.0-use.local + resolution: "@atls/nestjs-kafka@workspace:packages/nestjs-kafka" + dependencies: + "@atls/logger": "npm:^0.0.2" + "@nestjs/common": "npm:^10.0.5" + "@nestjs/core": "npm:^10.0.5" + camelcase: "npm:^8.0.0" + kafkajs: "npm:^2.2.4" + reflect-metadata: "npm:^0.1.13" + rxjs: "npm:^7.8.1" + peerDependencies: + "@nestjs/common": ^10 + "@nestjs/core": ^10 + reflect-metadata: ^0.1 + rxjs: ^7 + languageName: unknown + linkType: soft + "@atls/nestjs-keto@workspace:packages/nestjs-keto": version: 0.0.0-use.local resolution: "@atls/nestjs-keto@workspace:packages/nestjs-keto" @@ -7833,6 +7852,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^8.0.0": + version: 8.0.0 + resolution: "camelcase@npm:8.0.0" + checksum: 10c0/56c5fe072f0523c9908cdaac21d4a3b3fb0f608fb2e9ba90a60e792b95dd3bb3d1f3523873ab17d86d146e94171305f73ef619e2f538bd759675bc4a14b4bff3 + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001646": version: 1.0.30001660 resolution: "caniuse-lite@npm:1.0.30001660" @@ -12039,6 +12065,13 @@ __metadata: languageName: node linkType: hard +"kafkajs@npm:^2.2.4": + version: 2.2.4 + resolution: "kafkajs@npm:2.2.4" + checksum: 10c0/6eece1f682a257955f647b6142430e87681d11bf01e9ebe0a73b4bc0653b8f970c48ba72a3b25fdbb4205cfbb36e5ef93d954b8f8c5f7767ae2b4693bdec9d88 + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4"