Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 0 additions & 46 deletions .eslintrc.js

This file was deleted.

31 changes: 31 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"eslint.enable": true,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"eslint.options": {
"overrideConfigFile": "eslint.config.mjs"
},
"eslint.format.enable": true,
"eslint.lintTask.enable": true,
"eslint.run": "onSave",
"eslint.problems.shortenToSingleLine": false,

"eslint.trace.server": "off",
"eslint.debug": false,

"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},

"js/ts.preferences.importModuleSpecifier": "relative",
"js/ts.preferences.preferTypeOnlyAutoImports": false,

"prettier.enable": true,
"prettier.requireConfig": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
205 changes: 205 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginJsdoc from 'eslint-plugin-jsdoc';
import pluginPerfectionist from 'eslint-plugin-perfectionist';
import functional from 'eslint-plugin-functional';
import pluginUnicorn from 'eslint-plugin-unicorn';
import pluginSonarjs from 'eslint-plugin-sonarjs';

export default tseslint.config(
{
ignores: ['node_modules', 'dist', '**/*.js', '**/*.d.ts', 'infra', 'migrations'],
},
eslint.configs.recommended,
...tseslint.configs.recommended,
{
plugins: {
functional,
perfectionist: pluginPerfectionist,
unicorn: pluginUnicorn,
sonarjs: pluginSonarjs,
jsdoc: pluginJsdoc,
},
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
args: 'after-used',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/prefer-readonly': 'off',
'@typescript-eslint/prefer-readonly-parameter-types': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: false,
fixStyle: 'separate-type-imports',
},
],
'functional/prefer-readonly-type': 'off',
'functional/no-conditional-statements': 'off',
'functional/no-return-void': 'off',
'functional/immutable-data': 'warn',
'functional/no-let': 'off',
'functional/no-expression-statements': 'off',

'perfectionist/sort-imports': [
'error',
{
type: 'natural',
order: 'asc',
groups: [
'builtin', // node:fs
'external', // @nestjs, rxjs
'internal', // @core, @shared
'parent', // ../
'sibling', // ./
'index', // index.ts
'type', // type imports
],
},
],
'no-duplicate-imports': 'error',

'unicorn/filename-case': [
'error',
{
case: 'kebabCase',
ignore: ['index.ts', '\\.d\\.ts$'],
},
],
'unicorn/prefer-node-protocol': 'error',
'unicorn/no-array-method-this-argument': 'warn',
'unicorn/prefer-structured-clone': 'error',
'unicorn/no-useless-undefined': 'error',
'unicorn/prefer-export-from': 'error',
'unicorn/prefer-spread': 'warn',
'unicorn/no-array-reduce': 'warn',
'unicorn/no-array-push-push': 'warn',

'sonarjs/cognitive-complexity': ['error', 15],
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
'sonarjs/no-identical-functions': 'error',
'sonarjs/no-collapsible-if': 'error',
'sonarjs/no-unused-collection': 'error',

'jsdoc/require-description': ['warn', { descriptionStyle: 'body' }],
'jsdoc/check-param-names': 'error',
'jsdoc/check-types': 'error',
'jsdoc/require-param-type': 'error',
'jsdoc/require-returns-type': 'error',

eqeqeq: ['error', 'always'],
curly: ['error', 'all'],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-return-await': 'error',
'require-await': 'error',
'no-var': 'error',
'prefer-const': 'error',
'prefer-template': 'error',
'object-shorthand': 'error',
'arrow-body-style': ['error', 'as-needed'],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/await-thenable': 'error',

'no-restricted-syntax': [
'error',
{
selector: `ImportDeclaration[importKind=type] > ImportSpecifier[imported.name=/.*Dto/]`,
message:
'DTO с декораторами должны использовать обычный импорт, а не import type',
},
{
selector: `ImportDeclaration[importKind=type] > ImportSpecifier[imported.name=/.*Validation/]`,
message:
'Классы валидации должны использовать обычный импорт, а не import type',
},
{
selector: `ImportDeclaration[importKind=type] > ImportSpecifier[imported.name=/.*Entity/]`,
message:
'Entity с декораторами должны использовать обычный импорт, а не import type',
},
{
selector: 'WhileStatement',
message:
'⚠️ Цикл while может быть бесконечным. Используйте for или for...of с явным счётчиком',
},
{
selector: 'DoWhileStatement',
message: '⚠️ Цикл do-while сложнее читать. Используйте for или for...of',
},
{
selector: 'ForInStatement',
message: 'Цикл for-in включает прототип. Используйте Object.keys() + for...of',
},
{
selector: 'LabeledStatement',
message: 'Метки делают код нечитаемым. Используйте функции с return',
},
{
selector: 'SwitchStatement[cases.length>10]',
message: '⚠️ Switch с более чем 10 кейсами. Используйте Map или объект',
},
{
selector: 'SwitchStatement:not([cases.length<=5])',
message:
'⚠️ Большой switch statement. Рассмотрите использование Map или реестра стратегий',
},
],
'no-restricted-properties': [
'error',
{
object: 'Array',
property: 'pop',
message: 'Используйте slice/spread вместо pop',
},
{
object: 'Array',
property: 'splice',
message: 'Используйте filter/slice вместо splice',
},
{
object: 'Object',
property: 'assign',
message: 'Используйте spread оператор: {...obj, newProp} вместо Object.assign',
},
],
},
},
{
files: ['infra/**/*.ts', '**/migrations/**/*.ts', '**/*.config.ts', 'libs/**/*.ts'],
rules: {
'functional/no-conditional-statements': 'off',
'functional/immutable-data': 'off',
},
},
{
files: [
'**/*.{facade,repository,service,controller,query,use-case,adapter}.ts',
'**/controller.ts',
'**/adapter.ts',
],
rules: {
'require-await': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-useless-constructor': 'off',
'sonarjs/cognitive-complexity': 'off',
'unicorn/no-useless-undefined': 'off',
'unicorn/prefer-export-from': 'off',
'functional/immutable-data': 'off',
},
},
);
40 changes: 23 additions & 17 deletions libs/bootstrap/src/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import fastifyCompress from '@fastify/compress';
import fastifyCookie from '@fastify/cookie';
import fastifyCsrf from '@fastify/csrf-protection';
import fastifyMultipart from '@fastify/multipart';
import { Logger, VersioningType } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { createId } from '@paralleldrive/cuid2';

