├── packages ├── mdc │ ├── .gitignore │ ├── .prettierrc.json │ ├── .releaserc.json │ ├── .eslintrc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── index.ts │ │ │ │ ├── mdc.module.ts │ │ │ │ └── mdc.service.ts │ │ └── test │ │ │ └── ts │ │ │ └── index.spec.ts │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── README.md │ ├── LICENSE │ ├── package.json │ └── jest.config.json ├── common │ ├── .gitignore │ ├── .prettierrc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── index.ts │ │ │ │ ├── port │ │ │ │ └── index.ts │ │ │ │ └── request-size │ │ │ │ └── index.ts │ │ └── test │ │ │ └── ts │ │ │ ├── request-size.spec.ts │ │ │ └── port.spec.ts │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── README.md │ └── package.json ├── config │ ├── .gitignore │ ├── .prettierrc.json │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── index.ts │ │ │ │ ├── config.module.ts │ │ │ │ ├── uniconfig-json-schema-validation-plugin │ │ │ │ ├── resolve-schema-path.ts │ │ │ │ └── index.ts │ │ │ │ └── config.service.ts │ │ └── test │ │ │ └── ts │ │ │ ├── config │ │ │ ├── app-config.schema.json │ │ │ └── test.json │ │ │ ├── config.service.spec.ts │ │ │ ├── uniconfig-json-schema-validation-plugin │ │ │ ├── resolve-schema-path.spec.ts │ │ │ └── index.spec.ts │ │ │ └── index.spec.ts │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ └── jest.config.json ├── consul │ ├── .gitignore │ ├── .prettierrc.json │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── index.ts │ │ │ │ ├── consul.module.ts │ │ │ │ └── consul.service.ts │ │ └── test │ │ │ └── ts │ │ │ ├── mock │ │ │ └── fakeConsulDiscovery.ts │ │ │ ├── config │ │ │ └── test.json │ │ │ └── index.spec.ts │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── package.json │ └── README.md ├── facade │ ├── .gitignore │ ├── .prettierrc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ └── index.ts │ │ └── test │ │ │ └── ts │ │ │ └── index.spec.ts │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── README.md │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ └── jest.config.json ├── metric │ ├── .gitignore │ ├── .prettierrc.json │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── graphite.servise.interface.ts │ │ │ │ ├── utills.ts │ │ │ │ ├── metric.service.interface.ts │ │ │ │ ├── graphite.service.ts │ │ │ │ ├── decorators │ │ │ │ ├── metered.decorator.ts │ │ │ │ ├── error.decorator.ts │ │ │ │ └── request-rate.decorator.ts │ │ │ │ ├── index.ts │ │ │ │ ├── get-node-metrics.ts │ │ │ │ ├── metric.module.ts │ │ │ │ └── metric.service.ts │ │ └── test │ │ │ └── ts │ │ │ ├── config │ │ │ └── test.json │ │ │ └── metric.module.spec.ts │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── package.json │ └── README.md ├── logger │ ├── .prettierrc.json │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── index.ts │ │ │ │ ├── logger.pipe.ts │ │ │ │ ├── app.pipe.ts │ │ │ │ ├── masker.ts │ │ │ │ ├── logger.module.ts │ │ │ │ ├── logger-meta.pipe.ts │ │ │ │ ├── logger.service.ts │ │ │ │ └── winston.ts │ │ └── test │ │ │ └── ts │ │ │ ├── app.pipe.spec.ts │ │ │ ├── logger.pipe.spec.ts │ │ │ └── winston.spec.ts │ ├── .releaserc.json │ ├── .eslintrc.json │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── package.json │ └── README.md ├── svc-info │ ├── .prettierrc.json │ ├── .gitignore │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── index.ts │ │ │ │ ├── svc-info.module.ts │ │ │ │ └── svc-info.controller.ts │ │ └── test │ │ │ └── ts │ │ │ └── mock │ │ │ └── package.json │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── package.json │ └── README.md ├── thrift │ ├── .prettierrc.json │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── src │ │ ├── main │ │ │ └── ts │ │ │ │ ├── index.ts │ │ │ │ ├── thrift.server.ts │ │ │ │ ├── thrift.module.ts │ │ │ │ ├── thrift.decorators.ts │ │ │ │ └── interfaces.ts │ │ └── test │ │ │ └── ts │ │ │ ├── mock │ │ │ ├── fakeConsulDiscovery9091.ts │ │ │ ├── fakeConsulDiscovery9090.ts │ │ │ ├── client.cjs │ │ │ ├── server.cjs │ │ │ └── gen-nodejs │ │ │ │ └── shared_types.cjs │ │ │ ├── thrift.module.spec.ts │ │ │ ├── config │ │ │ └── test.json │ │ │ ├── thrift.decorators.spec.ts │ │ │ └── thrift.client.spec.ts │ ├── .gitignore │ ├── tsconfig.esm.json │ ├── tsconfig.cjs.json │ ├── LICENSE │ ├── tsconfig.json │ ├── package.json │ ├── jest.config.json │ └── README.md └── connection-provider │ ├── .gitignore │ ├── .prettierrc.json │ ├── .eslintrc.json │ ├── .releaserc.json │ ├── tsconfig.esm.json │ ├── src │ ├── main │ │ └── ts │ │ │ ├── index.ts │ │ │ ├── connection-provider.module.ts │ │ │ ├── connection-provider.service.ts │ │ │ └── interfaces.ts │ └── test │ │ └── ts │ │ └── index.spec.ts │ ├── tsconfig.cjs.json │ ├── tsconfig.json │ ├── LICENSE │ ├── jest.config.json │ ├── package.json │ ├── README.md │ └── CHANGELOG.md ├── renovate.json ├── tsconfig.node-test.json ├── .yarnrc.yml ├── .gitignore ├── package.json ├── jest.config.json ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── LICENSE └── README.md /packages/mdc/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/common/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/config/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/consul/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/facade/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/mdc/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/metric/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/common/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/config/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/consul/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/facade/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/logger/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/metric/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/svc-info/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/thrift/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/facade/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export const foo = 'bar' 2 | -------------------------------------------------------------------------------- /packages/connection-provider/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /packages/connection-provider/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-qiwi" 2 | -------------------------------------------------------------------------------- /packages/svc-info/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | src/test/ts/log 4 | -------------------------------------------------------------------------------- /packages/facade/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi" 3 | } 4 | -------------------------------------------------------------------------------- /packages/svc-info/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi" 3 | } 4 | -------------------------------------------------------------------------------- /packages/logger/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | src/test/ts/log 4 | somepath 5 | -------------------------------------------------------------------------------- /packages/common/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './port' 2 | export * from './request-size' 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "rangeStrategy": "replace" 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.node-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | enableGlobalCache: true 4 | 5 | yarnPath: .yarn/releases/yarn-4.0.0-rc.13.cjs 6 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { IPipe } from '@qiwi/logwrap' 2 | 3 | export type TLoggerPipe = IPipe 4 | -------------------------------------------------------------------------------- /packages/mdc/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/mdc", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/common/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/common/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/common", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/config/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/config/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/config", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/consul/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/consul/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/consul", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/facade/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/facade", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/logger/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/logger", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/mdc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/metric/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/metric/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/metric", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/thrift/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/thrift/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/thrift", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/svc-info/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/svc-info", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/graphite.servise.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IGraphiteService { 2 | sendMetric(metrics: Record): Promise 3 | } 4 | -------------------------------------------------------------------------------- /packages/connection-provider/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/svc-info/src/main/ts/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ISvcInfoModuleOpts { 2 | path?: string 3 | packagePath?: string 4 | package?: Record 5 | } 6 | -------------------------------------------------------------------------------- /packages/connection-provider/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghPages": "gh-pages target/docs packages/connection-provider", 3 | "changelog": "changelog", 4 | "npmFetch": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/svc-info/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export { SvcInfoController } from './svc-info.controller' 2 | export { SvcInfoModule } from './svc-info.module' 3 | export * from './interfaces' 4 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/utills.ts: -------------------------------------------------------------------------------- 1 | export const isPromise = (value: any) => 2 | value instanceof Promise || 3 | (typeof value?.then === 'function' && typeof value?.catch === 'function') 4 | -------------------------------------------------------------------------------- /packages/consul/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export { ConsulModule } from './consul.module' 2 | export { ConsulService } from './consul.service' 3 | export type { IConsulService } from './consul.service' 4 | -------------------------------------------------------------------------------- /packages/logger/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-qiwi", 3 | "rules": { 4 | "@typescript-eslint/ban-ts-comment": "off", 5 | "unicorn/consistent-function-scoping": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/thrift/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './thrift.module' 2 | export * from './thrift.client' 3 | export * from './thrift.server' 4 | export * from './thrift.decorators' 5 | export * from './interfaces' 6 | -------------------------------------------------------------------------------- /packages/facade/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise 2 | *Facade* 3 | 4 | ## Install 5 | ```shell script 6 | yarn add @qiwi/nestjs-enterprise 7 | ``` 8 | 9 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/facade/) 10 | -------------------------------------------------------------------------------- /packages/mdc/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | export { mdc, validator, logger, context as clscxt } from '@qiwi/mware' 3 | export { MdcModule } from './mdc.module' 4 | export { MdcService } from './mdc.service' 5 | export type { IMdcService } from './mdc.service' 6 | -------------------------------------------------------------------------------- /packages/facade/src/test/ts/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import { foo } from '../../main/ts' 5 | 6 | describe('', () => { 7 | it('', () => { 8 | equal(foo, 'bar') 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export { LoggerModule } from './logger.module' 2 | export { LoggerService } from './logger.service' 3 | export { createMetaPipe } from './logger-meta.pipe' 4 | export { masker, maskerLoggerPipeFactory } from './masker' 5 | export * from './interfaces' 6 | -------------------------------------------------------------------------------- /packages/config/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export { ConfigModule } from './config.module' 2 | export { 3 | resolveConfigPath, 4 | DEFAULT_KUBE_CONFIG_PATH, 5 | DEFAULT_LOCAL_CONFIG_PATH, 6 | ConfigService, 7 | configServiceFactory, 8 | } from './config.service' 9 | export type { IConfigService } from './config.service' 10 | -------------------------------------------------------------------------------- /packages/mdc/src/main/ts/mdc.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common' 2 | 3 | import { MdcService } from './mdc.service' 4 | 5 | @Global() 6 | @Module({ 7 | controllers: [], 8 | providers: [{ provide: 'IMdc', useClass: MdcService }], 9 | exports: ['IMdc'], 10 | }) 11 | export class MdcModule {} 12 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/config/app-config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "name": { 5 | "type": "string" 6 | }, 7 | "local": { 8 | "type": "string" 9 | }, 10 | "version": { 11 | "type": "string" 12 | } 13 | }, 14 | "required": ["name", "version"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/metric.service.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IMetricService { 2 | histogram(metricName: string): { 3 | update(value: number): void 4 | } 5 | 6 | meter(metricName: string): { 7 | update(value?: number): void 8 | } 9 | 10 | timer(metricName: string): { 11 | update(value?: number): void 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "name": "test-name-app", 4 | "local": "$env:LOCAL", 5 | "version": "$pkg:version" 6 | }, 7 | "sources": { 8 | "env": { 9 | "pipeline": "env" 10 | }, 11 | "host": { 12 | "pipeline": "ip" 13 | }, 14 | "pkg": { 15 | "pipeline": "pkg" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mdc/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/common/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/config/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/metric/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/svc-info/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/thrift/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | 4 | # misc 5 | 6 | .DS_Store 7 | /.idea 8 | 9 | # local 10 | 11 | *.local 12 | 13 | # yarn 14 | 15 | /.pnp.* 16 | /.yarn/cache 17 | /.yarn/unplugged 18 | /.yarn/install-state.gz 19 | /yarn-error.log 20 | 21 | # open-ssl 22 | 23 | *.ca 24 | *.crt 25 | *.csr 26 | *.der 27 | *.kdb 28 | *.org 29 | *.p12 30 | *.pem 31 | *.rnd 32 | *.ssleay 33 | *.smime 34 | -------------------------------------------------------------------------------- /packages/connection-provider/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/connection-provider/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export { ConnectionProviderModule } from './connection-provider.module' 2 | export { ConnectionProviderService } from './connection-provider.service' 3 | export type { IConnectionProvider } from './connection-provider.service' 4 | export type { 5 | IServiceDeclaration, 6 | IThriftServiceProfile, 7 | IDiscoverable, 8 | IConnectionParams, 9 | } from './interfaces' 10 | -------------------------------------------------------------------------------- /packages/connection-provider/src/main/ts/connection-provider.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common' 2 | 3 | import { ConnectionProviderService } from './connection-provider.service' 4 | 5 | @Global() 6 | @Module({ 7 | providers: [ 8 | { provide: 'IConnectionProvider', useClass: ConnectionProviderService }, 9 | ], 10 | exports: ['IConnectionProvider'], 11 | }) 12 | export class ConnectionProviderModule {} 13 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/logger.pipe.ts: -------------------------------------------------------------------------------- 1 | import type { ILogEntry } from '@qiwi/logwrap' 2 | import type { ILogger } from '@qiwi/substrate' 3 | 4 | export const createLoggerPipe = 5 | (logger: ILogger) => 6 | (entry: ILogEntry): ILogEntry => { 7 | const { input, meta, level } = entry 8 | logger.log({ 9 | level, 10 | message: input.join(' '), 11 | meta, 12 | }) 13 | 14 | return entry 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # misc 2 | 3 | .DS_Store 4 | /.idea 5 | .vscode 6 | 7 | # local 8 | 9 | *.local 10 | 11 | # yarn 12 | 13 | /.pnp.* 14 | /.yarn/cache 15 | /.yarn/unplugged 16 | /.yarn/install-state.gz 17 | /yarn-error.log 18 | 19 | # open-ssl 20 | 21 | *.ca 22 | *.crt 23 | *.csr 24 | *.der 25 | *.kdb 26 | *.org 27 | *.p12 28 | *.pem 29 | *.rnd 30 | *.ssleay 31 | *.smime 32 | 33 | /node_modules 34 | /target 35 | 36 | somepath 37 | .npmrc 38 | 39 | -------------------------------------------------------------------------------- /packages/mdc/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/common/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/config/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/metric/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/svc-info/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/connection-provider/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/consul/src/test/ts/mock/fakeConsulDiscovery.ts: -------------------------------------------------------------------------------- 1 | import { IConsulService } from '../../../main/ts' 2 | 3 | export class FakeConsulDiscovery implements IConsulService { 4 | async register() { 5 | return 'registered' 6 | } 7 | 8 | async getKv() { 9 | return { 10 | value: 'consul', 11 | } 12 | } 13 | 14 | async getConnectionParams() { 15 | return { 16 | host: 'test', 17 | port: 8080, 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-monorepo", 3 | "version": "0.0.0", 4 | "description": "QIWI Nestjs Enterprise", 5 | "type": "module", 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "private": true, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 13 | }, 14 | "packageManager": "yarn@4.0.0-rc.13", 15 | "packasso": "@packasso/lib", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /packages/facade/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [ 15 | { 16 | "path": "../common/tsconfig.esm.json" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/consul/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [ 15 | { 16 | "path": "../connection-provider/tsconfig.esm.json" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/facade/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [ 18 | { 19 | "path": "../common/tsconfig.cjs.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/consul/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [ 18 | { 19 | "path": "../connection-provider/tsconfig.cjs.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/app.pipe.ts: -------------------------------------------------------------------------------- 1 | import type { ILogEntry } from '@qiwi/logwrap' 2 | 3 | /** 4 | * @param {string} name - app name 5 | * @param {string} version - app version 6 | * @param {string} host - app host 7 | * @return {ILogEntry} Meta logger entry enriched with params (name, versions, host) 8 | */ 9 | export const createAppPipe = 10 | (name: string, version: string, host: string) => 11 | (entry: ILogEntry): ILogEntry => { 12 | Object.assign(entry.meta, { 13 | name, 14 | version, 15 | host, 16 | }) 17 | return entry 18 | } 19 | -------------------------------------------------------------------------------- /packages/thrift/src/main/ts/thrift.server.ts: -------------------------------------------------------------------------------- 1 | import * as thrift from 'thrift' 2 | 3 | /** 4 | * Thrift server decorator 5 | * 6 | * @param processor 7 | * @param port 8 | * @constructor 9 | */ 10 | export const ThriftServer = (processor: any, port: number): ClassDecorator => { 11 | // eslint-disable-next-line @typescript-eslint/ban-types 12 | return (target: TFunction) => { 13 | const server = thrift.createServer(processor, target.prototype) 14 | server.listen(port) 15 | // @ts-ignore 16 | target.prototype._server = server 17 | return target 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import { dirname, join } from 'node:path' 3 | import { describe, it } from 'node:test' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | import { configServiceFactory } from '../../main/ts/config.service' 7 | 8 | const testFileDir = dirname(fileURLToPath(import.meta.url)) 9 | 10 | const testCfgPath = join(testFileDir, 'config', 'test.json') 11 | 12 | describe('configServiceFactory', () => { 13 | it('returns config service', async () => { 14 | assert.ok(await configServiceFactory({ path: testCfgPath })) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/mdc/src/main/ts/mdc.service.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { getContextValue } from '@qiwi/mware-context' 3 | // @ts-ignore 4 | import { TRACE_KEY } from '@qiwi/mware-mdc' 5 | 6 | import { Injectable } from '@nestjs/common' 7 | 8 | export interface IMdcService { 9 | getTrace(): any 10 | } 11 | 12 | @Injectable() 13 | export class MdcService implements IMdcService { 14 | getTrace(ns?: unknown) { 15 | const trace = getContextValue(TRACE_KEY, ns) 16 | if (!trace) { 17 | return { 18 | trace_id: 'foo', 19 | span_id: 'bar', 20 | parent_span_id: 'baz', 21 | } 22 | } 23 | 24 | return trace 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/svc-info/src/main/ts/svc-info.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Module } from '@nestjs/common' 2 | 3 | import type { ISvcInfoModuleOpts } from './interfaces' 4 | import { SvcInfoController } from './svc-info.controller' 5 | 6 | @Global() 7 | @Module({ 8 | controllers: [SvcInfoController], 9 | }) 10 | export class SvcInfoModule { 11 | static register(opts: ISvcInfoModuleOpts): DynamicModule { 12 | return { 13 | module: SvcInfoModule, 14 | controllers: [SvcInfoController], 15 | providers: [ 16 | { 17 | provide: 'ISvcInfoModuleOpts', 18 | useValue: opts, 19 | }, 20 | ], 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/thrift/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es2022", 6 | "module": "es2022", 7 | "rootDir": "./src/main/ts", 8 | "outDir": "./target/esm", 9 | "tsBuildInfoFile": "./target/esm/.tsbuildinfo" 10 | }, 11 | "include": [ 12 | "./src/main/ts" 13 | ], 14 | "references": [ 15 | { 16 | "path": "../config/tsconfig.esm.json" 17 | }, 18 | { 19 | "path": "../connection-provider/tsconfig.esm.json" 20 | }, 21 | { 22 | "path": "../consul/tsconfig.esm.json" 23 | }, 24 | { 25 | "path": "../logger/tsconfig.esm.json" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/mock/fakeConsulDiscovery9091.ts: -------------------------------------------------------------------------------- 1 | import { IConsulService } from '../../../main/ts' 2 | 3 | export class FakeConsulDiscovery9091 implements IConsulService { 4 | async register() { 5 | return 'registered' 6 | } 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | async getConnectionParams(_serviceName: string) { 10 | return { 11 | host: 'localhost', 12 | port: 9091, 13 | } 14 | } 15 | // @ts-ignore 16 | // eslint-disable-next-line @typescript-eslint/no-empty-function 17 | getKv(key: string) {} 18 | 19 | // @ts-ignore 20 | // eslint-disable-next-line @typescript-eslint/no-empty-function 21 | setKv(data: any) {} 22 | } 23 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/mock/fakeConsulDiscovery9090.ts: -------------------------------------------------------------------------------- 1 | import { IConsulService } from '../../../main/ts' 2 | 3 | export class FakeConsulDiscovery9090 implements IConsulService { 4 | async register() { 5 | return 'registered' 6 | } 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | async getConnectionParams(_serviceName: string) { 10 | return { 11 | host: 'localhost', 12 | port: 9090, 13 | } 14 | } 15 | 16 | // @ts-ignore 17 | // eslint-disable-next-line @typescript-eslint/no-empty-function 18 | getKv(key: string) {} 19 | 20 | // @ts-ignore 21 | // eslint-disable-next-line @typescript-eslint/no-empty-function 22 | setKv(data: any) {} 23 | } 24 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/thrift.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { notEqual } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import { 5 | INJECT_THRIFT_SERVICE, 6 | InjectThriftService, 7 | ThriftClientProvider, 8 | ThriftModule, 9 | ThriftServer, 10 | thriftServiceFactory, 11 | } from '../../main/ts' 12 | 13 | describe('thrift.module', () => { 14 | it('exports', () => { 15 | const cases = [ 16 | ThriftModule, 17 | ThriftServer, 18 | ThriftClientProvider, 19 | INJECT_THRIFT_SERVICE, 20 | InjectThriftService, 21 | thriftServiceFactory, 22 | ] 23 | for (const dep of cases) { 24 | notEqual(dep, undefined) 25 | } 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/thrift/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext" 9 | ], 10 | "rootDir": "./src/main/ts", 11 | "outDir": "./target/cjs", 12 | "tsBuildInfoFile": "./target/cjs/.tsbuildinfo" 13 | }, 14 | "include": [ 15 | "./src/main/ts" 16 | ], 17 | "references": [ 18 | { 19 | "path": "../config/tsconfig.cjs.json" 20 | }, 21 | { 22 | "path": "../connection-provider/tsconfig.cjs.json" 23 | }, 24 | { 25 | "path": "../consul/tsconfig.cjs.json" 26 | }, 27 | { 28 | "path": "../logger/tsconfig.cjs.json" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/mdc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/metric/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/svc-info/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/connection-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": {} 22 | }, 23 | "include": [ 24 | "./src/main/ts", 25 | "./src/test/ts" 26 | ], 27 | "exclude": [ 28 | "./node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/masker.ts: -------------------------------------------------------------------------------- 1 | import type { ILogEntry } from '@qiwi/logwrap' 2 | 3 | import luhn from 'fast-luhn' 4 | 5 | /** 6 | * @param {string | number} input 7 | * @return {string} masked PAN or input 8 | */ 9 | export const masker = (input: string | number): string => { 10 | return (input + '').replace(/\s*(\d\s*){13,19}/g, (v) => 11 | luhn(v.replace(/\s+/g, '')) 12 | ? `${v.slice(0, 4)} **** **** ${v.slice(-4)}` 13 | : '' + input, 14 | ) 15 | } 16 | /** 17 | * Creates pipe for pan masking 18 | * @see {@link masker} 19 | * @return {ILogEntry} logEntry with masked input 20 | */ 21 | export const maskerLoggerPipeFactory = 22 | () => 23 | (entry: ILogEntry): ILogEntry => ({ 24 | ...entry, 25 | input: entry.input.map(masker), 26 | }) 27 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/logger.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Module } from '@nestjs/common' 2 | 3 | import { TLoggerPipe } from './interfaces' 4 | import { LoggerService } from './logger.service' 5 | 6 | @Global() 7 | @Module({ 8 | providers: [ 9 | { provide: 'ILogger', useClass: LoggerService }, 10 | { provide: 'ILoggerPipeline', useValue: [] }, 11 | ], 12 | 13 | exports: ['ILogger'], 14 | }) 15 | export class LoggerModule { 16 | static register(...pipes: TLoggerPipe[]): DynamicModule { 17 | return { 18 | module: LoggerModule, 19 | exports: ['ILogger'], 20 | providers: [ 21 | { provide: 'ILogger', useClass: LoggerService }, 22 | { provide: 'ILoggerPipeline', useValue: pipes }, 23 | ], 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/thrift/src/main/ts/thrift.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common' 2 | 3 | import { ThriftClientProvider } from './thrift.client' 4 | import { 5 | INJECT_THRIFT_SERVICE, 6 | thriftServiceFactory, 7 | } from './thrift.decorators' 8 | 9 | @Global() 10 | @Module({ 11 | providers: [ 12 | { provide: 'IThriftClientProvider', useClass: ThriftClientProvider }, 13 | { provide: 'IThriftClientService', useExisting: 'IThriftClientProvider' }, // Legacy 14 | { 15 | provide: INJECT_THRIFT_SERVICE, 16 | useFactory: thriftServiceFactory, 17 | inject: ['IThriftClientProvider'], 18 | }, 19 | ], 20 | exports: [ 21 | 'IThriftClientService', 22 | 'IThriftClientProvider', 23 | INJECT_THRIFT_SERVICE, 24 | ], 25 | }) 26 | export class ThriftModule {} 27 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/graphite.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import GraphiteClient from 'graphite' 3 | 4 | import { IGraphiteService } from './graphite.servise.interface' 5 | 6 | @Injectable() 7 | export class GraphiteService implements IGraphiteService { 8 | private client: GraphiteClient 9 | 10 | constructor(graphiteApiEndpoint: string) { 11 | this.client = GraphiteClient.createClient( 12 | `plaintext://${graphiteApiEndpoint}/`, 13 | ) 14 | } 15 | 16 | public sendMetric(metrics: Record) { 17 | return new Promise((resolve, reject) => { 18 | this.client.write(metrics, (err) => { 19 | if (err) { 20 | reject(new Error(`Error sending metric: ${err}`)) 21 | } 22 | 23 | resolve() 24 | }) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/facade/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": { 22 | "@qiwi/nestjs-enterprise-common": [ 23 | "../common/src/main/ts" 24 | ] 25 | } 26 | }, 27 | "include": [ 28 | "./src/main/ts", 29 | "./src/test/ts" 30 | ], 31 | "exclude": [ 32 | "./node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectCoverage": true, 3 | "collectCoverageFrom": [ 4 | "/src/main/[jt]s/**/*.[jt]s?(x)" 5 | ], 6 | "coverageDirectory": "/target/coverage", 7 | "coveragePathIgnorePatterns": [ 8 | "/node_modules", 9 | "/target" 10 | ], 11 | "projects": [ 12 | "/packages/common/jest.config.json", 13 | "/packages/config/jest.config.json", 14 | "/packages/connection-provider/jest.config.json", 15 | "/packages/consul/jest.config.json", 16 | "/packages/mdc/jest.config.json", 17 | "/packages/facade/jest.config.json", 18 | "/packages/logger/jest.config.json", 19 | "/packages/metric/jest.config.json", 20 | "/packages/svc-info/jest.config.json", 21 | "/packages/thrift/jest.config.json" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/consul/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": { 22 | "@qiwi/nestjs-enterprise-connection-provider": [ 23 | "../connection-provider/src/main/ts" 24 | ] 25 | } 26 | }, 27 | "include": [ 28 | "./src/main/ts", 29 | "./src/test/ts" 30 | ], 31 | "exclude": [ 32 | "./node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/config/src/main/ts/config.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Module } from '@nestjs/common' 2 | 3 | import { configServiceFactory } from './config.service' 4 | 5 | @Global() 6 | @Module({ 7 | controllers: [], 8 | providers: [ 9 | { 10 | provide: 'IConfigService', 11 | useFactory: configServiceFactory, 12 | }, 13 | ], 14 | exports: ['IConfigService'], 15 | }) 16 | export class ConfigModule { 17 | static register( 18 | options: { 19 | path?: string 20 | config?: Record 21 | schemaPath?: string 22 | } = {}, 23 | ): DynamicModule { 24 | return { 25 | module: ConfigModule, 26 | providers: [ 27 | { 28 | provide: 'IConfigService', 29 | useFactory: () => configServiceFactory(options), 30 | }, 31 | ], 32 | exports: ['IConfigService'], 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./" 4 | }, 5 | "files": [], 6 | "references": [ 7 | { 8 | "path": "./packages/common/tsconfig.cjs.json" 9 | }, 10 | { 11 | "path": "./packages/connection-provider/tsconfig.cjs.json" 12 | }, 13 | { 14 | "path": "./packages/config/tsconfig.cjs.json" 15 | }, 16 | { 17 | "path": "./packages/consul/tsconfig.cjs.json" 18 | }, 19 | { 20 | "path": "./packages/facade/tsconfig.cjs.json" 21 | }, 22 | { 23 | "path": "./packages/logger/tsconfig.cjs.json" 24 | }, 25 | { 26 | "path": "./packages/mdc/tsconfig.cjs.json" 27 | }, 28 | { 29 | "path": "./packages/metric/tsconfig.cjs.json" 30 | }, 31 | { 32 | "path": "./packages/svc-info/tsconfig.cjs.json" 33 | }, 34 | { 35 | "path": "./packages/thrift/tsconfig.cjs.json" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./" 4 | }, 5 | "files": [], 6 | "references": [ 7 | { 8 | "path": "./packages/common/tsconfig.esm.json" 9 | }, 10 | { 11 | "path": "./packages/connection-provider/tsconfig.esm.json" 12 | }, 13 | { 14 | "path": "./packages/config/tsconfig.esm.json" 15 | }, 16 | { 17 | "path": "./packages/consul/tsconfig.esm.json" 18 | }, 19 | { 20 | "path": "./packages/facade/tsconfig.esm.json" 21 | }, 22 | { 23 | "path": "./packages/logger/tsconfig.esm.json" 24 | }, 25 | { 26 | "path": "./packages/mdc/tsconfig.esm.json" 27 | }, 28 | { 29 | "path": "./packages/metric/tsconfig.esm.json" 30 | }, 31 | { 32 | "path": "./packages/svc-info/tsconfig.esm.json" 33 | }, 34 | { 35 | "path": "./packages/thrift/tsconfig.esm.json" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /packages/config/src/main/ts/uniconfig-json-schema-validation-plugin/resolve-schema-path.ts: -------------------------------------------------------------------------------- 1 | import { existsSync as nodeExistsSync } from 'node:fs' 2 | import { dirname, resolve } from 'node:path' 3 | 4 | export const defaultAppSchemaFilename = 'app.config.schema.json' 5 | 6 | const defaultDeps = { 7 | existsSync: nodeExistsSync, 8 | } 9 | 10 | export const resolveSchemaPath = ( 11 | options: { 12 | path?: any 13 | schemaPath?: string 14 | } = {}, 15 | deps = defaultDeps, 16 | ): string | undefined => { 17 | if (options.schemaPath) { 18 | return options.schemaPath 19 | } 20 | 21 | const defaultSchemaPath = 22 | options.path && typeof options.path === 'string' 23 | ? resolve(dirname(options.path), defaultAppSchemaFilename) 24 | : resolve(process.cwd(), 'config', defaultAppSchemaFilename) 25 | 26 | if (deps.existsSync(defaultSchemaPath)) { 27 | return defaultSchemaPath 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/logger/src/test/ts/app.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { deepEqual, equal, notEqual } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import type { ILogEntry } from '@qiwi/logwrap' 5 | 6 | import { createAppPipe } from '../../main/ts/app.pipe' 7 | 8 | describe('createAppPipe', () => { 9 | it('is defined', () => notEqual(createAppPipe, undefined)) 10 | 11 | it('creates pipe', () => { 12 | const pipe = createAppPipe('name', 'version', 'localhost') 13 | equal(typeof pipe, 'function') 14 | }) 15 | 16 | it('pipe processes log entry', () => { 17 | const name = 'name' 18 | const version = 'version' 19 | const host = 'localhost' 20 | const pipe = createAppPipe(name, version, host) 21 | const input: ILogEntry = { 22 | level: 'info', 23 | input: [], 24 | meta: {}, 25 | } 26 | deepEqual(pipe(input), { ...input, meta: { name, version, host } }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/metric/src/test/ts/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "name": "test-name-app", 4 | "local": "$env:LOCAL", 5 | "version": "$pkg:version", 6 | 7 | "graphite": { 8 | "url": "http://graphite-url.com" 9 | }, 10 | 11 | "metric": { 12 | "prefix": "metric", 13 | "interval": 30 14 | }, 15 | 16 | "logger": { 17 | "level": "debug", 18 | "dir": "$logDir:", 19 | "maxsize": 157286400, 20 | "datePattern": "YYYY-MM-DD", 21 | "appJsonFilename": "application-json.log", 22 | "appFilename": "testlog.log", 23 | "maxFiles": 10, 24 | "tailable": true, 25 | "zippedArchive": true 26 | } 27 | }, 28 | "sources": { 29 | "logDir": { 30 | "data": ["src/test/ts", "log"], 31 | "pipeline": "path" 32 | }, 33 | "env": { 34 | "pipeline": "env" 35 | }, 36 | "host": { 37 | "pipeline": "ip" 38 | }, 39 | "pkg": { 40 | "pipeline": "pkg" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/consul/src/test/ts/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "name": "test-name-app", 4 | "local": "$env:LOCAL", 5 | "version": "$pkg:version", 6 | 7 | "consul": { 8 | "host": "consul-test.qiwi.com", 9 | "port": "80", 10 | "tags": ["b2b-published", "published"], 11 | "enabled": false 12 | }, 13 | 14 | "logger": { 15 | "level": "debug", 16 | "dir": "$logDir:", 17 | "maxsize": 157286400, 18 | "datePattern": "YYYY-MM-DD", 19 | "appJsonFilename": "application-json.log", 20 | "appFilename": "testlog.log", 21 | "maxFiles": 10, 22 | "tailable": true, 23 | "zippedArchive": true 24 | } 25 | }, 26 | "sources": { 27 | "logDir": { 28 | "data": ["src/test/ts", "log"], 29 | "pipeline": "path" 30 | }, 31 | "env": { 32 | "pipeline": "env" 33 | }, 34 | "host": { 35 | "pipeline": "ip" 36 | }, 37 | "pkg": { 38 | "pipeline": "pkg" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/mdc/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-mdc 2 | Nestjs module for working with continuation-local storage. 3 | 4 | ## Installation 5 | ```shell script 6 | yarn add @qiwi/nestjs-enterprise-mdc 7 | ``` 8 | 9 | ## Configuration 10 | ```typescript 11 | import { MdcModule } from '@qiwi/nestjs-enterprise-mdc' 12 | 13 | @Module({ 14 | imports: [ 15 | MdcModule, 16 | // and so on 17 | ], 18 | controllers: [], 19 | providers: [], 20 | }) 21 | export class AppModule {} 22 | ``` 23 | 24 | ## Usage 25 | ```typescript 26 | // main.ts 27 | import { mdc, logger as log, clscxt } from '@qiwi/nestjs-enterprise-mdc' 28 | 29 | async function bootstrap() { 30 | const app = await NestFactory.create(AppModule) 31 | //... 32 | app 33 | .use(clscxt()) 34 | .use(mdc()) 35 | .use(log({ logger })) 36 | //... 37 | ``` 38 | 39 | ## Api 40 | #### mdc, validator, logger, context as clscxt 41 | see [@qiwi/mware](https://github.com/qiwi/mware) 42 | 43 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/mdc/) 44 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/decorators/metered.decorator.ts: -------------------------------------------------------------------------------- 1 | import { constructDecorator } from '@qiwi/decorator-utils' 2 | 3 | import { Inject } from '@nestjs/common' 4 | 5 | /** 6 | * The decorator measures the number of function calls and also tracks 1-, 5-, and 15-minute moving averages. 7 | * 8 | * - **count** - The total of all values added to the meter. 9 | * - **1MinuteRate** - The rate of the meter biased towards the last 1 minute. 10 | * - **5MinuteRate** - The rate of the meter biased towards the last 5 minute. 11 | * - **15MinuteRate** - The rate of the meter biased towards the last 15 minute. 12 | */ 13 | export const MeteredDecorator = constructDecorator( 14 | ({ target, proto, args: [metricName] }) => { 15 | const injectMetric = Inject('IMetricService') 16 | injectMetric(proto, 'metricService') 17 | 18 | return function (...args: Array) { 19 | // @ts-ignore 20 | this.metricService.meter(metricName).update() 21 | // @ts-ignore 22 | return target.apply(this, args) 23 | } 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/logger-meta.pipe.ts: -------------------------------------------------------------------------------- 1 | import type { ILogEntry } from '@qiwi/logwrap' 2 | 3 | import lo from 'lodash' 4 | 5 | /** 6 | * Creates pipe for metadata injection, used with @qiwi-private/js-platform-mdc-nestjs 7 | * @see {@link https://github.qiwi.com/common/js-platform/tree/master/packages/mdc-nestjs} 8 | */ 9 | export const createMetaPipe = 10 | () => 11 | ({ meta, input, level }: ILogEntry): ILogEntry => { 12 | const { name, version, host, event, origin, extra, trace } = meta 13 | const { trace_id, span_id, parent_span_id } = trace || {} 14 | const auth = lo.pick(meta.auth || {}, 'value.principal', 'type') 15 | const publicMeta = { 16 | ...extra, 17 | mdc: { 18 | traceId: trace_id || undefined, 19 | spanId: span_id || undefined, 20 | parentSpanId: parent_span_id || undefined, 21 | }, 22 | event, 23 | origin, 24 | auth, 25 | app_version: version, 26 | serviceName: name, 27 | host, 28 | } 29 | 30 | return { 31 | meta: { ...meta, publicMeta }, 32 | input, 33 | level, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/mdc/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/common/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/config/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/consul/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/consul/src/main/ts/consul.module.ts: -------------------------------------------------------------------------------- 1 | import { ConsulDiscoveryService } from '@qiwi/consul-service-discovery' 2 | import type { IConfig, ILogger } from '@qiwi/substrate' 3 | 4 | import { Global, Module } from '@nestjs/common' 5 | 6 | import { ConsulService } from './consul.service' 7 | 8 | @Global() 9 | @Module({ 10 | providers: [ 11 | { 12 | provide: 'IDiscoveryService', 13 | useFactory: (config: IConfig, logger: ILogger) => { 14 | const port = config.get('consul.port') 15 | const host = config.get('consul.host') 16 | const secure = config.get('consul.secure') 17 | const ca = config.get('consul.ca') 18 | const defaults = config.get('consul.defaults') 19 | return new ConsulDiscoveryService( 20 | { 21 | host, 22 | port, 23 | secure, 24 | defaults, 25 | ca, 26 | }, 27 | { logger }, 28 | ) 29 | }, 30 | inject: ['IConfigService'], 31 | }, 32 | { provide: 'IConsul', useClass: ConsulService }, 33 | ], 34 | exports: ['IConsul'], 35 | }) 36 | export class ConsulModule {} 37 | -------------------------------------------------------------------------------- /packages/facade/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/logger/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/metric/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/svc-info/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/thrift/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/connection-provider/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 QIWI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/thrift/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2022", 5 | "moduleResolution": "node", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "removeComments": false, 15 | "downlevelIteration": true, 16 | "importHelpers": true, 17 | "baseUrl": "./", 18 | "types": [ 19 | "node" 20 | ], 21 | "paths": { 22 | "@qiwi/nestjs-enterprise-config": [ 23 | "../config/src/main/ts" 24 | ], 25 | "@qiwi/nestjs-enterprise-connection-provider": [ 26 | "../connection-provider/src/main/ts" 27 | ], 28 | "@qiwi/nestjs-enterprise-consul": [ 29 | "../consul/src/main/ts" 30 | ], 31 | "@qiwi/nestjs-enterprise-logger": [ 32 | "../logger/src/main/ts" 33 | ] 34 | } 35 | }, 36 | "include": [ 37 | "./src/main/ts", 38 | "./src/test/ts" 39 | ], 40 | "exclude": [ 41 | "./node_modules" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/uniconfig-json-schema-validation-plugin/resolve-schema-path.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import { 5 | defaultAppSchemaFilename, 6 | resolveSchemaPath, 7 | } from '../../../main/ts/uniconfig-json-schema-validation-plugin/resolve-schema-path' 8 | 9 | describe('resolveSchemaPath', () => { 10 | it('returns schemaPath', () => { 11 | assert.equal(resolveSchemaPath({ schemaPath: 'foo' }), 'foo') 12 | }) 13 | 14 | it('returns ${dirname(path)}/app.config.schema.json if it exists', () => { 15 | const opts = { path: 'foo/bar.json' } 16 | assert.equal(resolveSchemaPath(opts), undefined) 17 | assert.ok( 18 | resolveSchemaPath(opts, { existsSync: () => true })?.endsWith( 19 | `foo/${defaultAppSchemaFilename}`, 20 | ), 21 | ) 22 | }) 23 | 24 | it('returns ${process.cwd()}/app.config.schema.json if it exists', () => { 25 | assert.equal(resolveSchemaPath(), undefined) 26 | assert.equal( 27 | resolveSchemaPath(undefined, { existsSync: () => true }), 28 | `${process.cwd()}/config/${defaultAppSchemaFilename}`, 29 | ) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/mdc/src/test/ts/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | // @ts-ignore 5 | import { getContext, setContextValue } from '@qiwi/mware-context' 6 | // @ts-ignore 7 | import { TRACE_KEY } from '@qiwi/mware-mdc' 8 | 9 | import { Test } from '@nestjs/testing' 10 | import lodash from 'lodash' 11 | 12 | import { MdcModule } from '../../main/ts' 13 | 14 | const toMatchObject = (actual: any, expected: any) => { 15 | equal(lodash.isMatch(actual, expected), true) 16 | } 17 | 18 | describe('mdc', () => { 19 | it('test', async () => { 20 | const module = await Test.createTestingModule({ 21 | imports: [MdcModule], 22 | }).compile() 23 | 24 | const mdcService = module.get('IMdc') 25 | 26 | const ctx = getContext() 27 | ctx.run(() => { 28 | setContextValue( 29 | TRACE_KEY, 30 | { 31 | trace_id: '1', 32 | span_id: '2', 33 | parent_span_id: '3', 34 | }, 35 | ctx, 36 | ) 37 | toMatchObject(mdcService.getTrace(ctx), { 38 | trace_id: '1', 39 | span_id: '2', 40 | parent_span_id: '3', 41 | }) 42 | }) 43 | await module.close() 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /packages/connection-provider/src/main/ts/connection-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common' 2 | import lo from 'lodash' 3 | 4 | import type { 5 | IConnectionParams, 6 | IDiscoverable, 7 | IServiceDeclaration, 8 | } from './interfaces' 9 | import { DiscoveryType } from './interfaces' 10 | 11 | export interface IConnectionProvider { 12 | /** 13 | * Get connection params from serviceProfile 14 | * 15 | * @param serviceProfile 16 | */ 17 | getConnectionParams( 18 | serviceProfile: IServiceDeclaration, 19 | ): Promise 20 | } 21 | 22 | @Injectable() 23 | export class ConnectionProviderService implements IConnectionProvider { 24 | constructor( 25 | @Inject('IConsul') 26 | private consul: IDiscoverable, 27 | ) {} 28 | 29 | async getConnectionParams(serviceProfile: IServiceDeclaration) { 30 | const discovery = serviceProfile.discovery 31 | 32 | if (discovery.type === DiscoveryType.CONSUL) { 33 | return this.consul.getConnectionParams(discovery.serviceName) 34 | } 35 | 36 | if (discovery.type === DiscoveryType.ENDPOINT) { 37 | return lo.sample(discovery.endpoints) 38 | } 39 | 40 | throw new Error('Invalid serviceProfile') 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QIWI Nestjs enterprise 2 | Components to build Nestjs-based applications for enterprise 3 | 4 | * [@qiwi/nestjs-enterprise-common](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/common#readme) 5 | * [@qiwi/nestjs-enterprise-consul](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/consul#readme) 6 | * [@qiwi/nestjs-enterprise-config](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/config#readme) 7 | * [@qiwi/nestjs-enterprise-logger](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/logger#readme) 8 | * [@qiwi/nestjs-enterprise-mdc](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/mdc#readme) 9 | * [@qiwi/nestjs-enterprise-svc-info](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/svc-info#readme) 10 | * [@qiwi/nestjs-enterprise-thrift](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/thrift#readme) 11 | * [@qiwi/nestjs-enterprise-connection-provider](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/connection-provider#readme) 12 | 13 | ### Roadmap 14 | Make some parts of `qiwi-js-platform` public 15 | * [x] Config 16 | * [x] Logger 17 | * [x] Consul 18 | * [ ] Healthcheck 19 | * [x] Svc-info 20 | * [ ] Metrics 21 | * [x] MDC 22 | * [ ] Swagger 23 | * [x] Thrift 24 | * [x] Connection provider 25 | -------------------------------------------------------------------------------- /packages/facade/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise", 3 | "description": "QIWI Nestjs enterprise facade", 4 | "version": "1.4.1", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "keywords": [], 26 | "exports": "./src/main/ts/index.ts", 27 | "dependencies": { 28 | "@qiwi/nestjs-enterprise-common": "workspace:*", 29 | "@qiwi/substrate": "^2.0.4", 30 | "tslib": "^2.6.1" 31 | }, 32 | "author": "Anton Golub ", 33 | "license": "MIT", 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 40 | }, 41 | "homepage": "https://github.com/qiwi/nestjs-enterprise/#readme" 42 | } 43 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common' 2 | 3 | import { ErrorDecorator } from './decorators/error.decorator' 4 | import { MeteredDecorator } from './decorators/metered.decorator' 5 | import { RequestRateDecorator } from './decorators/request-rate.decorator' 6 | 7 | export { GraphiteService } from './graphite.service' 8 | export { MetricService } from './metric.service' 9 | export { MetricModule } from './metric.module' 10 | export { getNodeMetrics } from './get-node-metrics' 11 | export type { IMetricService } from './metric.service.interface' 12 | export type { IGraphiteService } from './graphite.servise.interface' 13 | 14 | /** 15 | * Union {@link ErrorDecorator}, {@link MeteredDecorator} and {@link RequestRateDecorator} 16 | * 17 | * @param metricName 18 | * @constructor 19 | */ 20 | function MetricDecorator(metricName: string) { 21 | return applyDecorators( 22 | ErrorDecorator(metricName + '.Error'), 23 | MeteredDecorator(metricName + '.Metered'), 24 | RequestRateDecorator(metricName + '.Request-rate'), 25 | ) 26 | } 27 | 28 | export { MetricDecorator } 29 | 30 | export { RequestRateDecorator } from './decorators/request-rate.decorator' 31 | export { MeteredDecorator } from './decorators/metered.decorator' 32 | export { ErrorDecorator } from './decorators/error.decorator' 33 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "name": "test-name-app", 4 | "local": "$env:LOCAL", 5 | "version": "$pkg:version", 6 | 7 | "services": { 8 | "common-auth": { 9 | "type": "thrift", 10 | "thriftServiceName": "someTserviceName", 11 | "discovery": { 12 | "type": "consul", 13 | "serviceName": "serviceName" 14 | }, 15 | "creds": { 16 | "type": "username-and-password", 17 | "username": "myusername", 18 | "password": "mypass" 19 | } 20 | } 21 | }, 22 | 23 | "thriftPool": { 24 | "min": 0, 25 | "max": 10 26 | }, 27 | 28 | "logger": { 29 | "level": "debug", 30 | "dir": "$logDir:", 31 | "maxsize": 157286400, 32 | "datePattern": "YYYY-MM-DD", 33 | "appJsonFilename": "application-json.log", 34 | "appFilename": "testlog.log", 35 | "maxFiles": 10, 36 | "tailable": true, 37 | "zippedArchive": true 38 | } 39 | }, 40 | "sources": { 41 | "logDir": { 42 | "data": ["src/test/ts", "log"], 43 | "pipeline": "path" 44 | }, 45 | "env": { 46 | "pipeline": "env" 47 | }, 48 | "host": { 49 | "pipeline": "ip" 50 | }, 51 | "pkg": { 52 | "pipeline": "pkg" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/get-node-metrics.ts: -------------------------------------------------------------------------------- 1 | import os from 'node:os' 2 | 3 | /** 4 | * Return process and os metric. 5 | * 6 | * - node.process.memory-usage.rss - representing the Resident Set Size (RSS) in bytes. 7 | * - node.process.memory-usage.heap-total - refer to V8's memory usage. 8 | * - node.process.memory-usage.heap-used - refer to V8's memory usage. 9 | * - node.os.loadavg.1m - 1 minute load averages. 10 | * - node.os.loadavg.5m - 5 minute load averages. 11 | * - node.os.loadavg.15m - 15 minute load averages. 12 | * - node.os.freemem - amount of free system memory in bytes as an integer. 13 | * - node.os.totalmem - total amount of system memory in bytes as an integer. 14 | * 15 | * @see {@link https://nodejs.org/api/process.html#process_process_memoryusage} 16 | * @see {@link https://nodejs.org/api/os.html#os_os_loadavg} 17 | */ 18 | export const getNodeMetrics = () => ({ 19 | // https://nodejs.org/api/process.html#process_process_memoryusage 20 | 'node.process.memory-usage.rss': process.memoryUsage().rss, 21 | 'node.process.memory-usage.heap-total': process.memoryUsage().heapTotal, 22 | 'node.process.memory-usage.heap-used': process.memoryUsage().heapUsed, 23 | 24 | // https://nodejs.org/api/os.html#os_os_loadavg 25 | 'node.os.loadavg.1m': os.loadavg()[0], 26 | 'node.os.loadavg.5m': os.loadavg()[1], 27 | 'node.os.loadavg.15m': os.loadavg()[2], 28 | 'node.os.freemem': os.freemem(), 29 | 'node.os.totalmem': os.totalmem(), 30 | }) 31 | -------------------------------------------------------------------------------- /packages/mdc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-mdc", 3 | "version": "1.4.1", 4 | "description": "MDC module for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/mware": "^1.14.2", 30 | "@qiwi/substrate": "^2.0.4", 31 | "@types/lodash": "^4.14.198", 32 | "rxjs": "^7.8.1", 33 | "tslib": "^2.6.1" 34 | }, 35 | "devDependencies": { 36 | "@nestjs/testing": "^10.2.6", 37 | "lodash": "^4.17.21", 38 | "reflect-metadata": "^0.1.13" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 43 | }, 44 | "keywords": [ 45 | "js platform", 46 | "config" 47 | ], 48 | "bugs": { 49 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 50 | }, 51 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /packages/connection-provider/src/test/ts/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import lodash from 'lodash' 5 | 6 | import { ConnectionProviderService } from '../../main/ts' 7 | import { DiscoveryType, TServiceType } from '../../main/ts/interfaces' 8 | 9 | const toMatchObject = (actual: any, expected: any) => { 10 | equal(lodash.isMatch(actual, expected), true) 11 | } 12 | 13 | describe('connection-provider', () => { 14 | describe('simple provider', () => { 15 | const service = new ConnectionProviderService({ 16 | getConnectionParams: async () => ({ host: 'host', port: 1000 }), 17 | }) 18 | 19 | it('returns endpoints with service name', async () => { 20 | toMatchObject( 21 | await service.getConnectionParams({ 22 | type: TServiceType.THRIFT, 23 | thriftServiceName: 'name', 24 | discovery: { 25 | type: DiscoveryType.CONSUL, 26 | serviceName: 'serviceName', 27 | }, 28 | }), 29 | { host: 'host', port: 1000 }, 30 | ) 31 | }) 32 | 33 | it('returns endpoints with endpoints', async () => { 34 | toMatchObject( 35 | await service.getConnectionParams({ 36 | type: TServiceType.THRIFT, 37 | thriftServiceName: 'name', 38 | discovery: { 39 | type: DiscoveryType.ENDPOINT, 40 | endpoints: [{ host: '10', port: 20 }], 41 | }, 42 | }), 43 | { host: '10', port: 20 }, 44 | ) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/mock/client.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var thrift = require('thrift') 3 | var Calculator = require('./gen-nodejs/Calculator.cjs') 4 | var ttypes = require('./gen-nodejs/tutorial_types.cjs') 5 | const assert = require('assert') 6 | 7 | var transport = thrift.TBufferedTransport 8 | var protocol = thrift.TBinaryProtocol 9 | 10 | var connection = thrift.createConnection('localhost', 9090, { 11 | transport: transport, 12 | protocol: protocol, 13 | }) 14 | 15 | connection.on('error', function (err) { 16 | assert(false, err) 17 | }) 18 | 19 | // Create a Calculator client with the connection 20 | var client = thrift.createClient(Calculator, connection) 21 | 22 | client.ping(function (err, response) { 23 | console.log('ping()') 24 | }) 25 | 26 | client.add(1, 1, function (err, response) { 27 | console.log('1+1=' + response) 28 | }) 29 | 30 | work = new ttypes.Work() 31 | work.op = ttypes.Operation.DIVIDE 32 | work.num1 = 1 33 | work.num2 = 0 34 | 35 | client.calculate(1, work, function (err, message) { 36 | if (err) { 37 | console.log('InvalidOperation ' + err) 38 | } else { 39 | console.log('Whoa? You know how to divide by zero?') 40 | } 41 | }) 42 | 43 | work.op = ttypes.Operation.SUBTRACT 44 | work.num1 = 15 45 | work.num2 = 10 46 | 47 | client.calculate(1, work, function (err, message) { 48 | console.log('15-10=' + message) 49 | 50 | client.getStruct(1, function (err, message) { 51 | console.log('Check log: ' + message.value) 52 | 53 | //close the connection once we're done 54 | connection.end() 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-config", 3 | "version": "1.4.1", 4 | "description": "Config module for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/uniconfig": "^3.5.14", 30 | "@qiwi/uniconfig-plugin-ajv": "^3.5.5", 31 | "rxjs": "^7.8.1", 32 | "tslib": "^2.6.1" 33 | }, 34 | "devDependencies": { 35 | "@nestjs/testing": "^10.2.6", 36 | "class-transformer": "^0.5.1", 37 | "class-validator": "^0.14.0", 38 | "reflect-metadata": "^0.1.13" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 43 | }, 44 | "keywords": [ 45 | "js platform", 46 | "config" 47 | ], 48 | "bugs": { 49 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 50 | }, 51 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/decorators/error.decorator.ts: -------------------------------------------------------------------------------- 1 | import { constructDecorator } from '@qiwi/decorator-utils' 2 | 3 | import { Inject } from '@nestjs/common' 4 | 5 | import { isPromise } from '../utills' 6 | 7 | const onError = (service: any, metricName: string, e: any) => { 8 | service 9 | .meter(`${metricName}.${(e as Error).name || 'UnresolvedError'}`) 10 | .update() 11 | throw e 12 | } 13 | 14 | /** 15 | * The decorator measures the number of error events and also tracks 1-, 5-, and 15-minute moving averages. 16 | * 17 | * - **count** - The total of all values added to the meter. 18 | * - **1MinuteRate** - The rate of the meter biased towards the last 1 minute. 19 | * - **5MinuteRate** - The rate of the meter biased towards the last 5 minute. 20 | * - **15MinuteRate** - The rate of the meter biased towards the last 15 minute. 21 | */ 22 | export const ErrorDecorator = constructDecorator( 23 | ({ target, proto, args: [metricName] }) => { 24 | const injectMetric = Inject('IMetricService') 25 | injectMetric(proto, 'metricService') 26 | return function (...args: Array) { 27 | // @ts-ignore 28 | 29 | try { 30 | // @ts-ignore 31 | const res = target.apply(this, args) 32 | 33 | if (isPromise(res)) { 34 | return res.catch((e: Error) => { 35 | // @ts-ignore 36 | onError(this.metricService, metricName, e) 37 | }) 38 | } 39 | } catch (e) { 40 | // @ts-ignore 41 | onError(this.metricService, metricName, e) 42 | } 43 | } 44 | }, 45 | ) 46 | -------------------------------------------------------------------------------- /packages/mdc/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-mdc", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/common/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-common", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/config/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-config", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/logger/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-logger", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/metric/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-metric", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/svc-info/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-svc-info", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/metric/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-metric", 3 | "version": "1.6.1", 4 | "description": "Metric module for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/decorator-utils": "^4.5.4", 30 | "graphite": "^0.1.5", 31 | "measured-reporting": "^2.0.0" 32 | }, 33 | "devDependencies": { 34 | "@nestjs/testing": "^10.2.6", 35 | "@types/graphite": "^0.1.0", 36 | "@types/lodash": "^4.14.198", 37 | "@types/node": "^20.8.8", 38 | "lodash": "^4.17.21", 39 | "reflect-metadata": "^0.1.13" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 44 | }, 45 | "keywords": [ 46 | "js platform", 47 | "metric" 48 | ], 49 | "bugs": { 50 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 51 | }, 52 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /packages/connection-provider/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-connection-provider", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": {}, 30 | "transform": { 31 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 32 | "^.+\\.[jt]sx?$": [ 33 | "@swc/jest", 34 | { 35 | "jsc": { 36 | "parser": { 37 | "syntax": "typescript", 38 | "tsx": true, 39 | "decorators": true, 40 | "dynamicImport": true 41 | }, 42 | "transform": { 43 | "react": { 44 | "runtime": "automatic" 45 | }, 46 | "legacyDecorator": true, 47 | "decoratorMetadata": true 48 | } 49 | } 50 | } 51 | ] 52 | }, 53 | "extensionsToTreatAsEsm": [ 54 | ".ts", 55 | ".tsx" 56 | ], 57 | "transformIgnorePatterns": [ 58 | "/node_modules/" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/svc-info/src/test/ts/mock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-monorepo", 3 | "version": "0.0.0", 4 | "description": "QIWI Nestjs Enterprise", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "private": true, 9 | "scripts": { 10 | "clean": "lerna clean --yes && lerna run clean", 11 | "prebuild": "tsc -b packages/facade/tsconfig.es5.json", 12 | "build": "yarn clean && yarn prebuild && lerna run build --stream --concurrency 2", 13 | "bootstrap": "lerna bootstrap", 14 | "jest": "yarn infra:jest --runInBand --forceExit --detectOpenHandles", 15 | "test": "yarn jest", 16 | "test:report": "yarn test && yarn coveralls:push", 17 | "test:concurrent": "lerna run test --concurrency 1 --stream --no-prefix && yarn coverage:merge", 18 | "coverage:merge": "node scripts/js/coverage-merge.js", 19 | "codeclimate:push": "codeclimate-test-reporter < ./coverage/lcov.info", 20 | "coveralls:push": "cat ./coverage/lcov.info | coveralls || echo 'coveralls push failed :(' && exit 0", 21 | "docs": "typedoc packages/**/src/main", 22 | "postupdate": "yarn && yarn bootstrap && npx yarn-audit-fix && yarn build && yarn test", 23 | "lint": "lerna run lint", 24 | "lint:fix": "lerna run lint:fix", 25 | "format": "lerna run format" 26 | }, 27 | "devDependencies": { 28 | "codeclimate-test-reporter": "^0.5.1", 29 | "coveralls": "^3.1.1", 30 | "find-git-root": "^1.0.4", 31 | "jest": "^27.0.6", 32 | "lerna": "^4.0.0", 33 | "snazzy": "^9.0.0" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/connection-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-connection-provider", 3 | "version": "1.1.1", 4 | "description": "Nestjs connection-provider module", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "files": [ 27 | "README.md", 28 | "CHANGELOG.md", 29 | "target", 30 | "typings", 31 | "buildstamp.json" 32 | ], 33 | "dependencies": { 34 | "@nestjs/common": "^10.1.3", 35 | "@nestjs/core": "^10.1.3", 36 | "@qiwi/substrate": "^2.0.4", 37 | "lodash": "^4.17.21", 38 | "reflect-metadata": "^0.1.13", 39 | "rxjs": "^7.8.1", 40 | "tslib": "^2.6.1" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/qiwi/nestjs-enterprise.git" 45 | }, 46 | "keywords": [ 47 | "js platform", 48 | "connection-provider" 49 | ], 50 | "bugs": { 51 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 52 | }, 53 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 54 | "devDependencies": { 55 | "@types/lodash": "^4.14.198" 56 | }, 57 | "license": "MIT" 58 | } 59 | -------------------------------------------------------------------------------- /packages/facade/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": { 30 | "@qiwi/nestjs-enterprise-common": "/../common/src/main/ts" 31 | }, 32 | "transform": { 33 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 34 | "^.+\\.[jt]sx?$": [ 35 | "@swc/jest", 36 | { 37 | "jsc": { 38 | "parser": { 39 | "syntax": "typescript", 40 | "tsx": true, 41 | "decorators": true, 42 | "dynamicImport": true 43 | }, 44 | "transform": { 45 | "react": { 46 | "runtime": "automatic" 47 | }, 48 | "legacyDecorator": true, 49 | "decoratorMetadata": true 50 | } 51 | } 52 | } 53 | ] 54 | }, 55 | "extensionsToTreatAsEsm": [ 56 | ".ts", 57 | ".tsx" 58 | ], 59 | "transformIgnorePatterns": [ 60 | "/node_modules/" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/logger.service.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'node:os' 2 | 3 | import { Logwrap, mdc } from '@qiwi/logwrap' 4 | // @ts-ignore 5 | import { DEFAULT_NS } from '@qiwi/mware-context' 6 | import type { IConfig, ILogger } from '@qiwi/substrate' 7 | import { LogLevel } from '@qiwi/substrate' 8 | 9 | import { 10 | Inject, 11 | Injectable, 12 | LoggerService as LoggerServiceNest, 13 | } from '@nestjs/common' 14 | 15 | import { createAppPipe } from './app.pipe' 16 | import type { TLoggerPipe } from './interfaces' 17 | import { createLoggerPipe } from './logger.pipe' 18 | import createWinstonLogger from './winston' 19 | 20 | @Injectable() 21 | export class LoggerService 22 | extends Logwrap 23 | implements ILogger, LoggerServiceNest 24 | { 25 | // @ts-ignore 26 | constructor( 27 | @Inject('ILoggerPipeline') pipeline: TLoggerPipe[], 28 | @Inject('IConfigService') config: IConfig, 29 | ) { 30 | const loggerConfig = config.get('logger') 31 | super({ 32 | level: loggerConfig.level, 33 | pipeline: [ 34 | mdc({ ns: DEFAULT_NS }), 35 | createAppPipe(config.get('name'), config.get('version'), os.hostname()), 36 | ...pipeline, 37 | createLoggerPipe(createWinstonLogger(loggerConfig)), 38 | ], 39 | }) 40 | } 41 | 42 | log = (...args: any[]): void => { 43 | return this.info(...args) 44 | } 45 | 46 | push = (entry: { 47 | meta: Record 48 | level: LogLevel 49 | input: any[] 50 | }) => { 51 | // @ts-ignore 52 | LoggerService.perform(this.level, this.pipeline, entry) 53 | } 54 | 55 | verbose(...args: any[]): void { 56 | return this.debug(...args) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/common/src/main/ts/port/index.ts: -------------------------------------------------------------------------------- 1 | import { constructDecorator, METHOD, PARAM } from '@qiwi/decorator-utils' 2 | 3 | import { 4 | CanActivate, 5 | createParamDecorator, 6 | ExecutionContext, 7 | Inject, 8 | Injectable, 9 | SetMetadata, 10 | UseGuards, 11 | } from '@nestjs/common' 12 | import { Reflector } from '@nestjs/core' 13 | 14 | @Injectable() 15 | export class PortCanActivate implements CanActivate { 16 | constructor(@Inject(Reflector) private reflector: Reflector) {} 17 | 18 | canActivate(context: ExecutionContext): boolean { 19 | const port = this.reflector.get('port', context.getHandler()) 20 | return ( 21 | context.switchToHttp().getRequest().connection.localPort.toString() === 22 | port 23 | ) 24 | } 25 | } 26 | 27 | export const PortParam = createParamDecorator( 28 | (_data: unknown, ctx: ExecutionContext) => { 29 | return ctx.switchToHttp().getRequest().connection.localPort 30 | }, 31 | ) 32 | 33 | export const Port = (args?: string) => { 34 | return constructDecorator(({ targetType, proto, propName, paramIndex }) => { 35 | if (targetType === METHOD) { 36 | UseGuards(PortCanActivate)( 37 | proto, 38 | // @ts-ignore 39 | propName, 40 | // @ts-ignore 41 | Object.getOwnPropertyDescriptor(proto, propName), 42 | ) 43 | SetMetadata('port', args)( 44 | proto, 45 | // @ts-ignore 46 | propName, 47 | // @ts-ignore 48 | Object.getOwnPropertyDescriptor(proto, propName), 49 | ) 50 | } 51 | 52 | if (targetType === PARAM) { 53 | // @ts-ignore 54 | return PortParam()(proto, propName, paramIndex) 55 | } 56 | })() 57 | } 58 | -------------------------------------------------------------------------------- /packages/logger/src/test/ts/logger.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { deepEqual, equal, notEqual } from 'node:assert' 2 | import { describe, it, mock } from 'node:test' 3 | 4 | import type { ILogEntry } from '@qiwi/logwrap' 5 | import type { ILogger } from '@qiwi/substrate' 6 | 7 | import { createLoggerPipe } from '../../main/ts/logger.pipe' 8 | import createWinstonLogger from '../../main/ts/winston' 9 | 10 | const loggerConfig = { 11 | dir: 'somepath', 12 | level: 'debug', 13 | maxsize: 157_286_400, 14 | datePattern: 'YYYY-MM-DD', 15 | appJsonFilename: 'application-json.log', 16 | appFilename: 'testlog.log', 17 | maxFiles: 10, 18 | tailable: true, 19 | zippedArchive: true, 20 | } 21 | 22 | const dummy = () => { 23 | /* noop */ 24 | } 25 | 26 | describe('createAppPipe', () => { 27 | it('is defined', () => notEqual(createLoggerPipe, undefined)) 28 | 29 | it('creates pipe', () => { 30 | const pipe = createLoggerPipe(createWinstonLogger(loggerConfig)) 31 | equal(typeof pipe, 'function') 32 | }) 33 | 34 | it('pipe calls log and ', () => { 35 | const log = mock.fn() 36 | const loggerMock: ILogger = { 37 | log, 38 | trace: dummy, 39 | debug: dummy, 40 | info: dummy, 41 | warn: dummy, 42 | error: dummy, 43 | } 44 | const pipe = createLoggerPipe(loggerMock) 45 | const input = ['foo', 'bar', 'baz'] 46 | const inputEntry: ILogEntry = { 47 | level: 'info', 48 | input, 49 | meta: {}, 50 | } 51 | deepEqual(pipe(inputEntry), inputEntry) 52 | deepEqual(log.mock.calls.at(0)?.arguments.at(0), { 53 | level: inputEntry.level, 54 | message: input.join(' '), 55 | meta: inputEntry.meta, 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /packages/consul/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-consul", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": { 30 | "@qiwi/nestjs-enterprise-connection-provider": "/../connection-provider/src/main/ts" 31 | }, 32 | "transform": { 33 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 34 | "^.+\\.[jt]sx?$": [ 35 | "@swc/jest", 36 | { 37 | "jsc": { 38 | "parser": { 39 | "syntax": "typescript", 40 | "tsx": true, 41 | "decorators": true, 42 | "dynamicImport": true 43 | }, 44 | "transform": { 45 | "react": { 46 | "runtime": "automatic" 47 | }, 48 | "legacyDecorator": true, 49 | "decoratorMetadata": true 50 | } 51 | } 52 | } 53 | ] 54 | }, 55 | "extensionsToTreatAsEsm": [ 56 | ".ts", 57 | ".tsx" 58 | ], 59 | "transformIgnorePatterns": [ 60 | "/node_modules/" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/svc-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-svc-info", 3 | "version": "1.4.1", 4 | "description": "Service-info controller for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "resolve-cwd": "^3.0.0", 30 | "rxjs": "^7.8.1", 31 | "tslib": "^2.6.1" 32 | }, 33 | "devDependencies": { 34 | "@nestjs/swagger": "^7.1.8", 35 | "@nestjs/testing": "^10.2.6", 36 | "@types/bluebird": "^3.5.38", 37 | "@types/rimraf": "^4.0.5", 38 | "@types/supertest": "^2.0.12", 39 | "class-transformer": "^0.5.1", 40 | "class-validator": "^0.14.0", 41 | "reflect-metadata": "^0.1.13", 42 | "rimraf": "^5.0.1", 43 | "supertest": "^6.3.3" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 48 | }, 49 | "keywords": [ 50 | "js platform", 51 | "diag" 52 | ], 53 | "bugs": { 54 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 55 | }, 56 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 57 | "license": "MIT" 58 | } 59 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/mock/server.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var thrift = require('thrift') 3 | var Calculator = require('./gen-nodejs/Calculator.cjs') 4 | var ttypes = require('./gen-nodejs/tutorial_types.cjs') 5 | var SharedStruct = require('./gen-nodejs/shared_types.cjs').SharedStruct 6 | 7 | var data = {} 8 | 9 | var server = thrift.createServer(Calculator, { 10 | ping: function (result) { 11 | console.log('ping()') 12 | result(null) 13 | }, 14 | 15 | add: function (n1, n2, result) { 16 | console.log('add(', n1, ',', n2, ')') 17 | result(null, n1 + n2) 18 | }, 19 | 20 | calculate: function (logid, work, result) { 21 | console.log('calculate(', logid, ',', work, ')') 22 | 23 | var val = 0 24 | if (work.op == ttypes.Operation.ADD) { 25 | val = work.num1 + work.num2 26 | } else if (work.op === ttypes.Operation.SUBTRACT) { 27 | val = work.num1 - work.num2 28 | } else if (work.op === ttypes.Operation.MULTIPLY) { 29 | val = work.num1 * work.num2 30 | } else if (work.op === ttypes.Operation.DIVIDE) { 31 | if (work.num2 === 0) { 32 | var x = new ttypes.InvalidOperation() 33 | x.whatOp = work.op 34 | x.why = 'Cannot divide by 0' 35 | result(x) 36 | return 37 | } 38 | val = work.num1 / work.num2 39 | } else { 40 | var x = new ttypes.InvalidOperation() 41 | x.whatOp = work.op 42 | x.why = 'Invalid operation' 43 | result(x) 44 | return 45 | } 46 | 47 | var entry = new SharedStruct() 48 | entry.key = logid 49 | entry.value = '' + val 50 | data[logid] = entry 51 | 52 | result(null, val) 53 | }, 54 | 55 | getStruct: function (key, result) { 56 | result(null, data[key]) 57 | }, 58 | 59 | zip: function () {}, 60 | }) 61 | 62 | module.exports = server 63 | -------------------------------------------------------------------------------- /packages/thrift/src/main/ts/thrift.decorators.ts: -------------------------------------------------------------------------------- 1 | import * as thrift from 'thrift' 2 | import { Inject } from '@nestjs/common' 3 | 4 | import type { 5 | IThriftClientProvider, 6 | IThriftConnectionOpts, 7 | IThriftServiceProfile, 8 | } from './interfaces' 9 | 10 | type IThriftFactoryArgs = [ 11 | any, 12 | any, 13 | IThriftServiceProfile | string, 14 | IThriftConnectionOpts | undefined, 15 | ] 16 | 17 | // TODO reflect.metadata? 18 | const factoryArgs: IThriftFactoryArgs[] = [] 19 | const cache = new WeakMap() 20 | 21 | export const INJECT_THRIFT_SERVICE = Symbol('InjectThriftService') 22 | 23 | /** 24 | * Thrift client decorator 25 | * 26 | * @param Client 27 | * @param serviceProfile 28 | * @param connOpts 29 | * @constructor 30 | * @return Inject thrift client service 31 | */ 32 | export const InjectThriftService = ( 33 | Client: thrift.TClientConstructor, 34 | serviceProfile: IThriftServiceProfile | string, 35 | connOpts?: IThriftConnectionOpts, 36 | ) => { 37 | const inject = Inject(INJECT_THRIFT_SERVICE) 38 | 39 | return (...args: Parameters) => { 40 | factoryArgs.push([args[0], Client, serviceProfile, connOpts]) 41 | 42 | return inject(...args) 43 | } 44 | } 45 | 46 | export const thriftServiceFactory = ( 47 | thriftClientService: IThriftClientProvider, 48 | ) => { 49 | const args = factoryArgs.pop() 50 | if (!args) { 51 | return 52 | } 53 | 54 | const [_Parent, Client, serviceProfile, connOpts] = args as IThriftFactoryArgs 55 | const cached = cache.get(Client) // TODO use composite key 56 | 57 | if (cached) { 58 | return cached 59 | } 60 | 61 | const thriftService = thriftClientService.getClient( 62 | serviceProfile, 63 | Client, 64 | connOpts, 65 | ) 66 | cache.set(Client, thriftService) 67 | 68 | return thriftService 69 | } 70 | -------------------------------------------------------------------------------- /packages/consul/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-consul", 3 | "version": "1.3.1", 4 | "description": "Nestjs module for working with Consul", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/consul-service-discovery": "^1.10.6", 30 | "@qiwi/substrate": "^2.0.4", 31 | "@types/lodash": "^4.14.198", 32 | "reflect-metadata": "^0.1.13", 33 | "rxjs": "^7.8.1", 34 | "tslib": "^2.6.1" 35 | }, 36 | "devDependencies": { 37 | "@nestjs/testing": "^10.2.6", 38 | "@qiwi/nestjs-enterprise-connection-provider": "workspace:*", 39 | "@types/bluebird": "^3.5.38", 40 | "@types/consul": "^0.40.0", 41 | "@types/lodash": "^4.14.198", 42 | "class-transformer": "^0.5.1", 43 | "class-validator": "^0.14.0", 44 | "lodash": "^4.17.21" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/qiwi/nestjs-enterprise.git" 49 | }, 50 | "keywords": [ 51 | "js platform", 52 | "consul" 53 | ], 54 | "bugs": { 55 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 56 | }, 57 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 58 | "license": "MIT" 59 | } 60 | -------------------------------------------------------------------------------- /packages/common/src/main/ts/request-size/index.ts: -------------------------------------------------------------------------------- 1 | import { constructDecorator, METHOD, PARAM } from '@qiwi/decorator-utils' 2 | 3 | import { 4 | CanActivate, 5 | createParamDecorator, 6 | ExecutionContext, 7 | Inject, 8 | Injectable, 9 | SetMetadata, 10 | UseGuards, 11 | } from '@nestjs/common' 12 | import { Reflector } from '@nestjs/core' 13 | 14 | const getRequestSize = (ctx: ExecutionContext) => 15 | ctx.switchToHttp().getRequest().socket.bytesRead 16 | 17 | @Injectable() 18 | export class RequestSizeCanActivate implements CanActivate { 19 | constructor(@Inject(Reflector) private reflector: Reflector) {} 20 | 21 | canActivate(context: ExecutionContext): boolean { 22 | const requestSizeLimit = this.reflector.get( 23 | 'requestSizeLimit', 24 | context.getHandler(), 25 | ) 26 | const requestSize = getRequestSize(context) 27 | return requestSizeLimit > requestSize 28 | } 29 | } 30 | 31 | const RequestSizeGuard = ( 32 | proto: any, 33 | propName: any, 34 | descriptor: any, 35 | requestSizeLimit?: number, 36 | ) => { 37 | UseGuards(RequestSizeCanActivate)(proto, propName, descriptor) 38 | SetMetadata('requestSizeLimit', requestSizeLimit)(proto, propName, descriptor) 39 | } 40 | 41 | export const RequestSizeParam = createParamDecorator( 42 | (_data: unknown, ctx: ExecutionContext) => getRequestSize(ctx), 43 | ) 44 | 45 | export const RequestSize = constructDecorator( 46 | ({ 47 | targetType, 48 | descriptor, 49 | proto, 50 | propName, 51 | paramIndex, 52 | args: [requestSizeLimit], 53 | }) => { 54 | if (targetType === METHOD) { 55 | RequestSizeGuard(proto, propName, descriptor, requestSizeLimit) 56 | } 57 | 58 | if (targetType === PARAM) { 59 | // @ts-ignore 60 | RequestSizeParam()(proto, propName, paramIndex) 61 | } 62 | }, 63 | ) 64 | -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-logger", 3 | "version": "1.6.1", 4 | "description": "Logger module for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/logwrap": "^1.5.1", 30 | "@qiwi/mware-context": "^1.14.0", 31 | "@qiwi/substrate": "^2.0.4", 32 | "@types/winston": "^2.4.4", 33 | "fast-luhn": "^2.0.1", 34 | "fast-safe-stringify": "^2.1.1", 35 | "lodash": "^4.17.21", 36 | "rxjs": "^7.8.1", 37 | "tslib": "^2.6.1", 38 | "winston": "^3.10.0" 39 | }, 40 | "devDependencies": { 41 | "@nestjs/testing": "^10.2.6", 42 | "@types/lodash": "^4.14.198", 43 | "class-transformer": "^0.5.1", 44 | "class-validator": "^0.14.0", 45 | "reflect-metadata": "^0.1.13", 46 | "tempy": "^3.1.0" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 51 | }, 52 | "keywords": [ 53 | "js platform", 54 | "logger" 55 | ], 56 | "bugs": { 57 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 58 | }, 59 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 60 | "license": "MIT" 61 | } 62 | -------------------------------------------------------------------------------- /packages/connection-provider/src/main/ts/interfaces.ts: -------------------------------------------------------------------------------- 1 | export type IConnectionParams = { 2 | host: string 3 | port: number 4 | } 5 | 6 | export interface IDiscoverable { 7 | /** 8 | * Get connection params by service name. 9 | * 10 | * @param serviceName 11 | * @return Host and port to connection 12 | */ 13 | getConnectionParams( 14 | serviceName: string, 15 | ): Promise 16 | } 17 | 18 | export const enum TServiceType { 19 | THRIFT = 'thrift', 20 | HTTP = 'http', 21 | DB = 'db', 22 | } 23 | 24 | export const enum DiscoveryType { 25 | CONSUL = 'consul', 26 | ENDPOINT = 'endpoint', 27 | } 28 | 29 | export type IConsulDiscovery = { 30 | type: DiscoveryType.CONSUL 31 | serviceName: string 32 | } 33 | 34 | export type IEndpointDiscovery = { 35 | type: DiscoveryType.ENDPOINT 36 | endpoints: Array 37 | } 38 | 39 | export type TDiscovery = IConsulDiscovery | IEndpointDiscovery 40 | 41 | export interface IServiceDiscoverable { 42 | discovery: TDiscovery 43 | } 44 | 45 | export interface IThriftServiceProfile extends IServiceDiscoverable { 46 | type: TServiceType.THRIFT 47 | thriftServiceName: string 48 | } 49 | 50 | export const enum CredType { 51 | USERANDPASSWORD = 'username-and-password', 52 | } 53 | 54 | export type IUsernameAndPasswordCreds = { 55 | type: CredType.USERANDPASSWORD 56 | username: string 57 | password: string 58 | } 59 | 60 | export type ICreds = IUsernameAndPasswordCreds 61 | 62 | export interface IServiceProfile { 63 | type: TServiceType 64 | creds?: ICreds 65 | } 66 | 67 | export interface IDbServiceProfile extends IServiceProfile { 68 | type: TServiceType.DB 69 | discovery: IEndpointDiscovery 70 | creds: IUsernameAndPasswordCreds 71 | [key: string]: any 72 | } 73 | 74 | export type IServiceDeclaration = IThriftServiceProfile | IDbServiceProfile 75 | -------------------------------------------------------------------------------- /packages/common/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-common 2 | *Common assets* 3 | 4 | ## Install 5 | ```shell script 6 | yarn add @qiwi/nestjs-enterprise-common 7 | ``` 8 | 9 | ## Decorators 10 | ### @Port 11 | ```typescript 12 | import { Controller, Get} from '@nestjs/common' 13 | import { Port } from '@qiwi/nestjs-enterprise-common' 14 | 15 | @Controller() 16 | export class CardInfoController { 17 | 18 | @Port('8080') 19 | @Get('only8080') 20 | async test(@Port() port: number) { 21 | return port 22 | } 23 | } 24 | ``` 25 | 26 | - When used as a method decorator or class decorator, works like [guard](https://docs.nestjs.com/guards), letting only the specified port. 27 | - When used as a parameter decorator, extracts port value from request.socket data. 28 | 29 | ### @RequestSize 30 | ```typescript 31 | import { Controller, Post,} from '@nestjs/common' 32 | import { RequestSize } from '@qiwi/nestjs-enterprise-common' 33 | 34 | // Class decorator 35 | @Controller() 36 | @RequestSize(512) 37 | export class TestClassController { 38 | @Post('req-limit-512-class') 39 | async test(@RequestSize() size: number) { 40 | return size 41 | } 42 | } 43 | 44 | // Method decorator 45 | @Controller() 46 | export class TestMethodController { 47 | @RequestSize(512) 48 | @Post('req-limit-512-method') 49 | async test(@RequestSize() size: number) { 50 | return size 51 | } 52 | } 53 | 54 | // Parameter decorator 55 | @Controller() 56 | export class TestParamController { 57 | @Post('return-req-size') 58 | async test(@RequestSize() size: number) { 59 | return size 60 | } 61 | } 62 | ``` 63 | 64 | - When used as a method decorator or class decorator, work like [guard](https://docs.nestjs.com/guards), allows a request that is smaller than the specified size. 65 | - When used as a parameter decorator, get size of request. 66 | 67 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/common/) 68 | -------------------------------------------------------------------------------- /packages/thrift/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-thrift", 3 | "version": "1.3.1", 4 | "description": "thrift module for nestjs enterprise", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/substrate": "^2.0.4", 30 | "@types/thrift": "^0.10.12", 31 | "generic-pool": "^3.9.0", 32 | "inside-out-promise": "^2.1.5", 33 | "lodash": "^4.17.21", 34 | "reflect-metadata": "^0.1.13", 35 | "rxjs": "^7.8.1", 36 | "thrift": "0.18.1", 37 | "tslib": "^2.6.1" 38 | }, 39 | "devDependencies": { 40 | "@nestjs/testing": "^10.2.6", 41 | "@qiwi/nestjs-enterprise-config": "workspace:*", 42 | "@qiwi/nestjs-enterprise-connection-provider": "workspace:*", 43 | "@qiwi/nestjs-enterprise-consul": "workspace:*", 44 | "@qiwi/nestjs-enterprise-logger": "workspace:*", 45 | "@types/generic-pool": "^3.8.1" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/qiwi/nestjs-enterprise.git" 50 | }, 51 | "keywords": [ 52 | "js platform", 53 | "thrift" 54 | ], 55 | "bugs": { 56 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 57 | }, 58 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 59 | "license": "MIT" 60 | } 61 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qiwi/nestjs-enterprise-common", 3 | "version": "1.4.1", 4 | "description": "QIWI Nestjs enterprise common assets", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public", 8 | "type": "module", 9 | "main": "./target/cjs/index.cjs", 10 | "module": "./target/esm/index.mjs", 11 | "types": "./target/esm/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "require": "./target/cjs/index.cjs", 15 | "import": "./target/esm/index.mjs", 16 | "types": "./target/esm/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "target/cjs/**/*", 21 | "target/esm/**/*", 22 | "target/buildstamp.json" 23 | ] 24 | }, 25 | "exports": "./src/main/ts/index.ts", 26 | "dependencies": { 27 | "@nestjs/common": "^10.1.3", 28 | "@nestjs/core": "^10.1.3", 29 | "@qiwi/decorator-utils": "^4.5.4", 30 | "graphite": "^0.1.5", 31 | "reflect-metadata": "^0.1.13", 32 | "rxjs": "^7.8.1", 33 | "tslib": "^2.6.1" 34 | }, 35 | "devDependencies": { 36 | "@nestjs/platform-express": "^10.1.3", 37 | "@nestjs/testing": "^10.2.6", 38 | "@types/express": "^4.17.17", 39 | "@types/graphite": "^0.1.0", 40 | "@types/lodash": "^4.14.198", 41 | "@types/supertest": "^2.0.12", 42 | "axios": "^1.6.0", 43 | "class-transformer": "^0.5.1", 44 | "class-validator": "^0.14.0", 45 | "express": "^4.18.2", 46 | "inside-out-promise": "^2.1.5", 47 | "lodash": "^4.17.21", 48 | "supertest": "^6.3.3" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "git+https://github.com/qiwi/nestjs-enterprise.git" 53 | }, 54 | "keywords": [ 55 | "decorator", 56 | "common" 57 | ], 58 | "bugs": { 59 | "url": "https://github.com/qiwi/nestjs-enterprise/issues" 60 | }, 61 | "homepage": "https://github.com/qiwi/nestjs-enterprise#readme", 62 | "license": "MIT" 63 | } 64 | -------------------------------------------------------------------------------- /packages/thrift/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "@qiwi/nestjs-enterprise-thrift", 3 | "injectGlobals": false, 4 | "clearMocks": true, 5 | "resetMocks": true, 6 | "testEnvironment": "node", 7 | "testMatch": [ 8 | "/src/test/[jt]s/**/*.[jt]s?(x)" 9 | ], 10 | "testPathIgnorePatterns": [ 11 | "__mocks__", 12 | "__snapshots__" 13 | ], 14 | "collectCoverage": true, 15 | "collectCoverageFrom": [ 16 | "/src/main/[jt]s/**/*.[jt]s?(x)" 17 | ], 18 | "coverageDirectory": "/target/coverage", 19 | "coveragePathIgnorePatterns": [ 20 | "/node_modules", 21 | "/target" 22 | ], 23 | "coverageProvider": "v8", 24 | "coverageReporters": [ 25 | "json", 26 | "lcov" 27 | ], 28 | "snapshotResolver": "@packasso/jest-snapshot-resolver", 29 | "moduleNameMapper": { 30 | "@qiwi/nestjs-enterprise-config": "/../config/src/main/ts", 31 | "@qiwi/nestjs-enterprise-connection-provider": "/../connection-provider/src/main/ts", 32 | "@qiwi/nestjs-enterprise-consul": "/../consul/src/main/ts", 33 | "@qiwi/nestjs-enterprise-logger": "/../logger/src/main/ts" 34 | }, 35 | "transform": { 36 | "^.+\\.svg$": "@packasso/jest-svgr-transformer", 37 | "^.+\\.[jt]sx?$": [ 38 | "@swc/jest", 39 | { 40 | "jsc": { 41 | "parser": { 42 | "syntax": "typescript", 43 | "tsx": true, 44 | "decorators": true, 45 | "dynamicImport": true 46 | }, 47 | "transform": { 48 | "react": { 49 | "runtime": "automatic" 50 | }, 51 | "legacyDecorator": true, 52 | "decoratorMetadata": true 53 | } 54 | } 55 | } 56 | ] 57 | }, 58 | "extensionsToTreatAsEsm": [ 59 | ".ts", 60 | ".tsx" 61 | ], 62 | "transformIgnorePatterns": [ 63 | "/node_modules/" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /packages/config/src/main/ts/uniconfig-json-schema-validation-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync as nodeReadFileSync, promises } from 'node:fs' 2 | import { resolve } from 'node:path' 3 | 4 | import type { IAny, IContext } from '@qiwi/uniconfig' 5 | import { pipe as ajvPipe } from '@qiwi/uniconfig-plugin-ajv' 6 | 7 | type TOptionalValidationPluginInput = any 8 | 9 | type TOptionalValidationPluginOpts = { 10 | deps?: { 11 | readFileSync: (path: string) => Buffer 12 | readFile: (path: string) => Promise 13 | } 14 | schemaPath?: string 15 | } 16 | 17 | const optionalValidationPluginDefaultDeps = { 18 | readFile: promises.readFile, 19 | readFileSync: nodeReadFileSync, 20 | } 21 | 22 | const readJsonFactory = (deps: TOptionalValidationPluginInput['deps']) => ({ 23 | readJson: (path: string) => 24 | deps 25 | .readFile(path) 26 | .then((d: Buffer) => d.toString()) 27 | .then(JSON.parse), 28 | readJsonSync: (path: string) => 29 | JSON.parse(deps.readFileSync(path).toString()), 30 | }) 31 | 32 | export const uniconfigJsonSchemaValidationPluginFactory = ( 33 | opts: TOptionalValidationPluginOpts, 34 | ) => { 35 | const { deps = optionalValidationPluginDefaultDeps, schemaPath } = opts 36 | const { readJson, readJsonSync } = readJsonFactory(deps) 37 | 38 | return { 39 | name: 'json-schema-validation', 40 | async handle( 41 | context: IContext, 42 | data: TOptionalValidationPluginInput, 43 | ): Promise { 44 | return schemaPath 45 | ? ajvPipe.handle(context, { 46 | data: data, 47 | schema: await readJson(resolve(schemaPath)), 48 | }) 49 | : data 50 | }, 51 | handleSync(context: IContext, data: TOptionalValidationPluginInput): IAny { 52 | return schemaPath 53 | ? ajvPipe.handleSync(context, { 54 | data: data, 55 | schema: readJsonSync(resolve(schemaPath)), 56 | }) 57 | : data 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/connection-provider/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-connection-provider 2 | Nestjs module for getting endpoints from serviceProfile 3 | 4 | ## Installation 5 | Requires following packages to be installed 6 | - [@qiwi/nestjs-enterprise-consul](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/consul) 7 | 8 | ```shell script 9 | yarn add @qiwi/nestjs-enterprise-connection-provider 10 | ``` 11 | 12 | ## Configuration 13 | Imports 14 | ```typescript 15 | import { Module } from '@nestjs/common' 16 | import { ConnectionProviderModule } from '@qiwi/nestjs-enterprise-connection-provider' 17 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger-nestjs' 18 | import { ConsulModule } from '@qiwi/nestjs-enterprise-consul-nestjs' 19 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config-nestjs' 20 | 21 | @Module({ 22 | imports: [ 23 | ConsulModule, 24 | ConnectionProviderModule, 25 | // and so on 26 | ], 27 | controllers: [], 28 | providers: [], 29 | }) 30 | export class AppModule {} 31 | ``` 32 | 33 | ## Usage 34 | ```typescript 35 | @Injectable() 36 | export class ThriftClientProvider implements IThriftClientProvider { 37 | constructor( 38 | @Inject('IConnectionProvider') private connectionProvider: IConnectionProvider, 39 | @Inject('IConfig') private config: IConfig 40 | ) {} 41 | 42 | private async createConnection( 43 | serviceProfile: IThriftServiceProfile | string, 44 | ): Promise { 45 | const profile = serviceProfile ? this.config.get(serviceProfile) : serviceProfile 46 | const connectionParams = await this.connectionProvider.getConnectionParams(profile) 47 | //...etc 48 | } 49 | } 50 | ``` 51 | 52 | ## API 53 | ### Class ConnectionProviderModule 54 | Exports `IConnectionProvider` with token `IConnectionProviderService` 55 | ### Class ConnectionProviderService 56 | #### getConnectionParams( serviceProfile: IServiceDeclaration ): Promise 57 | 58 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/connection-provider/) 59 | -------------------------------------------------------------------------------- /packages/config/src/main/ts/config.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ASYNC, 3 | Config, 4 | IConfig as IConfigService, 5 | rollupPlugin, 6 | } from '@qiwi/uniconfig' 7 | 8 | import { uniconfigJsonSchemaValidationPluginFactory } from './uniconfig-json-schema-validation-plugin/index' 9 | import { resolveSchemaPath } from './uniconfig-json-schema-validation-plugin/resolve-schema-path' 10 | 11 | export const DEFAULT_LOCAL_CONFIG_PATH = '/config/local.json' 12 | export const DEFAULT_KUBE_CONFIG_PATH = '/config/kube.json' 13 | 14 | export const resolveConfigPath = ( 15 | path?: string, 16 | local?: boolean, 17 | env?: string, 18 | ): string | string[][] => { 19 | if (path) { 20 | return path 21 | } 22 | 23 | return local 24 | ? DEFAULT_LOCAL_CONFIG_PATH 25 | : env 26 | ? [[`/config/${env}.json`], [DEFAULT_KUBE_CONFIG_PATH]] 27 | : DEFAULT_KUBE_CONFIG_PATH 28 | } 29 | 30 | const normalizeOpts = (opts?: string | Record) => { 31 | if (typeof opts === 'string' || opts === undefined) { 32 | return { 33 | mode: ASYNC, 34 | data: resolveConfigPath( 35 | opts, 36 | !!process.env.LOCAL, 37 | process.env.ENVIRONMENT_PROFILE_NAME, 38 | ), 39 | pipeline: 'path>file>json>datatree>json-schema-validation', 40 | } 41 | } 42 | 43 | return opts 44 | } 45 | 46 | export class ConfigService extends Config implements IConfigService { 47 | constructor(opts?: string | Record, schemaPath?: string) { 48 | rollupPlugin( 49 | uniconfigJsonSchemaValidationPluginFactory({ 50 | schemaPath: resolveSchemaPath({ 51 | path: opts, 52 | schemaPath, 53 | }), 54 | }), 55 | ) 56 | 57 | super(normalizeOpts(opts)) 58 | } 59 | } 60 | 61 | export type { IConfig as IConfigService } from '@qiwi/uniconfig' 62 | 63 | export const configServiceFactory = async ( 64 | options: { schemaPath?: string } & Record = {}, 65 | ) => { 66 | const service = new ConfigService( 67 | options.path || options.config, 68 | options.schemaPath, 69 | ) 70 | await service.ready 71 | 72 | return service 73 | } 74 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/metric.module.ts: -------------------------------------------------------------------------------- 1 | import type { IConfig, ILogger } from '@qiwi/substrate' 2 | 3 | import { DynamicModule, Global, Module } from '@nestjs/common' 4 | 5 | import { GraphiteService, MetricService } from './' 6 | import { IGraphiteService } from './graphite.servise.interface' 7 | 8 | @Global() 9 | @Module({ 10 | providers: [ 11 | { 12 | provide: 'IGraphiteService', 13 | useFactory: (config: IConfig) => { 14 | const url = config.get('graphite.url') 15 | return new GraphiteService(url) 16 | }, 17 | inject: ['IConfigService'], 18 | }, 19 | { 20 | provide: 'IMetricService', 21 | useFactory: ( 22 | graphiteService: IGraphiteService, 23 | config: IConfig, 24 | logger: ILogger, 25 | ) => { 26 | const options = { 27 | prefix: config.get('metric.prefix'), 28 | interval: config.get('metric.interval'), 29 | } 30 | return new MetricService(graphiteService, options, logger) 31 | }, 32 | inject: ['IGraphiteService', 'IConfigService', 'ILogger'], 33 | }, 34 | ], 35 | exports: ['IGraphiteService', 'IMetricService'], 36 | }) 37 | export class MetricModule { 38 | static register( 39 | graphiteUrlOrService: string | IGraphiteService, 40 | metricsConfig: { prefix: string; interval: number }, 41 | ): DynamicModule { 42 | return { 43 | module: MetricModule, 44 | providers: [ 45 | { 46 | provide: 'IGraphiteService', 47 | useFactory: () => { 48 | if (typeof graphiteUrlOrService == 'string') { 49 | return new GraphiteService(graphiteUrlOrService) 50 | } 51 | return graphiteUrlOrService 52 | }, 53 | }, 54 | { 55 | provide: 'IMetricService', 56 | useFactory: (graphiteService, logger) => { 57 | return new MetricService(graphiteService, metricsConfig, logger) 58 | }, 59 | inject: ['IGraphiteService', 'ILogger'], 60 | }, 61 | ], 62 | exports: ['IGraphiteService', 'IMetricService'], 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/svc-info/src/main/ts/svc-info.controller.ts: -------------------------------------------------------------------------------- 1 | import { promises } from 'node:fs' 2 | import { createRequire } from 'node:module' 3 | 4 | import type { ILogger } from '@qiwi/substrate' 5 | 6 | import { Controller, Get, Inject, Optional } from '@nestjs/common' 7 | import { ApiExcludeEndpoint } from '@nestjs/swagger' 8 | import resolveCwd from 'resolve-cwd' 9 | 10 | import type { ISvcInfoModuleOpts } from './interfaces' 11 | 12 | const readJson = (path: string) => 13 | promises.readFile(path, 'utf-8').then((d) => JSON.parse(d.toString())) 14 | 15 | @Controller('/svc-info') 16 | export class SvcInfoController { 17 | constructor( 18 | @Inject('ILogger') private logger: ILogger, 19 | @Optional() 20 | @Inject('ISvcInfoModuleOpts') 21 | private opts: ISvcInfoModuleOpts = {}, 22 | ) {} 23 | 24 | @Get('uptime') 25 | @ApiExcludeEndpoint() 26 | uptime() { 27 | const uptime = Math.floor(process.uptime()) 28 | 29 | const minInSec = 60 30 | const hoursInSec = 60 * minInSec 31 | const dayInSec = 24 * hoursInSec 32 | 33 | const days = Math.floor(uptime / dayInSec) 34 | const restSecWithoutDays = uptime % dayInSec 35 | const hours = Math.floor(restSecWithoutDays / hoursInSec) 36 | const restSecWithoutHoursAndDays = uptime % hoursInSec 37 | const min = Math.floor(restSecWithoutHoursAndDays / minInSec) 38 | const sec = uptime % minInSec 39 | 40 | return `Uptime is ${days} days, ${hours} hours, ${min} mins, ${sec} secs` 41 | } 42 | 43 | @Get('version') 44 | @ApiExcludeEndpoint() 45 | async version() { 46 | const { version, name } = 47 | this.opts.package || 48 | (await readJson(resolveCwd(this.opts.packagePath || './package.json'))) 49 | return { version, name } 50 | } 51 | 52 | @Get('buildstamp') 53 | @ApiExcludeEndpoint() 54 | buildInfo() { 55 | const path = this.opts.path || './buildstamp.json' 56 | try { 57 | return readJson(resolveCwd(path)) 58 | } catch (e) { 59 | const message = `required buildstamp on path ${path} is malformed or unreachable` 60 | this.logger.warn(message, e) 61 | return message 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/decorators/request-rate.decorator.ts: -------------------------------------------------------------------------------- 1 | import { constructDecorator } from '@qiwi/decorator-utils' 2 | 3 | import { Inject } from '@nestjs/common' 4 | 5 | import { isPromise } from '../utills' 6 | 7 | /** 8 | * The decorator collects statistically significant query processing times skewed toward the last 5 minutes to examine their distribution. 9 | * 10 | * - **count** - The total of all values added to the meter. 11 | * - **1MinuteRate** - The rate of the meter biased towards the last 1 minute. 12 | * - **5MinuteRate** - The rate of the meter biased towards the last 5 minute. 13 | * - **15MinuteRate** - The rate of the meter biased towards the last 15 minute. 14 | * - **min** - The lowest observed value. 15 | * - **max** - The highest observed value. 16 | * - **sum** - The sum of all observed values. 17 | * - **variance** - The variance of all observed values. 18 | * - **mean** - The average of all observed values. 19 | * - **stddev** - The stddev of all observed values. 20 | * - **count** - The number of observed values. 21 | * - **median** - 50% of all values in the resevoir are at or below this value. 22 | * - **p75** - See median, 75% percentile. 23 | * - **p95** - See median, 95% percentile. 24 | * - **p99** - See median, 99% percentile. 25 | * - **p999** - See median, 99.9% percentile. 26 | */ 27 | export const RequestRateDecorator = constructDecorator( 28 | ({ target, proto, args: [metricName] }) => { 29 | const injectMetric = Inject('IMetricService') 30 | injectMetric(proto, 'metricService') 31 | return function (...args: Array) { 32 | const start = Date.now() 33 | // @ts-ignore 34 | const res = target.apply(this, args) 35 | 36 | if (isPromise(res)) { 37 | return res.then((data: any) => { 38 | // @ts-ignore 39 | this.metricService.timer(metricName).update(Date.now() - start) 40 | return data 41 | }) 42 | } 43 | 44 | // @ts-ignore 45 | this.metricService.timer(metricName).update(Date.now() - start) 46 | return res 47 | } 48 | }, 49 | ) 50 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/mock/gen-nodejs/shared_types.cjs: -------------------------------------------------------------------------------- 1 | // 2 | // Autogenerated by Thrift Compiler (0.13.0) 3 | // 4 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | // 6 | 'use strict' 7 | /* eslint-disable */ 8 | var thrift = require('thrift') 9 | var Thrift = thrift.Thrift 10 | var Q = thrift.Q 11 | var Int64 = require('node-int64') 12 | 13 | var ttypes = (module.exports = {}) 14 | var SharedStruct = (module.exports.SharedStruct = function (args) { 15 | this.key = null 16 | this.value = null 17 | if (args) { 18 | if (args.key !== undefined && args.key !== null) { 19 | this.key = args.key 20 | } 21 | if (args.value !== undefined && args.value !== null) { 22 | this.value = args.value 23 | } 24 | } 25 | }) 26 | SharedStruct.prototype = {} 27 | SharedStruct.prototype.read = function (input) { 28 | input.readStructBegin() 29 | while (true) { 30 | var ret = input.readFieldBegin() 31 | var ftype = ret.ftype 32 | var fid = ret.fid 33 | if (ftype == Thrift.Type.STOP) { 34 | break 35 | } 36 | switch (fid) { 37 | case 1: 38 | if (ftype == Thrift.Type.I32) { 39 | this.key = input.readI32() 40 | } else { 41 | input.skip(ftype) 42 | } 43 | break 44 | case 2: 45 | if (ftype == Thrift.Type.STRING) { 46 | this.value = input.readString() 47 | } else { 48 | input.skip(ftype) 49 | } 50 | break 51 | default: 52 | input.skip(ftype) 53 | } 54 | input.readFieldEnd() 55 | } 56 | input.readStructEnd() 57 | return 58 | } 59 | 60 | SharedStruct.prototype.write = function (output) { 61 | output.writeStructBegin('SharedStruct') 62 | if (this.key !== null && this.key !== undefined) { 63 | output.writeFieldBegin('key', Thrift.Type.I32, 1) 64 | output.writeI32(this.key) 65 | output.writeFieldEnd() 66 | } 67 | if (this.value !== null && this.value !== undefined) { 68 | output.writeFieldBegin('value', Thrift.Type.STRING, 2) 69 | output.writeString(this.value) 70 | output.writeFieldEnd() 71 | } 72 | output.writeFieldStop() 73 | output.writeStructEnd() 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /packages/thrift/src/main/ts/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { IThriftServiceProfile } from '@qiwi/nestjs-enterprise-connection-provider' 2 | 3 | import * as thrift from 'thrift' 4 | import { Pool } from 'generic-pool' 5 | import { TProtocol, TTransport } from 'thrift' 6 | 7 | export type { 8 | IServiceDeclaration, 9 | IConnectionProvider, 10 | IThriftServiceProfile, 11 | IConnectionParams, 12 | } from '@qiwi/nestjs-enterprise-connection-provider' 13 | export type { IConsulService } from '@qiwi/nestjs-enterprise-consul' 14 | 15 | export interface IThriftConnectionOpts { 16 | multiplexer?: boolean 17 | connectionOpts?: { transport: any; protocol: any } 18 | } 19 | 20 | export interface IThriftClientProvider { 21 | /** 22 | * Get thrift client from pool or create new client 23 | * 24 | * @param serviceProfile 25 | * @param clientConstructor 26 | * @param opts 27 | */ 28 | getClient( 29 | serviceProfile: IThriftServiceProfile | string, 30 | clientConstructor: thrift.TClientConstructor, 31 | opts?: IThriftConnectionOpts, 32 | ): TClient 33 | } 34 | 35 | // eslint-disable-next-line @typescript-eslint/ban-types 36 | export interface ClassType extends Function { 37 | new (...args: any[]): InstanceType 38 | prototype: InstanceType 39 | } 40 | 41 | export type Extender = >( 42 | base: BaseClass, 43 | ) => BaseClass 44 | 45 | export type TPoolOpts = { 46 | max?: number 47 | min?: number 48 | maxWaitingClients?: number 49 | testOnBorrow?: boolean 50 | testOnReturn?: boolean 51 | acquireTimeoutMillis?: number 52 | fifo?: boolean 53 | priorityRange?: number 54 | autostart?: boolean 55 | evictionRunIntervalMillis?: number 56 | numTestsPerEvictionRun?: number 57 | softIdleTimeoutMillis?: number 58 | idleTimeoutMillis?: number 59 | } 60 | 61 | export type TThriftPoolResource = { 62 | connection: thrift.Connection 63 | client: TClient 64 | profile: IThriftServiceProfile 65 | } 66 | export type TThriftPool = Pool> 67 | 68 | export type TThriftOpts = { 69 | multiplexer?: boolean 70 | connectionOpts?: { 71 | transport: TTransport 72 | protocol: TProtocol 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/metric/src/test/ts/metric.module.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import path from 'node:path' 3 | import { after, before } from 'node:test' 4 | import { describe, it } from 'node:test' 5 | import { fileURLToPath } from 'node:url' 6 | 7 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 8 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 9 | 10 | import { Test } from '@nestjs/testing' 11 | 12 | import { MetricModule } from '../../main/ts/metric.module' 13 | 14 | const testConfigPath = path.join( 15 | path.dirname(fileURLToPath(import.meta.url)), 16 | 'config', 17 | 'test.json', 18 | ) 19 | 20 | describe('MetricModuleDynamic', () => { 21 | let moduleFixtureWithParams: any 22 | 23 | before(async () => { 24 | const graphiteApiEndpoint = 'http://example.com' 25 | const prefix = 'prefix' 26 | const interval = 1000 27 | 28 | moduleFixtureWithParams = await Test.createTestingModule({ 29 | imports: [ 30 | MetricModule.register(graphiteApiEndpoint, { prefix, interval }), 31 | ConfigModule.register({ path: testConfigPath }), 32 | LoggerModule, 33 | ], 34 | }).compile() 35 | }) 36 | 37 | after(async () => { 38 | await moduleFixtureWithParams?.close() 39 | }) 40 | 41 | it('should be defined with parameters', () => { 42 | const metricService = moduleFixtureWithParams.get('IMetricService') 43 | const graphiteService = moduleFixtureWithParams.get('IGraphiteService') 44 | assert.ok(metricService, 'metricService is not defined') 45 | assert.ok(graphiteService, 'graphiteService is not defined') 46 | }) 47 | }) 48 | 49 | describe('MetricModuleStatic', () => { 50 | let moduleFixture: any 51 | before(async () => { 52 | moduleFixture = await Test.createTestingModule({ 53 | imports: [ 54 | MetricModule, 55 | ConfigModule.register({ path: testConfigPath }), 56 | LoggerModule, 57 | ], 58 | }).compile() 59 | }) 60 | 61 | after(async () => { 62 | await moduleFixture?.close() 63 | }) 64 | 65 | it('should be defined without parameters', () => { 66 | const metricService = moduleFixture.get('IMetricService') 67 | const graphiteService = moduleFixture.get('IGraphiteService') 68 | assert.ok(metricService, 'metricService is not defined') 69 | assert.ok(graphiteService, 'graphiteService is not defined') 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /packages/svc-info/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-svc-info 2 | Returns uptime by `/svc-info/uptime`, application name with version by `/svc-info/version` and build info by `/svc-info/buildstamp` 3 | ## Installation 4 | ```shell script 5 | yarn add @qiwi/nestjs-enterprise-svc-info 6 | ``` 7 | ## Before using 8 | Buildstamp is a file with build info, whose contents the module exposes by `/svc-info/buildstamp`. 9 | 10 | Set up buidstamp forwarding into a container in your `Dockerfile`: 11 | ```dockerfile 12 | COPY buildstamp.json $APP_DIR/buildstamp.json 13 | ``` 14 | Add to `"scripts"` in `package.json`: 15 | ```json 16 | "prebuild": "", 17 | ``` 18 | We suggest using [buildstamp](https://github.com/qiwi/buildstamp). 19 | 20 | ## Usage 21 | ### Usage as module: 22 | ```typescript 23 | import { SvcInfoModule } from '@qiwi/nestjs-enterprise-svc-info' 24 | // ... 25 | 26 | @Module({ 27 | imports: [ 28 | SvcInfoModule 29 | // and so on 30 | ], 31 | controllers: [ ], 32 | providers: [ ], 33 | }) 34 | export class AppModule {} 35 | ``` 36 | 37 | ### Usage as controller (if you need routes order): 38 | 39 | ```typescript 40 | import { SvcInfoController } from '@qiwi/nestjs-enterprise-svc-info' 41 | 42 | // ... 43 | 44 | @Module({ 45 | imports: [], 46 | controllers: [SvcInfoController], 47 | providers: [ ], 48 | }) 49 | export class AppModule {} 50 | ``` 51 | 52 | ### Setting a custom path to buildstamp file 53 | 54 | If you want to give a custom path to your buildstamp file, import module via `register`, which accepts `ISvcInfoModuleOpts`: 55 | ```typescript 56 | import { SvcInfoModule, ISvcInfoModuleOpts } from '@qiwi/nestjs-enterprise-svc-info' 57 | // ... 58 | const opts: ISvcInfoModuleOpts = { 59 | path: 'some/path/buildstamp.json' 60 | } 61 | 62 | @Module({ 63 | imports: [ 64 | SvcInfoModule.register(opts) 65 | // and so on 66 | ], 67 | controllers: [ ], 68 | providers: [ ], 69 | }) 70 | export class AppModule {} 71 | ``` 72 | In case of using the module as controller: 73 | 74 | ```typescript 75 | import { SvcInfoController, ISvcInfoModuleOpts } from '@qiwi/nestjs-enterprise-svc-info' 76 | // ... 77 | const opts: ISvcInfoModuleOpts = { 78 | path: 'some/path/buildstamp.json' 79 | } 80 | 81 | 82 | @Module({ 83 | imports: [], 84 | controllers: [SvcInfoController], 85 | providers: [ 86 | { provide: 'ISvcInfoModuleOpts', useValue: opts } 87 | ], 88 | }) 89 | export class AppModule {} 90 | ``` 91 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/svc-info) 92 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal, notEqual } from 'node:assert' 2 | import { dirname, join } from 'node:path' 3 | import { describe, it } from 'node:test' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | import { Inject, Injectable } from '@nestjs/common' 7 | import { Test } from '@nestjs/testing' 8 | 9 | import { 10 | ConfigModule, 11 | DEFAULT_KUBE_CONFIG_PATH, 12 | DEFAULT_LOCAL_CONFIG_PATH, 13 | resolveConfigPath, 14 | } from '../../main/ts' 15 | 16 | const testFileDir = dirname(fileURLToPath(import.meta.url)) 17 | 18 | const testCfgPath = join(testFileDir, 'config', 'test.json') 19 | const schemaPath = join(testFileDir, 'config', 'app-config.schema.json') 20 | 21 | describe('configModule', () => { 22 | describe('index', () => { 23 | it('properly exposes its inners', () => { 24 | notEqual(ConfigModule, undefined) 25 | }) 26 | }) 27 | 28 | describe('config module', () => { 29 | it('correctly work as dynamic module', async () => { 30 | @Injectable() 31 | class TestService { 32 | @Inject('IConfigService') config: any 33 | 34 | getServiceName() { 35 | return this.config.get('name') 36 | } 37 | } 38 | 39 | const module = await Test.createTestingModule({ 40 | imports: [ConfigModule.register({ path: testCfgPath, schemaPath })], 41 | providers: [TestService], 42 | }).compile() 43 | 44 | const service = module.get(TestService) 45 | equal(service.getServiceName(), 'test-name-app') 46 | await module.close() 47 | }) 48 | 49 | it('correctly work as static module', async () => { 50 | @Injectable() 51 | class TestService { 52 | @Inject('IConfigService') config: any 53 | 54 | getServiceName() { 55 | return this.config.get('name') 56 | } 57 | } 58 | 59 | @Injectable() 60 | class FakeProvider { 61 | get() { 62 | return 'fake-service-name' 63 | } 64 | } 65 | 66 | const module = await Test.createTestingModule({ 67 | imports: [ConfigModule], 68 | providers: [TestService], 69 | }) 70 | .overrideProvider('IConfigService') 71 | .useClass(FakeProvider) 72 | .compile() 73 | 74 | const service = module.get(TestService) 75 | equal(service.getServiceName(), 'fake-service-name') 76 | await module.close() 77 | }) 78 | }) 79 | 80 | describe('resolveConfigPath', () => { 81 | it('correctly resolve path', () => { 82 | equal(resolveConfigPath(undefined, true), DEFAULT_LOCAL_CONFIG_PATH) 83 | equal(resolveConfigPath(undefined, false), DEFAULT_KUBE_CONFIG_PATH) 84 | equal(resolveConfigPath('test'), 'test') 85 | }) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /packages/logger/src/main/ts/winston.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync } from 'node:fs' 2 | import { resolve } from 'node:path' 3 | 4 | import * as winston from 'winston' 5 | import safeJsonStrinify from 'fast-safe-stringify' 6 | 7 | export type TWinstonEntry = { 8 | level: string 9 | timestamp: unknown 10 | meta: Record 11 | message: unknown 12 | } 13 | 14 | const { 15 | createLogger, 16 | transports: { Console, File }, 17 | format: { combine, printf, timestamp }, 18 | }: any = winston 19 | 20 | const isTimestampValid = (timestamp: any) => 21 | typeof timestamp === 'number' || Date.parse(timestamp) 22 | 23 | /** 24 | * @param {TWinstonEntry} entry - logEntry with winston format 25 | * @return - logEntry with kibana format 26 | */ 27 | export const formatKibanaEntry = (entry: TWinstonEntry) => { 28 | const { level, timestamp, meta, message } = entry 29 | const { timestamp: metaTimestamp, publicMeta } = meta 30 | const formattedTimestamp = new Date( 31 | (isTimestampValid(metaTimestamp) && metaTimestamp) || timestamp, 32 | ).toISOString() 33 | return { 34 | '@timestamp': formattedTimestamp, 35 | level: level.toUpperCase(), 36 | message, 37 | ...publicMeta, 38 | } 39 | } 40 | 41 | const formatJson = printf((entry: TWinstonEntry) => 42 | safeJsonStrinify(formatKibanaEntry(entry)), 43 | ) 44 | 45 | type TWinstonFactoryOpts = { 46 | level: string 47 | dir: string 48 | maxsize: number 49 | appJsonFilename: string 50 | zippedArchive: boolean 51 | tailable: boolean 52 | maxFiles: number 53 | [key: string]: any 54 | } 55 | 56 | function createConsoleAppender() { 57 | return new Console({ 58 | format: combine(timestamp(), formatJson), 59 | }) 60 | } 61 | 62 | function createFileAppender({ 63 | dir, 64 | maxsize, 65 | appJsonFilename, 66 | zippedArchive, 67 | tailable, 68 | maxFiles, 69 | }: TWinstonFactoryOpts) { 70 | if (!dir) { 71 | return 72 | } 73 | 74 | const dirname = resolve(dir) 75 | 76 | try { 77 | if (!existsSync(dirname)) { 78 | mkdirSync(`${dirname}`) 79 | } 80 | } catch (e) { 81 | console.error(e) 82 | } 83 | 84 | return new File({ 85 | dirname, 86 | filename: appJsonFilename, 87 | format: combine(timestamp(), formatJson), 88 | maxsize, 89 | zippedArchive, 90 | tailable, 91 | maxFiles, 92 | }) 93 | } 94 | 95 | export function createTransports(opts: TWinstonFactoryOpts) { 96 | const transports = [createConsoleAppender(), createFileAppender(opts)] 97 | return transports.filter(Boolean) 98 | } 99 | 100 | export default function getWinstonLogger(opts: TWinstonFactoryOpts) { 101 | return createLogger({ 102 | level: opts.level, 103 | exitOnError: false, 104 | transports: createTransports(opts), 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/thrift.decorators.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import path from 'node:path' 3 | import { after, before, describe, it } from 'node:test' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 7 | import { ConnectionProviderModule } from '@qiwi/nestjs-enterprise-connection-provider' 8 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 9 | 10 | // @ts-ignore 11 | import * as thrift from 'thrift' 12 | import { Global, Injectable, Module } from '@nestjs/common' 13 | import { Test } from '@nestjs/testing' 14 | 15 | import { InjectThriftService, ThriftModule } from '../../main/ts' 16 | // @ts-ignore 17 | import { FakeConsulDiscovery9090 } from './mock/fakeConsulDiscovery9090' 18 | // @ts-ignore 19 | import Client from './mock/gen-nodejs/Calculator.cjs' 20 | // @ts-ignore 21 | import server from './mock/server.cjs' 22 | 23 | const testConfigPath = path.join( 24 | path.dirname(fileURLToPath(import.meta.url)), 25 | 'config', 26 | 'test.json', 27 | ) 28 | 29 | describe('thrift', () => { 30 | before(() => { 31 | server.listen(9090) 32 | }) 33 | 34 | after(() => { 35 | server.close() 36 | }) 37 | 38 | describe('thrift.decorators', () => { 39 | @Global() 40 | @Module({ 41 | providers: [ 42 | { 43 | provide: 'IConsul', 44 | useValue: { 45 | getConnectionParams: () => 46 | new FakeConsulDiscovery9090().getConnectionParams(''), 47 | }, 48 | }, 49 | ], 50 | exports: ['IConsul'], 51 | }) 52 | class GlobalModule {} 53 | 54 | const connOpts = { 55 | multiplexer: false, 56 | connectionOpts: { 57 | transport: thrift.TBufferedTransport, 58 | protocol: thrift.TBinaryProtocol, 59 | }, 60 | } 61 | 62 | @Injectable() 63 | class TestService { 64 | constructor( 65 | @InjectThriftService(Client, 'services.common-auth', connOpts) 66 | public foo: Client, 67 | @InjectThriftService(Client, 'services.common-auth', connOpts) 68 | public bar: Client, 69 | ) {} 70 | } 71 | 72 | it('thrift service is injectable', async () => { 73 | const module = await Test.createTestingModule({ 74 | imports: [ 75 | ConfigModule.register({ path: testConfigPath }), 76 | ConnectionProviderModule, 77 | ThriftModule, 78 | LoggerModule, 79 | GlobalModule, 80 | ], 81 | providers: [TestService], 82 | }) 83 | .overrideProvider('IDiscoveryService') 84 | .useFactory({ factory: () => new FakeConsulDiscovery9090() }) 85 | .overrideProvider('ILogger') 86 | .useValue(console) 87 | .compile() 88 | 89 | const testService = module.get(TestService) 90 | 91 | equal(testService.foo, testService.bar) 92 | equal(await testService.foo.add(1, 2), 3) 93 | equal(await testService.bar.add(10, -10), 0) 94 | await module.get('IThriftClientService').pools.get(Client).clear() 95 | }) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /packages/consul/src/main/ts/consul.service.ts: -------------------------------------------------------------------------------- 1 | import { ConsulDiscoveryService } from '@qiwi/consul-service-discovery' 2 | import type { 3 | IConsulKvSetOptions, 4 | INormalizedConsulKvValue, 5 | } from '@qiwi/consul-service-discovery' 6 | import type { IDiscoverable } from '@qiwi/nestjs-enterprise-connection-provider' 7 | import type { IConfig, ILogger, IPromise } from '@qiwi/substrate' 8 | 9 | import { Inject, Injectable, OnModuleDestroy } from '@nestjs/common' 10 | 11 | const CONSUL_CHECK_REG_INTERVAL = 60_000 12 | 13 | export interface IConsulService extends IDiscoverable { 14 | /** 15 | * Register service in consul. 16 | */ 17 | register(): IPromise 18 | 19 | /** 20 | * Get value by key from storage 21 | * 22 | * @param key 23 | * @return Value by key 24 | */ 25 | getKv(key: string): Promise 26 | 27 | /** 28 | * Set key-value in storage 29 | * 30 | * @param data 31 | */ 32 | setKv(data: IConsulKvSetOptions): IPromise 33 | } 34 | 35 | @Injectable() 36 | export class ConsulService implements IConsulService, OnModuleDestroy { 37 | constructor( 38 | @Inject('ILogger') private log: ILogger, 39 | @Inject('IConfigService') private config: IConfig, 40 | @Inject('IDiscoveryService') 41 | private discoveryService: ConsulDiscoveryService, 42 | ) {} 43 | 44 | async onModuleDestroy(): Promise { 45 | await this.discoveryService.clear() 46 | } 47 | 48 | async register() { 49 | const serviceName: string = this.config.get('name') 50 | const consulHost: string = this.config.get('consul.host') 51 | const consulPort: string = this.config.get('consul.port') 52 | const tags: string[] = this.config.get('consul.tags') 53 | const consulToken: string = this.config.get('consul.token') 54 | const consulUrl = `http://${consulHost}:${consulPort}/v1/agent/service/register` 55 | const port: number = this.config.get('server.port') 56 | const ip: string = this.config.get('server.host') 57 | const consulCheckRegInterval = 58 | this.config.get('consul.checkRegInterval') || CONSUL_CHECK_REG_INTERVAL 59 | 60 | this.log.info('consul registration attempt', consulUrl) 61 | 62 | return this.discoveryService 63 | .register( 64 | { 65 | name: serviceName, 66 | token: consulToken, 67 | tags, 68 | address: ip, 69 | port, 70 | check: { 71 | http: `http://${ip}:${port}/health`, 72 | interval: '15s', 73 | // @ts-ignore 74 | deregistercriticalserviceafter: '10m', 75 | }, 76 | }, 77 | consulCheckRegInterval, 78 | ) 79 | .then(() => this.log.info('registered in consul OK')) 80 | } 81 | 82 | async getConnectionParams(serviceName: string) { 83 | const connectionParams = await this.discoveryService.getConnectionParams( 84 | serviceName, 85 | ) 86 | return connectionParams 87 | ? { host: connectionParams.host, port: +connectionParams.port } 88 | : undefined 89 | } 90 | 91 | async getKv(key: string) { 92 | return this.discoveryService.getKv(key) 93 | } 94 | 95 | async setKv(data: IConsulKvSetOptions) { 96 | return this.discoveryService.setKv(data) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/logger/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-logger 2 | Nestjs module for logging based on [winston](https://github.com/winstonjs/winston) 3 | 4 | ## Installation 5 | Following packages should be installed before 6 | - [@qiwi/nestjs-enterprise-config](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/config) 7 | - [@qiwi/nestjs-enterprise-mdc](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/mdc) 8 | ```shell script 9 | yarn add @qiwi/nestjs-enterprise-logger 10 | ``` 11 | ## Configuration 12 | Import 13 | ```typescript 14 | import { 15 | LoggerModule, 16 | createMetaPipe, 17 | maskerLoggerPipeFactory, 18 | } from '@qiwi/nestjs-enterprise-logger' 19 | 20 | @Module({ 21 | imports: [ 22 | ConfigModule, 23 | LoggerModule.register(createMetaPipe(), maskerLoggerPipeFactory()), 24 | // and so on 25 | ] 26 | }) 27 | 28 | export class AppModule {} 29 | ``` 30 | 31 | ## Usage 32 | ```typescript 33 | @Injectable() 34 | class MyService { 35 | constructor(@Inject('ILogger') private logger: ILogger) {} 36 | myError() { 37 | this.logger.error('foo') 38 | } 39 | myInfo() { 40 | this.logger.info('foo') 41 | } 42 | 43 | } 44 | ``` 45 | 46 | For `createMetaPipe` 47 | ```typescript 48 | import { 49 | logger as log, 50 | } from '@qiwi/nestjs-enterprise-logger' 51 | 52 | async function bootstrap() { 53 | const app = await NestFactory.create(AppModule) 54 | const logger = app.get('ILogger') 55 | app 56 | .use(log({ logger })) 57 | .useLogger(logger) 58 | 59 | //... 60 | logger.info() 61 | ``` 62 | ## Customization 63 | You can inject functions of type `TLoggerPipe` as your own pipes when create `LoggerService` or register `LoggerModule`. 64 | Your pipes will be inserted in the following order: 65 | - `mdc` pipe from `@qiwi/logwrap`; 66 | - `app` pipe (adds app name, app version and os info to log entry); 67 | - your own pipe; 68 | - ... 69 | - your own pipe; 70 | - `logger` pipe (prints log entry). 71 | ## API 72 | ### Class LoggerModule 73 | Exports `LoggerService` with token `ILogger` 74 | #### register (...pipes: TLoggerPipe[]): DynamicModule 75 | 76 | ### Class LoggerService 77 | #### constructor(pipeline: TLoggerPipe[], config: IConfig) 78 | #### push(entry: ILogEntry): void 79 | | field | type | description | 80 | | --- | --- | --- | 81 | |LogEntry.meta | Record| Metadata 82 | |LogEntry.level | ERROR | WARN | INFO | DEBUG | TRACE | Log level 83 | |LogEntry.input | any[] | Data 84 | #### trace(...data: any[]): void 85 | #### debug(...data: any[]): void 86 | #### info(...data: any[]): void 87 | #### warn(...data: any[]): void 88 | #### error(...data: any[]): void 89 | 90 | ### Function createMetaPipe = () => (entry: ILogEntry): ILogEntry 91 | Creates pipe for metadata injection, used with [@qiwi-private/js-platform-mdc-nestjs](https://github.qiwi.com/common/js-platform/tree/master/packages/mdc-nestjs) 92 | 93 | ### Function maskerLoggerPipeFactory = () => (entry: ILogEntry): ILogEntry 94 | Creates pipe for pan masking 95 | 96 | ### Function masker = (input: string | number): string 97 | Masks pans 98 | 99 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/logger/) 100 | -------------------------------------------------------------------------------- /packages/consul/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-consul 2 | Nestjs module for working with Consul 3 | 4 | ## Installation 5 | Requires following packages to be installed 6 | - [@qiwi/nestjs-enterprise-config](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/config) 7 | - [@qiwi/nestjs-enterprise-logger](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/logger) 8 | 9 | ```shell script 10 | yarn add @qiwi/nestjs-enterprise-consul 11 | ``` 12 | 13 | ## Configuration 14 | ```typescript 15 | import { Module } from '@nestjs/common' 16 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 17 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 18 | import { ConsulModule } from '@qiwi/nestjs-enterprise-consul' 19 | 20 | @Module({ 21 | imports: [ 22 | ConfigModule, 23 | LoggerModule, 24 | ConsulModule, 25 | // and so on 26 | ], 27 | controllers: [], 28 | providers: [], 29 | }) 30 | export class AppModule {} 31 | ``` 32 | 33 | ### Config 34 | ```json 35 | { 36 | "data": { 37 | "name": "APP_NAME", 38 | "server": { 39 | "port": 8080, 40 | "host": "$host:" 41 | }, 42 | "consul": { 43 | "host": "CONSUL_AGENT_HOST", 44 | "port": "CONSUL_AGENT_PORT", 45 | "token": "consul token", 46 | "tags": ["tag"] 47 | } 48 | }, 49 | "sources": { 50 | "host": { 51 | "pipeline": "ip" 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ## Usage 58 | ```typescript 59 | // main.ts 60 | import { NestFactory } from '@nestjs/core' 61 | import { AppModule } from './app.module' 62 | 63 | async function bootstrap() { 64 | 65 | const app = await NestFactory.create(AppModule) 66 | //... 67 | 68 | const consul = app.get('IConsul') 69 | const local = config.get('local') 70 | //... 71 | 72 | if (!local) { 73 | consul.register() 74 | } 75 | } 76 | //... 77 | 78 | ``` 79 | ```typescript 80 | // service.ts 81 | @Injectable() 82 | export class ConnectionProviderService implements IConnectionProvider { 83 | constructor( 84 | @Inject('IConsul') 85 | private consul: IConsul, 86 | ) {} 87 | 88 | async getConnectionParams(serviceName: string) { 89 | return this.consul.getConnectionParams(serviceName) 90 | } 91 | } 92 | 93 | ``` 94 | 95 | ## API 96 | ### ConsulModule class 97 | Exports `ConsulService` with token `IConsul` 98 | ### ConsulService class 99 | #### register(opts: any): IPromise 100 | #### getConnectionParams(opts: any): Promise 101 | | field | type | description | 102 | | --- | --- | --- | 103 | |IConnectionParams.host | string | host 104 | |IConnectionParams.port | number | port 105 | 106 | #### getKv(opts: any): Promise \ 107 | | field | type | description | 108 | | --- | --- | --- | 109 | |INormalizedConsulKvValue.createIndex | number | createIndex 110 | |INormalizedConsulKvValue.modifyIndex | number | modifyIndex 111 | |INormalizedConsulKvValue.lockIndex | number | lockIndex 112 | |INormalizedConsulKvValue.key | string | key 113 | |INormalizedConsulKvValue.flags | number | flags 114 | |INormalizedConsulKvValue.value | string | value 115 | 116 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/consul/) 117 | -------------------------------------------------------------------------------- /packages/config/src/test/ts/uniconfig-json-schema-validation-plugin/index.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import { describe, it, mock } from 'node:test' 3 | 4 | import { uniconfigJsonSchemaValidationPluginFactory } from '../../../main/ts/uniconfig-json-schema-validation-plugin/index' 5 | 6 | const appConfigSchema = { 7 | properties: { 8 | name: { 9 | type: 'string', 10 | }, 11 | }, 12 | required: ['name'], 13 | } 14 | 15 | const appConfig = { 16 | name: 'foo', 17 | } 18 | 19 | const configSchemaBuffer = { 20 | toString() { 21 | return JSON.stringify(appConfigSchema) 22 | }, 23 | } 24 | 25 | const readFileFactory = () => 26 | mock.fn(() => { 27 | return Promise.resolve(configSchemaBuffer) 28 | }) 29 | 30 | const readFileSyncFactory = () => 31 | mock.fn(() => { 32 | return configSchemaBuffer 33 | }) 34 | 35 | const schemaPath = '/some/path' 36 | 37 | describe('uniconfigJsonSchemaValidationPluginFactory async', () => { 38 | it('throws validation error', async () => { 39 | const readFile = readFileFactory() 40 | 41 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 42 | schemaPath, 43 | deps: { readFile } as any, 44 | }) 45 | await assert.rejects(() => plugin.handle({} as any, { foo: 42 })) 46 | }) 47 | 48 | it('does not throw validation error when config is ok', async () => { 49 | const readFile = readFileFactory() 50 | 51 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 52 | schemaPath, 53 | deps: { readFile } as any, 54 | }) 55 | assert.deepEqual(await plugin.handle({} as any, appConfig), appConfig) 56 | }) 57 | 58 | it('does not throw validation error when schema is absent', async () => { 59 | const readFile = readFileFactory() 60 | 61 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 62 | schemaPath, 63 | deps: { readFile } as any, 64 | }) 65 | assert.deepEqual(await plugin.handle({} as any, appConfig), appConfig) 66 | }) 67 | }) 68 | 69 | describe('uniconfigJsonSchemaValidationPluginFactory sync', () => { 70 | it('throws validation error', () => { 71 | const readFileSync = readFileSyncFactory() 72 | 73 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 74 | schemaPath, 75 | deps: { 76 | readFileSync, 77 | } as any, 78 | }) 79 | assert.throws(() => plugin.handleSync({} as any, { bar: 42 })) 80 | }) 81 | 82 | it('does not throw validation error when config is ok', () => { 83 | const readFileSync = readFileSyncFactory() 84 | 85 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 86 | schemaPath, 87 | deps: { 88 | readFileSync, 89 | } as any, 90 | }) 91 | assert.deepEqual(plugin.handleSync({} as any, appConfig), appConfig) 92 | }) 93 | 94 | it('does not throw validation error when schema is absent', () => { 95 | const readFileSync = readFileSyncFactory() 96 | 97 | const plugin = uniconfigJsonSchemaValidationPluginFactory({ 98 | schemaPath, 99 | deps: { 100 | readFileSync, 101 | } as any, 102 | }) 103 | assert.deepEqual(plugin.handleSync({} as any, appConfig), appConfig) 104 | }) 105 | }) 106 | -------------------------------------------------------------------------------- /packages/logger/src/test/ts/winston.spec.ts: -------------------------------------------------------------------------------- 1 | import { deepEqual } from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | 4 | import { formatKibanaEntry, TWinstonEntry } from '../../main/ts/winston' 5 | 6 | type TTestCase = { 7 | description: string 8 | input: TWinstonEntry 9 | output: ReturnType 10 | } 11 | 12 | describe('formatKibanaEntry', () => { 13 | it('test cases', async (t) => { 14 | const testCases: TTestCase[] = [ 15 | { 16 | description: 'adds string timestamp from meta', 17 | input: { 18 | level: 'foo', 19 | timestamp: 1_599_847_151_451, 20 | meta: { 21 | timestamp: '2020-09-11T18:30:19.868Z', 22 | }, 23 | message: 'bar', 24 | }, 25 | output: { 26 | '@timestamp': '2020-09-11T18:30:19.868Z', 27 | level: 'FOO', 28 | message: 'bar', 29 | }, 30 | }, 31 | { 32 | description: 'adds instant timestamp from meta', 33 | input: { 34 | level: 'foo', 35 | timestamp: 1_599_847_151_451, 36 | meta: { 37 | timestamp: 1_599_849_019_865, 38 | }, 39 | message: 'bar', 40 | }, 41 | output: { 42 | '@timestamp': '2020-09-11T18:30:19.865Z', 43 | level: 'FOO', 44 | message: 'bar', 45 | }, 46 | }, 47 | { 48 | description: 'adds timestamp from root', 49 | input: { 50 | level: 'foo', 51 | timestamp: 1_599_847_151_451, 52 | meta: { 53 | timestamp: 'bar', 54 | publicMeta: { 55 | baz: 'baz', 56 | }, 57 | }, 58 | message: 'bar', 59 | }, 60 | output: { 61 | '@timestamp': '2020-09-11T17:59:11.451Z', 62 | level: 'FOO', 63 | message: 'bar', 64 | baz: 'baz', 65 | }, 66 | }, 67 | { 68 | description: 'adds root timestamp if meta.timestamp is null', 69 | input: { 70 | level: 'foo', 71 | timestamp: 1_599_847_151_451, 72 | meta: { 73 | // eslint-disable-next-line unicorn/no-null 74 | timestamp: null, 75 | publicMeta: { 76 | baz: 'baz', 77 | }, 78 | }, 79 | message: 'bar', 80 | }, 81 | output: { 82 | '@timestamp': '2020-09-11T17:59:11.451Z', 83 | level: 'FOO', 84 | message: 'bar', 85 | baz: 'baz', 86 | }, 87 | }, 88 | { 89 | description: 'adds root timestamp if meta.timestamp is undefined', 90 | input: { 91 | level: 'foo', 92 | timestamp: 1_600_081_785_361, 93 | meta: { 94 | timestamp: undefined, 95 | publicMeta: { 96 | baz: 'baz', 97 | }, 98 | }, 99 | message: 'bar', 100 | }, 101 | output: { 102 | '@timestamp': '2020-09-14T11:09:45.361Z', 103 | level: 'FOO', 104 | message: 'bar', 105 | baz: 'baz', 106 | }, 107 | }, 108 | ] 109 | 110 | for (const { input, output, description } of testCases) { 111 | await t.test(description, () => { 112 | deepEqual(formatKibanaEntry(input), output) 113 | }) 114 | } 115 | }) 116 | }) 117 | -------------------------------------------------------------------------------- /packages/thrift/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-thrift" 2 | 3 | Nestjs module for working with [Apache Thrift](https://thrift.apache.org/) 4 | 5 | # Installation 6 | Requires following packages to be installed 7 | - [@qiwi/nestjs-enterprise-logger](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/logger) 8 | - [@qiwi/nestjs-enterprise-config](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/config) 9 | - [@qiwi/nestjs-enterprise-connection-provider](https://github.com/qiwi/nestjs-enterprise/tree/master/packages/connection-provider) 10 | 11 | ```shell script 12 | yarn add @qiwi/nestjs-enterprise-thrift 13 | ``` 14 | 15 | ## Configuration 16 | Imports 17 | ```typescript 18 | import { Module } from '@nestjs/common' 19 | import { ConnectionProviderModule } from '@qiwi/nestjs-enterprise-connection-provider' 20 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger-nestjs' 21 | import { ConsulModule } from '@qiwi/nestjs-enterprise-consul-nestjs' 22 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config-nestjs' 23 | import { ThriftModule } from '@qiwi/nestjs-enterprise-thrift' 24 | 25 | @Module({ 26 | imports: [ 27 | ConfigModule, 28 | LoggerModule, 29 | ConsulModule, 30 | ConnectionProviderModule, 31 | // and so on 32 | ], 33 | controllers: [], 34 | providers: [], 35 | }) 36 | export class AppModule {} 37 | ``` 38 | ## Usage 39 | ```typescript 40 | @Injectable() 41 | class AuthService { 42 | client?: Client 43 | creds?: TCredentials 44 | token?: TAuthentication 45 | 46 | @Inject('IConfigService') 47 | config: IConfig 48 | 49 | @Inject('IThriftClientService') 50 | thrift: IThriftClientService 51 | 52 | getClient() { 53 | const serviceProfile: IServiceDeclaration = this.config.get('serviceName') 54 | this.client = this.thrift.getClient(serviceProfile, Client, { 55 | multiplexer: false, 56 | connectionOpts: { 57 | transport: thrift.TBufferedTransport, 58 | protocol: thrift.TBinaryProtocol, 59 | }, 60 | }) 61 | return this.client 62 | } 63 | } 64 | ``` 65 | 66 | ## Decorators 67 | ### @ThriftServer 68 | ```typescript 69 | @ThriftServer(CalculatorProcessor, 9091) 70 | class TestServer { 71 | ping(result: () => void) { 72 | result() 73 | } 74 | 75 | add(n1: any, n2: any, result: (arg0: null, arg1: any) => void) { 76 | result(null, n1 + n2) 77 | } 78 | } 79 | ``` 80 | 81 | ### Config 82 | ```json 83 | { 84 | "data": { 85 | "services": { 86 | "service-1": { 87 | "type": "thrift", 88 | "thriftServiceName": "test service 1", 89 | "discovery": { 90 | "type": "endpoint", 91 | "endpoints": [{ "host": "service-1.test.com", "port": "8080" }] 92 | } 93 | }, 94 | "service-2": { 95 | "type": "thrift", 96 | "thriftServiceName": "test service 2", 97 | "discovery": { 98 | "type": "consul", 99 | "serviceName": "consul name" 100 | }, 101 | "creds": { 102 | "type": "username-and-password", 103 | "username": "username", 104 | "password": "password" 105 | } 106 | } 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | ## API 113 | ### Class ConnectionProviderModule 114 | Exports `IThriftClientService` with token `IThriftClientService` 115 | 116 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/thrift/) 117 | -------------------------------------------------------------------------------- /packages/thrift/src/test/ts/thrift.client.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal, notEqual } from 'node:assert' 2 | import path from 'node:path' 3 | import { after, before, describe, it } from 'node:test' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 7 | import { ConnectionProviderModule } from '@qiwi/nestjs-enterprise-connection-provider' 8 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 9 | import { IConfig } from '@qiwi/substrate' 10 | 11 | // @ts-ignore 12 | import * as thrift from 'thrift' 13 | import { Global, Inject, Injectable, Module } from '@nestjs/common' 14 | import { Test } from '@nestjs/testing' 15 | 16 | import { 17 | IThriftClientProvider, 18 | IThriftServiceProfile, 19 | ThriftClientProvider, 20 | ThriftModule, 21 | } from '../../main/ts' 22 | // @ts-ignore 23 | import { FakeConsulDiscovery9090 } from './mock/fakeConsulDiscovery9090' 24 | // @ts-ignore 25 | import Client from './mock/gen-nodejs/Calculator.cjs' 26 | // @ts-ignore 27 | import server from './mock/server.cjs' 28 | 29 | const testConfigPath = path.join( 30 | path.dirname(fileURLToPath(import.meta.url)), 31 | 'config', 32 | 'test.json', 33 | ) 34 | 35 | @Global() 36 | @Module({ 37 | providers: [ 38 | { 39 | provide: 'IConsul', 40 | useValue: { 41 | getConnectionParams: () => 42 | new FakeConsulDiscovery9090().getConnectionParams(''), 43 | }, 44 | }, 45 | ], 46 | exports: ['IConsul'], 47 | }) 48 | class GlobalModule {} 49 | 50 | @Injectable() 51 | class TestService { 52 | client?: Client 53 | 54 | constructor( 55 | @Inject('IConfigService') private config: IConfig, 56 | @Inject('IThriftClientService') 57 | private thrift: IThriftClientProvider, 58 | ) {} 59 | 60 | getClient() { 61 | const serviceProfile: IThriftServiceProfile = this.config.get( 62 | 'services.common-auth', 63 | ) 64 | this.client = this.thrift.getClient(serviceProfile, Client, { 65 | multiplexer: false, 66 | connectionOpts: { 67 | transport: thrift.TBufferedTransport, 68 | protocol: thrift.TBinaryProtocol, 69 | }, 70 | }) 71 | return this.client 72 | } 73 | } 74 | 75 | describe('thrift', () => { 76 | describe('index', () => { 77 | it('properly exposes its inners', () => { 78 | notEqual(ThriftModule, undefined) 79 | notEqual(ThriftClientProvider, undefined) 80 | }) 81 | }) 82 | }) 83 | 84 | describe('thrift module', () => { 85 | let module: any 86 | let thriftService: any 87 | 88 | before(async () => { 89 | module = await Test.createTestingModule({ 90 | imports: [ 91 | ConfigModule.register({ path: testConfigPath }), 92 | ConnectionProviderModule, 93 | ThriftModule, 94 | LoggerModule, 95 | GlobalModule, 96 | ], 97 | providers: [TestService], 98 | }) 99 | .overrideProvider('IDiscoveryService') 100 | .useFactory({ factory: () => new FakeConsulDiscovery9090() }) 101 | .overrideProvider('ILogger') 102 | .useValue(console) 103 | .compile() 104 | 105 | thriftService = module.get(TestService) 106 | await server.listen(9090) 107 | }) 108 | after(async () => { 109 | await module.close() 110 | await server.close() 111 | }) 112 | 113 | it('exposes thrift api', async () => { 114 | const thriftClient = thriftService.getClient() 115 | equal(await thriftClient.add(1, 2), 3) 116 | equal(await thriftClient.add(10, -10), 0) 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /packages/common/src/test/ts/request-size.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import { after, before, describe, it } from 'node:test' 3 | 4 | import { Controller, HttpCode, Patch, Post } from '@nestjs/common' 5 | import { NestApplication } from '@nestjs/core' 6 | import { Test, TestingModule } from '@nestjs/testing' 7 | import request from 'supertest' 8 | 9 | import { RequestSize } from '../../main/ts/request-size' 10 | 11 | type Cases = Array< 12 | [string, string, 'post' | 'patch', string, { data: string; status: number }] 13 | > 14 | 15 | @Controller() 16 | @RequestSize(512) 17 | export class TestClassController { 18 | @HttpCode(200) 19 | @Post('req-limit-512-class') 20 | async test(@RequestSize() size: number) { 21 | return size 22 | } 23 | } 24 | 25 | @Controller() 26 | export class TestMethodController { 27 | @RequestSize(512) 28 | @HttpCode(200) 29 | @Patch('req-limit-512-method') 30 | async test(@RequestSize() size: number) { 31 | return size 32 | } 33 | } 34 | 35 | @Controller() 36 | export class TestParamController { 37 | @HttpCode(200) 38 | @Post('return-req-size') 39 | async test(@RequestSize() size: number) { 40 | return size 41 | } 42 | } 43 | 44 | describe('RequestSize decorators', () => { 45 | const testData = 'data for test' 46 | let module: TestingModule 47 | let app: NestApplication 48 | 49 | const processTestCases = async (cases: Cases, t) => { 50 | for await (const [description, path, method, data, succ] of cases) { 51 | await t.test(description, async () => { 52 | request(app.getHttpServer()) 53 | [method](path) 54 | .send({ data }) 55 | .expect(succ.status) 56 | .expect((res) => { 57 | if (res.status === 200) { 58 | return equal(res.text, succ.data) 59 | } 60 | }) 61 | }) 62 | } 63 | } 64 | 65 | before(async () => { 66 | module = await Test.createTestingModule({ 67 | controllers: [ 68 | TestClassController, 69 | TestMethodController, 70 | TestParamController, 71 | ], 72 | }).compile() 73 | app = module.createNestApplication() 74 | 75 | await app.init() 76 | }) 77 | 78 | after(async () => { 79 | await app.close() 80 | }) 81 | 82 | it('class decorator', async (t) => { 83 | const cases: Cases = [ 84 | [ 85 | '200 if request size > 512', 86 | '/req-limit-512-class', 87 | 'post', 88 | testData, 89 | { data: '188', status: 200 }, 90 | ], 91 | [ 92 | '403 if request size > 512', 93 | '/req-limit-512-class', 94 | 'post', 95 | testData.padEnd(1000), 96 | // @ts-ignore 97 | { status: 403 }, 98 | ], 99 | ] 100 | await processTestCases(cases, t) 101 | }) 102 | it('method decorator', async (t) => { 103 | const cases: Cases = [ 104 | [ 105 | '200 if request size > 512', 106 | '/req-limit-512-method', 107 | 'patch', 108 | testData, 109 | { data: '190', status: 200 }, 110 | ], 111 | [ 112 | '403 if request size > 512', 113 | '/req-limit-512-method', 114 | 'patch', 115 | testData.padEnd(1000), 116 | // @ts-ignore 117 | { status: 403 }, 118 | ], 119 | ] 120 | await processTestCases(cases, t) 121 | }) 122 | 123 | it('param decorator', async (t) => { 124 | const cases: Cases = [ 125 | [ 126 | 'return request size', 127 | '/return-req-size', 128 | 'post', 129 | testData, 130 | { data: '184', status: 200 }, 131 | ], 132 | ] 133 | 134 | await processTestCases(cases, t) 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /packages/consul/src/test/ts/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal, notEqual } from 'node:assert' 2 | import path from 'node:path' 3 | import { describe, it } from 'node:test' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 7 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 8 | 9 | import { Inject, Injectable } from '@nestjs/common' 10 | import { Test } from '@nestjs/testing' 11 | import lodash from 'lodash' 12 | 13 | import { ConsulModule, IConsulService } from '../../main/ts' 14 | import { FakeConsulDiscovery } from './mock/fakeConsulDiscovery' 15 | 16 | const testConfigPath = path.join( 17 | path.dirname(fileURLToPath(import.meta.url)), 18 | 'config', 19 | 'test.json', 20 | ) 21 | 22 | const toMatchObject = (actual: any, expected: any) => { 23 | equal(lodash.isMatch(actual, expected), true) 24 | } 25 | 26 | describe('logger module', () => { 27 | describe('index', () => { 28 | it('properly exposes its inners', () => { 29 | notEqual(ConsulModule, undefined) 30 | }) 31 | }) 32 | 33 | describe('consul module', () => { 34 | it('getConnectionParams works', async () => { 35 | @Injectable() 36 | class TestService { 37 | constructor(@Inject('IConsul') private consul: IConsulService) {} 38 | async getConnectionParams() { 39 | return this.consul.getConnectionParams('test-consul-service-name') 40 | } 41 | } 42 | 43 | const module = await Test.createTestingModule({ 44 | providers: [TestService], 45 | imports: [ 46 | ConfigModule.register({ path: testConfigPath }), 47 | LoggerModule, 48 | ConsulModule, 49 | ], 50 | }) 51 | .overrideProvider('IDiscoveryService') 52 | .useFactory({ factory: () => new FakeConsulDiscovery() }) 53 | .overrideProvider('ILogger') 54 | .useValue({ info: console.log }) 55 | .compile() 56 | 57 | toMatchObject(await module.get(TestService).getConnectionParams(), { 58 | host: 'test', 59 | port: 8080, 60 | }) 61 | }) 62 | 63 | it('getKv works', async () => { 64 | @Injectable() 65 | class TestService { 66 | constructor(@Inject('IConsul') private consul: IConsulService) {} 67 | 68 | async getKv() { 69 | return this.consul.getKv('test-consul-key') 70 | } 71 | } 72 | 73 | const module = await Test.createTestingModule({ 74 | providers: [TestService], 75 | imports: [ 76 | ConfigModule.register({ path: testConfigPath }), 77 | ConsulModule, 78 | LoggerModule, 79 | ], 80 | }) 81 | .overrideProvider('IDiscoveryService') 82 | .useFactory({ factory: () => new FakeConsulDiscovery() }) 83 | .overrideProvider('ILogger') 84 | .useValue({ info: (el: undefined) => el }) 85 | .compile() 86 | 87 | toMatchObject(await module.get(TestService).getKv(), { 88 | value: 'consul', 89 | }) 90 | }) 91 | 92 | it('register works', async () => { 93 | @Injectable() 94 | class TestService { 95 | constructor(@Inject('IConsul') private consul: IConsulService) {} 96 | async register() { 97 | return this.consul.register('test-consul-service-name') 98 | } 99 | } 100 | 101 | const module = await Test.createTestingModule({ 102 | providers: [TestService], 103 | imports: [ 104 | ConfigModule.register({ path: testConfigPath }), 105 | ConsulModule, 106 | LoggerModule, 107 | ], 108 | }) 109 | .overrideProvider('IDiscoveryService') 110 | .useFactory({ factory: () => new FakeConsulDiscovery() }) 111 | .overrideProvider('ILogger') 112 | .useValue({ info: (el: undefined) => el }) 113 | .compile() 114 | 115 | equal(await module.get(TestService).register(), 'registered in consul OK') 116 | }) 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /packages/connection-provider/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## @qiwi/nestjs-enterprise-connection-provider [1.1.1](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.1.0...@qiwi/nestjs-enterprise-connection-provider@1.1.1) (2022-07-21) 2 | 3 | 4 | ### Performance Improvements 5 | 6 | * use yarn@4, update deps ([#56](https://github.com/qiwi/nestjs-enterprise/issues/56)) ([e166672](https://github.com/qiwi/nestjs-enterprise/commit/e166672d31c4ae19be1b6093dcdc3798856bb6aa)) 7 | 8 | 9 | 10 | 11 | 12 | ### Dependencies 13 | 14 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.3.1 15 | 16 | # @qiwi/nestjs-enterprise-connection-provider [1.1.0](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.5...@qiwi/nestjs-enterprise-connection-provider@1.1.0) (2022-04-21) 17 | 18 | 19 | ### Features 20 | 21 | * update logger ([b0e9d27](https://github.com/qiwi/nestjs-enterprise/commit/b0e9d27a513b78917a01d8221b3f4c0c663ae8f8)) 22 | 23 | 24 | 25 | 26 | 27 | ### Dependencies 28 | 29 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.3.0 30 | 31 | ## @qiwi/nestjs-enterprise-connection-provider [1.0.5](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.4...@qiwi/nestjs-enterprise-connection-provider@1.0.5) (2021-10-01) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * **package:** update nestjs-* deps, update consul ([0f811d3](https://github.com/qiwi/nestjs-enterprise/commit/0f811d3e0a52dfb4726774aaf94dc7ba914b296d)) 37 | 38 | 39 | 40 | 41 | 42 | ### Dependencies 43 | 44 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.2.5 45 | 46 | ## @qiwi/nestjs-enterprise-connection-provider [1.0.4](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.3...@qiwi/nestjs-enterprise-connection-provider@1.0.4) (2021-09-22) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **consul:** fix consul service discovery ([7b635a9](https://github.com/qiwi/nestjs-enterprise/commit/7b635a9925358fe60de3af070e3b6f358595910c)) 52 | 53 | 54 | 55 | 56 | 57 | ### Dependencies 58 | 59 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.2.4 60 | 61 | ## @qiwi/nestjs-enterprise-connection-provider [1.0.3](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.2...@qiwi/nestjs-enterprise-connection-provider@1.0.3) (2021-07-29) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * **pkg:** update deps, fix some vuls ([49c1bff](https://github.com/qiwi/nestjs-enterprise/commit/49c1bff99d37e3b95569e85e4210f164884b2ca2)) 67 | 68 | 69 | 70 | 71 | 72 | ### Dependencies 73 | 74 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.2.3 75 | 76 | ## @qiwi/nestjs-enterprise-connection-provider [1.0.2](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.1...@qiwi/nestjs-enterprise-connection-provider@1.0.2) (2021-07-14) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * up deps ([1d4906c](https://github.com/qiwi/nestjs-enterprise/commit/1d4906c84e6858328220d2a27a3d29192d21fca8)) 82 | 83 | 84 | 85 | 86 | 87 | ### Dependencies 88 | 89 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.2.2 90 | 91 | ## @qiwi/nestjs-enterprise-connection-provider [1.0.1](https://github.com/qiwi/nestjs-enterprise/compare/@qiwi/nestjs-enterprise-connection-provider@1.0.0...@qiwi/nestjs-enterprise-connection-provider@1.0.1) (2021-04-18) 92 | 93 | 94 | ### Performance Improvements 95 | 96 | * update deps ([#25](https://github.com/qiwi/nestjs-enterprise/issues/25)) ([0f4609d](https://github.com/qiwi/nestjs-enterprise/commit/0f4609d372deb4e5af1943c8505d03cb174356ae)) 97 | 98 | 99 | 100 | 101 | 102 | ### Dependencies 103 | 104 | * **@qiwi/nestjs-enterprise-infra:** upgraded to 1.2.1 105 | 106 | # @qiwi/nestjs-enterprise-connection-provider 1.0.0 (2021-02-26) 107 | 108 | 109 | ### Features 110 | 111 | * add thrift and connection-provider modules ([656761d](https://github.com/qiwi/nestjs-enterprise/commit/656761d137aa5d1d93ae364ce489e2061e23e8bf)) 112 | -------------------------------------------------------------------------------- /packages/common/src/test/ts/port.spec.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'node:assert' 2 | import http from 'node:http' 3 | import { after, before, describe, it } from 'node:test' 4 | 5 | import { Controller, Get, Module, ValidationPipe } from '@nestjs/common' 6 | import { NestFactory } from '@nestjs/core' 7 | import { ExpressAdapter } from '@nestjs/platform-express' 8 | import axios from 'axios' 9 | import express from 'express' 10 | import { factory as iop } from 'inside-out-promise' 11 | import lodash from 'lodash' 12 | 13 | import { Port } from '../../main/ts' 14 | 15 | const toMatchObject = (actual: any, expected: any) => { 16 | equal(lodash.isMatch(actual, expected), true) 17 | } 18 | 19 | const multiport = (server: any) => { 20 | const instances: any[] = [] 21 | 22 | const multiServer = { 23 | listen(port: number | number[], host: string): Promise { 24 | // @ts-ignore 25 | const ports = Array.isArray(port) ? port : [port] 26 | return Promise.all( 27 | ports.map((port: number) => { 28 | const promise = iop() 29 | // @ts-ignore 30 | const instance = http.createServer(server) 31 | instances.push(instance) 32 | 33 | instance 34 | // @ts-ignore 35 | .listen(port, host, (err, data) => { 36 | if (err) { 37 | multiServer.close() 38 | promise.reject(err) 39 | } else { 40 | promise.resolve(data) 41 | } 42 | }) 43 | 44 | return promise 45 | }), 46 | ) 47 | }, 48 | 49 | close() { 50 | return Promise.all( 51 | instances.map((instance) => { 52 | const promise = iop() 53 | if (instance.listening) { 54 | instance.close(() => promise.resolve()) 55 | } else { 56 | promise.resolve() 57 | } 58 | return promise 59 | }), 60 | ) 61 | }, 62 | } 63 | return multiServer 64 | } 65 | 66 | @Controller() 67 | export class TestController { 68 | @Port('8080') 69 | @Get('only8080') 70 | async only8080(@Port() port: number) { 71 | return port 72 | } 73 | 74 | @Port('8081') 75 | @Get('only8081') 76 | async only8081(@Port() port: number) { 77 | return port 78 | } 79 | } 80 | 81 | @Module({ 82 | controllers: [TestController], 83 | }) 84 | class AppModule {} 85 | 86 | describe('port decorators', () => { 87 | let multiServer: ReturnType 88 | 89 | const host = '127.0.0.1' 90 | before(async () => { 91 | // eslint-disable-next-line unicorn/consistent-function-scoping 92 | async function bootstrap() { 93 | const server = express() 94 | const app = await NestFactory.create( 95 | AppModule, 96 | new ExpressAdapter(server), 97 | { cors: true }, 98 | ) 99 | 100 | app.useGlobalPipes( 101 | new ValidationPipe({ 102 | whitelist: true, 103 | disableErrorMessages: false, 104 | }), 105 | ) 106 | 107 | await app.init() 108 | 109 | multiServer = multiport(server) 110 | await multiServer.listen([8080, 8081], host) 111 | } 112 | 113 | await bootstrap() 114 | }) 115 | 116 | after(async () => { 117 | await multiServer.close() 118 | }) 119 | 120 | it('test cases', async (t) => { 121 | const cases: Array<[string, string, any, any?]> = [ 122 | [ 123 | '8080 > 8080 = 200', 124 | `http://${host}:8080/only8080`, 125 | { data: 8080, status: 200 }, 126 | ], 127 | [ 128 | '8081 > 8081 = 200', 129 | `http://${host}:8081/only8081`, 130 | { data: 8081, status: 200 }, 131 | ], 132 | [ 133 | '8081 > 8080 = 403', 134 | `http://${host}:8081/only8080`, 135 | null, // eslint-disable-line unicorn/no-null 136 | { response: { status: 403 } }, 137 | ], 138 | [ 139 | '8080 > 8081 = 403', 140 | `http://${host}:8080/only8081`, 141 | null, // eslint-disable-line unicorn/no-null 142 | { response: { status: 403 } }, 143 | ], 144 | ] 145 | 146 | for await (const [description, url, succ, err] of cases) { 147 | await t.test(description, async () => { 148 | await axios 149 | .get(url) 150 | .then((data) => toMatchObject(data, succ)) 151 | .catch((e) => toMatchObject(e, err)) 152 | }) 153 | } 154 | }) 155 | }) 156 | -------------------------------------------------------------------------------- /packages/metric/README.md: -------------------------------------------------------------------------------- 1 | # @qiwi/nestjs-enterprise-metric 2 | Nestjs metric module 3 | 4 | ## Installation 5 | ```shell script 6 | yarn add @qiwi/nestjs-enterprise-metric 7 | ``` 8 | 9 | ## Usage 10 | ```typescript 11 | // main.ts 12 | async function bootstrap() { 13 | const app = await NestFactory.create(AppModule) 14 | metricService = app.get('IMetricService') 15 | metricService.attach(getNodeMetrics) 16 | 17 | // ...etc 18 | } 19 | ``` 20 | ```typescript 21 | import { 22 | GraphiteService, 23 | MetricDecorator, 24 | MetricService, 25 | ErrorDecorator, 26 | MeteredDecorator, 27 | RequestRateDecorator, 28 | } from '@qiwi/nestjs-enterprise-metric' 29 | import {Controller, Get, Module} from "@nestjs/common"; 30 | 31 | @Controller() 32 | export class TestClassController { 33 | @Get('MeteredDecorator') 34 | @MetricDecorator('Controller') 35 | async test() { 36 | return 'foo' 37 | } 38 | 39 | @Get('ErrorDecorator') 40 | @ErrorDecorator('Controller') 41 | async error() { 42 | return 'foo' 43 | } 44 | 45 | @Get('MeteredDecorator') 46 | @MeteredDecorator('Controller') 47 | async meter() { 48 | return 'foo' 49 | } 50 | 51 | @Get('RequestRateDecorator') 52 | @RequestRateDecorator('Controller') 53 | async rate() { 54 | return 'foo' 55 | } 56 | } 57 | 58 | @Module({ 59 | controllers: [TestClassController], 60 | providers: [{ 61 | provide: 'IMetricService', 62 | useFactory: (graphiteService)=>{ 63 | return new MetricService(graphiteService).register({prefix: '$some$your$metric-prefix', interval: 1000}) 64 | } 65 | }, { 66 | provide: 'IGraphiteService', 67 | useFactory: ()=>{ 68 | return new GraphiteService('yourGraphiteApiEndpoint') 69 | } 70 | }] 71 | }) 72 | 73 | export class AppModule {} 74 | ``` 75 | ### MetricModule 76 | The MetricModule provides functionality for working with metrics in your application. 77 | It adds the MetricService to the application using the IMetricService token and GraphiteService using the IGraphiteService token. 78 | ConfigModule and LoggerModule are required for MetricModule to work 79 | 80 | ### Static import 81 | ```typescript 82 | import { Module } from '@nestjs/common' 83 | import { MetricModule} from '@qiwi/nestjs-enterprise-metric' 84 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 85 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 86 | 87 | @Module({ 88 | imports: [ 89 | MetricModule, 90 | ConfigModule, 91 | LoggerModule 92 | ], 93 | }) 94 | export class AppModule {} 95 | ``` 96 | In this case you should specify parameters in config file 97 | ```json 98 | { 99 | "data": { 100 | "name": "test-name-app", 101 | 102 | "graphite": { 103 | "url": "http://graphite-url.com" 104 | }, 105 | 106 | "metric":{ 107 | "prefix": "metric", 108 | "interval": 30 109 | }, 110 | } 111 | } 112 | ``` 113 | 114 | ### Dynamic import 115 | 116 | ```typescript 117 | import { Module } from '@nestjs/common' 118 | import { MetricModule} from '@qiwi/nestjs-enterprise-metric' 119 | import { ConfigModule } from '@qiwi/nestjs-enterprise-config' 120 | import { LoggerModule } from '@qiwi/nestjs-enterprise-logger' 121 | 122 | @Module({ 123 | imports: [ 124 | MetricModule.register({ 125 | graphiteApiEndpoint: 'https://localhost:8080', 126 | metricsConfig: { prefix: 'some-prefix'; interval: 5000 }, 127 | }), 128 | ConfigModule.register({ path: 'some/path' }), 129 | LoggerModule 130 | ], 131 | }) 132 | export class AppModule {} 133 | ``` 134 | You can pass parameters to a `MetricModule.register()` method 135 | - graphiteUrlOrService - URL for connecting to the Graphite API or your own implementation of IGraphiteService 136 | - metricsConfig.prefix - prefix for metric entry name 137 | - metricsConfig.interval - period of metric sending in ms 138 | 139 | ## API 140 | ### Class MetricModule 141 | Exports `MetricService` with token `IMetricService` 142 | 143 | ### ErrorDecorator 144 | The decorator measures the number of error events and also tracks 1-, 5-, and 15-minute moving averages. 145 | ### MeteredDecorator 146 | The decorator measures the number of function calls and also tracks 1-, 5-, and 15-minute moving averages. 147 | ### RequestRateDecorator 148 | The decorator collects statistically significant query processing times skewed toward the last 5 minutes to examine their distribution. 149 | ### MetricDecorator 150 | Union ErrorDecorator, MeteredDecorator and RequestRateDecorator 151 | ### attach 152 | Attach your metric 153 | ### getNodeMetrics 154 | Return process and os metric. 155 | 156 | 157 | ### [Docs](https://qiwi.github.io/nestjs-enterprise/metric/) 158 | -------------------------------------------------------------------------------- /packages/metric/src/main/ts/metric.service.ts: -------------------------------------------------------------------------------- 1 | import type { ILogger } from '@qiwi/substrate' 2 | 3 | import { Inject, Injectable, OnModuleDestroy } from '@nestjs/common' 4 | // @ts-ignore 5 | import { Histogram, Meter, Timer } from 'measured-core' 6 | 7 | import { IMetricService } from './metric.service.interface' 8 | 9 | @Injectable() 10 | export class MetricService implements OnModuleDestroy, IMetricService { 11 | private collectionTimer: Record = {} 12 | private collectionHistogram: Record = {} 13 | private collectionMeter: Record = {} 14 | private metricsCallbacks: Array<(args?: Array) => any> = [] 15 | 16 | private readonly metricPrefix 17 | private readonly interval: any 18 | 19 | onModuleDestroy() { 20 | this.clearInterval() 21 | } 22 | 23 | constructor( 24 | @Inject('IGraphiteService') private graphiteService: any, 25 | opts: { prefix: string; interval: number }, 26 | @Inject('ILogger') private logger: ILogger, 27 | ) { 28 | this.metricPrefix = opts.prefix 29 | if (opts.interval) { 30 | this.interval = setInterval(this.push.bind(this), opts.interval) 31 | } 32 | } 33 | 34 | histogram(metricName: string) { 35 | if (!this.collectionHistogram[metricName]) { 36 | this.collectionHistogram[metricName] = new Histogram() 37 | } 38 | 39 | const histogram = this.collectionHistogram[metricName] 40 | return { 41 | update(value: number) { 42 | histogram.update(value) 43 | }, 44 | } 45 | } 46 | 47 | meter(metricName: string) { 48 | if (!this.collectionMeter[metricName]) { 49 | this.collectionMeter[metricName] = new Meter() 50 | } 51 | 52 | const meter = this.collectionMeter[metricName] 53 | return { 54 | update(value?: number) { 55 | meter.mark(value) 56 | }, 57 | } 58 | } 59 | 60 | timer(metricName: string) { 61 | if (!this.collectionTimer[metricName]) { 62 | this.collectionTimer[metricName] = new Timer() 63 | } 64 | 65 | const timer = this.collectionTimer[metricName] 66 | return { 67 | update(value?: number) { 68 | timer.update(value) 69 | }, 70 | } 71 | } 72 | 73 | async push() { 74 | try { 75 | const metric = { 76 | ...this.formatTimers(), 77 | ...this.formatMeter(), 78 | ...this.formatHistogram(), 79 | ...(await this.getMetricsFromCallbacks()), 80 | } 81 | 82 | return await this.graphiteService.sendMetric(metric) 83 | } catch (error) { 84 | this.logger.error('Error sending metric', error) 85 | } 86 | } 87 | 88 | /** 89 | * Attach metric 90 | * @param callback 91 | */ 92 | attach(callback: (args?: Array) => any) { 93 | this.metricsCallbacks.push(callback) 94 | } 95 | 96 | clearInterval() { 97 | clearInterval(this.interval) 98 | } 99 | 100 | private async getMetricsFromCallbacks() { 101 | const data = await Promise.all(this.metricsCallbacks.map((el) => el())) 102 | const flatData = data.reduce((acc, el) => ({ ...acc, ...el }), {}) 103 | 104 | return Object.entries(flatData).reduce((acc, [key, value]) => { 105 | return { ...acc, [`${this.metricPrefix}.${key}`]: value } 106 | }, {}) 107 | } 108 | 109 | private formatTimers() { 110 | return Object.entries(this.collectionTimer).reduce((acc, [name, value]) => { 111 | const { meter, histogram } = value.toJSON() 112 | 113 | return { 114 | ...acc, 115 | ...Object.entries(meter).reduce((acc, [key, value]) => { 116 | acc[`${this.metricPrefix}.${name}.meter.${key}`] = value 117 | return acc 118 | }, {} as Record), 119 | 120 | ...Object.entries(histogram).reduce((acc, [key, value]) => { 121 | acc[`${this.metricPrefix}.${name}.histogram.${key}`] = value 122 | return acc 123 | }, {} as Record), 124 | } 125 | }, {} as Record) 126 | } 127 | 128 | private formatMeter() { 129 | return Object.entries(this.collectionMeter).reduce((acc, [name, value]) => { 130 | return { 131 | ...acc, 132 | ...Object.entries(value.toJSON()).reduce((acc, [key, value]) => { 133 | acc[`${this.metricPrefix}.${name}.meter.${key}`] = value 134 | return acc 135 | }, {} as Record), 136 | } 137 | }, {} as Record) 138 | } 139 | 140 | private formatHistogram() { 141 | return Object.entries(this.collectionHistogram).reduce( 142 | (acc, [name, value]) => { 143 | return { 144 | ...acc, 145 | ...Object.entries(value.toJSON()).reduce((acc, [key, value]) => { 146 | acc[`${this.metricPrefix}.${name}.meter.${key}`] = value 147 | return acc 148 | }, {} as Record), 149 | } 150 | }, 151 | {} as Record, 152 | ) 153 | } 154 | } 155 | --------------------------------------------------------------------------------