diff --git a/package.json b/package.json index 2ac9ccc..aa1a2bc 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,11 @@ "@types/node": "^25.0.10", "prettier": "^3.8.1", "typescript": "^5.9.3" + }, + "dependencies": { + "@nestjs/common": "^11.1.16", + "@nestjs/config": "^4.0.3", + "@nestjs/microservices": "^11.1.16", + "@teacinema/contracts": "^1.1.3" } } diff --git a/src/lib/grpc/constants/grpc.constants.ts b/src/lib/grpc/constants/grpc.constants.ts new file mode 100644 index 0000000..dac0ac1 --- /dev/null +++ b/src/lib/grpc/constants/grpc.constants.ts @@ -0,0 +1 @@ +export const GRPC_CLIENT_PREFIX = 'GRPC_CLIENT' diff --git a/src/lib/grpc/decorators/index.ts b/src/lib/grpc/decorators/index.ts new file mode 100644 index 0000000..4dbfebc --- /dev/null +++ b/src/lib/grpc/decorators/index.ts @@ -0,0 +1 @@ +export * from './inject-grpc-client' diff --git a/src/lib/grpc/decorators/inject-grpc-client.ts b/src/lib/grpc/decorators/inject-grpc-client.ts new file mode 100644 index 0000000..ea4c6d0 --- /dev/null +++ b/src/lib/grpc/decorators/inject-grpc-client.ts @@ -0,0 +1,4 @@ +import { Inject } from '@nestjs/common' +import { GRPC_CLIENT_PREFIX } from '../constants/grpc.constants' + +export const InjectGrpc = (name: string) => Inject(`${GRPC_CLIENT_PREFIX}_${name}`) diff --git a/src/lib/grpc/factory/grpc-client.factory.ts b/src/lib/grpc/factory/grpc-client.factory.ts new file mode 100644 index 0000000..ba5b91a --- /dev/null +++ b/src/lib/grpc/factory/grpc-client.factory.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common' +import { + ClientGrpc, + ClientProxyFactory, + Transport +} from '@nestjs/microservices' + +@Injectable() +export class GrpcClientFactory { + private clients = new Map() + + public createClient(options: { + package: string + protoPath: string + url: string + }) { + return ClientProxyFactory.create({ + transport: Transport.GRPC, + options + }) as ClientGrpc + } + + public register(token: string, client: ClientGrpc) { + this.clients.set(token, client) + } + + public getClient(token: string) { + const client = this.clients.get(token) as T; + + if (!client) { + throw new Error(`Grpc client ${token} not found`) + } + + return client; + } + + + +} diff --git a/src/lib/grpc/grpc.module.ts b/src/lib/grpc/grpc.module.ts new file mode 100644 index 0000000..12abadf --- /dev/null +++ b/src/lib/grpc/grpc.module.ts @@ -0,0 +1,44 @@ +import { DynamicModule, Module } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' + +import { GRPC_CLIENT_PREFIX } from './constants/grpc.constants' +import { GrpcClientFactory } from './factory/grpc-client.factory' +import { GRPC_CLIENTS } from './registry/grpc.registry' + +@Module({}) +export class GrpcModule { + public static register( + clients: Array + ): DynamicModule { + return { + module: GrpcModule, + providers: [ + GrpcClientFactory, + ...clients.map(token => { + const cfg = GRPC_CLIENTS[token] + + return { + provide: `${GRPC_CLIENT_PREFIX}_${token}`, + useFactory: (factory: GrpcClientFactory, config: ConfigService) => { + const url = config.getOrThrow(cfg.env) + const client = factory.createClient({ + url, + package: cfg.package, + protoPath: cfg.protoPath + }) + + factory.register(token, client) + + return client + }, + inject: [ConfigService, GrpcClientFactory] + } + }) + ], + exports: [ + GrpcClientFactory, + ...clients.map(token => `${GRPC_CLIENT_PREFIX}_${token}`) + ] + } + } +} diff --git a/src/lib/grpc/index.ts b/src/lib/grpc/index.ts new file mode 100644 index 0000000..01622ac --- /dev/null +++ b/src/lib/grpc/index.ts @@ -0,0 +1,2 @@ +export * from './decorators' +export * from './grpc.module' diff --git a/src/lib/grpc/registry/grpc.registry.ts b/src/lib/grpc/registry/grpc.registry.ts new file mode 100644 index 0000000..fc8b115 --- /dev/null +++ b/src/lib/grpc/registry/grpc.registry.ts @@ -0,0 +1,19 @@ +import { PROTO_PATHS } from '@teacinema/contracts' + +export const GRPC_CLIENTS = { + AUTH_PACKAGE: { + package: 'auth.v1', + protoPath: PROTO_PATHS.AUTH, + env: 'AUTH_GRPC_URL' + }, + ACCOUNT_PACKAGE: { + package: 'auth.v1', + protoPath: PROTO_PATHS.ACCOUNT, + env: 'AUTH_GRPC_URL' + }, + USERS_PACKAGE: { + package: 'users.v1', + protoPath: PROTO_PATHS.USERS, + env: 'USERS_GRPC_URL' + } +} as const; diff --git a/src/lib/index.ts b/src/lib/index.ts index 0205761..f3663a8 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,3 @@ -// test +export * from './grpc' export * from './utils' export * from './enums' diff --git a/yarn.lock b/yarn.lock index f028afb..4ca4582 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,6 +74,16 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@borewit/text-codec@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.2.1.tgz#5d171538907a8cb395fdc2eb5e8f7947d96c7f2f" + integrity sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw== + +"@bufbuild/protobuf@^2.0.0", "@bufbuild/protobuf@^2.10.2": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.11.0.tgz#3ec3985c9074b23aea337957225fe15a0e845f8e" + integrity sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ== + "@jridgewell/gen-mapping@^0.3.12": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" @@ -100,6 +110,49 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@lukeed/csprng@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@nestjs/common@^11.1.16": + version "11.1.16" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.16.tgz#1f4061f66d7ae63e407f8d2be2aaeaf83097332b" + integrity sha512-JSIeW+USuMJkkcNbiOdcPkVCeI3TSnXstIVEPpp3HiaKnPRuSbUUKm9TY9o/XpIcPHWUOQItAtC5BiAwFdVITQ== + dependencies: + uid "2.0.2" + file-type "21.3.0" + iterare "1.2.1" + load-esm "1.0.3" + tslib "2.8.1" + +"@nestjs/config@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.3.tgz#bb24a5f64e4e3ccf6fbf9e02f63303d14d7f308e" + integrity sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ== + dependencies: + dotenv "17.2.3" + dotenv-expand "12.0.3" + lodash "4.17.23" + +"@nestjs/microservices@^11.1.12", "@nestjs/microservices@^11.1.16": + version "11.1.16" + resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-11.1.16.tgz#6f445c44adcee88af983830e0667c93ccefe2869" + integrity sha512-eG/ArIq0UJyR3i/GTYuApA4OZylhuLGacVaVT9mMxQgT7ZTpp5CZgOwLNdcUUdOS6qypK3waG1m2AC54xzdf0Q== + dependencies: + iterare "1.2.1" + tslib "2.8.1" + +"@teacinema/contracts@^1.1.3": + version "1.1.3" + resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.1.3/contracts-1.1.3.tgz#e08600a8dc09977dae35654d372e29e411029e33" + integrity sha512-kwcSh2YIxmoBJQ56L+tQpGj12ySwNwf9KRNcUqhPffLPO60PR5aiKA2eN2SfKc23TKp0YoSkdC9LWeor0a9Ysg== + dependencies: + "@nestjs/microservices" "^11.1.12" + protoc "33.4.0" + rxjs "^7.8.2" + ts-proto "2.11.0" + "@teacinema/core@^1.0.9": version "1.0.9" resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcore/-/1.0.9/core-1.0.9.tgz#deec0675c6cc03fe2511a6b8dc5d6b6763998708" @@ -107,6 +160,19 @@ dependencies: "@trivago/prettier-plugin-sort-imports" "^5.2.2" +"@tokenizer/inflate@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.4.1.tgz#fa6cdb8366151b3cc8426bf9755c1ea03a2fba08" + integrity sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA== + dependencies: + debug "^4.4.3" + token-types "^6.1.1" + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@trivago/prettier-plugin-sort-imports@^5.2.2": version "5.2.2" resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz#38983f0b83490a0a7d974a6f1e409fb4bf678d02" @@ -126,13 +192,67 @@ dependencies: undici-types "~7.16.0" -debug@^4.3.1: +case-anything@^2.1.13: + version "2.1.13" + resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.13.tgz#0cdc16278cb29a7fcdeb072400da3f342ba329e9" + integrity sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng== + +debug@^4.3.1, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +dotenv-expand@12.0.3: + version "12.0.3" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-12.0.3.tgz#6323ceca51ca0c1b1f0055e2aba39c79781739a6" + integrity sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA== + dependencies: + dotenv "^16.4.5" + +dotenv@17.2.3: + version "17.2.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" + integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== + +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dprint-node@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/dprint-node/-/dprint-node-1.0.8.tgz#a02470722d8208a7d7eb3704328afda1d6758625" + integrity sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg== + dependencies: + detect-libc "^1.0.3" + +file-type@21.3.0: + version "21.3.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-21.3.0.tgz#03334acfe59cc22f72fce2a1327c5a1190f0b7de" + integrity sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA== + dependencies: + "@tokenizer/inflate" "^0.4.1" + strtok3 "^10.3.4" + token-types "^6.1.1" + uint8array-extras "^1.4.0" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + javascript-natural-sort@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" @@ -148,7 +268,12 @@ jsesc@^3.0.2: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -lodash@^4.17.21: +load-esm@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/load-esm/-/load-esm-1.0.3.tgz#2073afe3da63902c323e80d9f135c301173ac92c" + integrity sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA== + +lodash@4.17.23, lodash@^4.17.21: version "4.17.23" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== @@ -168,11 +293,80 @@ prettier@^3.8.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== +protoc@33.4.0: + version "33.4.0" + resolved "https://registry.yarnpkg.com/protoc/-/protoc-33.4.0.tgz#4a288b59f5f50b9a2c4bbf345276357e59dd6f10" + integrity sha512-dC1SJKbn4f60onsAMrfiPMygfSz4w/tPGHtPCqMSa5CLfHocJXkHjsvXeuuhjSPAJ8uUc8rfUvs0Hy9nmKIOhA== + +rxjs@^7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +strtok3@^10.3.4: + version "10.3.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.4.tgz#793ebd0d59df276a085586134b73a406e60be9c1" + integrity sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg== + dependencies: + "@tokenizer/token" "^0.3.0" + +token-types@^6.1.1: + version "6.1.2" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.1.2.tgz#18d0fd59b996d421f9f83914d6101c201bd08129" + integrity sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww== + dependencies: + "@borewit/text-codec" "^0.2.1" + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +ts-poet@^6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-6.12.0.tgz#f28eb91778fdff8e1b264600c7ddddb40cd39d84" + integrity sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA== + dependencies: + dprint-node "^1.0.8" + +ts-proto-descriptors@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-2.1.0.tgz#0c60525ea94748f7b3f394dc10d6eb8eeac4fe39" + integrity sha512-S5EZYEQ6L9KLFfjSRpZWDIXDV/W7tAj8uW7pLsihIxyr62EAVSiKuVPwE8iWnr849Bqa53enex1jhDUcpgquzA== + dependencies: + "@bufbuild/protobuf" "^2.0.0" + +ts-proto@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-2.11.0.tgz#4b24b3a39893441d97e8a875e7d7c509a7bb4521" + integrity sha512-Emqt0T/Wf74aT+IAoOqEkWWhxVOVF6QwVW4CBIyL3RRQORJpaQDVHy/Ykg9psQH/Zo6XMpT9KBRjpkVSZ0vXkQ== + dependencies: + "@bufbuild/protobuf" "^2.10.2" + case-anything "^2.1.13" + ts-poet "^6.12.0" + ts-proto-descriptors "2.1.0" + +tslib@2.8.1, tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + typescript@^5.9.3: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +uid@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" + integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + dependencies: + "@lukeed/csprng" "^1.0.0" + +uint8array-extras@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86" + integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== + undici-types@~7.16.0: version "7.16.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"