import { DEFAULT_THROTTLER_OPTIONS } from './configs/throttler';
import { setupCors, setupLogger, setupThrottler, setupSwagger } from './setups';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';

import type { BootstrapOptions } from './interfaces/options.interface';
import fastifyCookie from '@fastify/cookie';
import fastifyCompress from '@fastify/compress';
import fastifyMultipart from '@fastify/multipart';
import fastifyCsrf from '@fastify/csrf-protection';
import { createId } from '@paralleldrive/cuid2';
import type { IncomingMessage } from 'http';
import type { IncomingMessage } from 'node:http';

export async function bootstrapApp(options: BootstrapOptions) {
const startTime = performance.now();
const adapter = new FastifyAdapter({
requestIdHeader: 'x-request-id',
requestIdLogLabel: 'request',
genReqId: (req: IncomingMessage) => {
return (req.headers['x-request-id'] as string) || createId();
},
genReqId: (req: IncomingMessage) => (req.headers['x-request-id'] as string) || createId(),
});

const {
Expand Down Expand Up @@ -56,7 +56,7 @@

app.getHttpAdapter()
.getInstance()
.addHook('onSend', async (request, reply, payload) => {
.addHook('onSend', (request, reply, payload) => {
reply.header('x-request-id', request.id);
return payload;
})
Expand All @@ -71,7 +71,7 @@
* хука для Node.js HTTP-слоя (this.raw). Приведение к 'any' здесь является легитимным решением
* для динамического monkey-patching-а.
*/
.addHook('onRequest', (request: any, reply: any, done) => {

Check warning on line 74 in libs/bootstrap/src/bootstrap.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unexpected any. Specify a different type

Check warning on line 74 in libs/bootstrap/src/bootstrap.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unexpected any. Specify a different type

Check warning on line 74 in libs/bootstrap/src/bootstrap.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unexpected any. Specify a different type

Check warning on line 74 in libs/bootstrap/src/bootstrap.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unexpected any. Specify a different type
reply.setHeader = function (key: string, value: string) {
return this.raw.setHeader(key, value);
};
Expand All @@ -82,7 +82,7 @@
done();
});

await setupLogger(app, options.serviceName);
setupLogger(app, options.serviceName);

await app.register(fastifyCompress, {
global: true,
Expand All @@ -97,7 +97,9 @@
},
});

if (apiPrefix) app.setGlobalPrefix(apiPrefix);
if (apiPrefix) {
app.setGlobalPrefix(apiPrefix);
}
if (version) {
const hasV = version.startsWith('v');

Expand All @@ -107,7 +109,9 @@
defaultVersion: hasV ? version.slice(1) : version,
});
}
if (useCors) setupCors(app, origins);
if (useCors) {
setupCors(app, origins);
}
if (swaggerOptions) {
const { path = 'docs', ...metadata } = swaggerOptions;

Expand Down Expand Up @@ -138,13 +142,15 @@
},
});
}
if (setupApp) setupApp(app);
if (setupApp) {
await setupApp(app);
}

await app.listen(port, '0.0.0.0', (_err, address) => {
const prefix = [apiPrefix, version].filter(Boolean).join('/');
const baseUrl = `${address}${prefix ? '/' + prefix : ''}`;
const baseUrl = `${address}${prefix ? `/${prefix}` : ''}`;

const swaggerBase = `${address}${apiPrefix ? '/' + apiPrefix : ''}`;
const swaggerBase = `${address}${apiPrefix ? `/${apiPrefix}` : ''}`;
const swaggerPath = swaggerOptions?.path ?? 'docs';

if (_err) {
Expand Down
1 change: 1 addition & 0 deletions libs/bootstrap/src/configs/throttler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dotenv/config';

import type { ThrottlerModuleOptions } from '@nestjs/throttler';

export const DEFAULT_THROTTLER_OPTIONS: ThrottlerModuleOptions = [
Expand Down
Loading
Loading