mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16:53 +05:00
Implement DDD aggregate boundary validation to detect and prevent direct entity references across aggregate boundaries. Features: - Detect direct entity imports between aggregates - Allow only ID or Value Object references - Support multiple folder structures (domain/aggregates/*, domain/*, domain/entities/*) - Filter allowed imports (value-objects, events, repositories, services) - Critical severity level for violations - 41 comprehensive tests with 92.55% coverage - CLI output with detailed suggestions - Examples of good and bad patterns Breaking changes: None Backwards compatible: Yes
414 lines
9.4 KiB
TypeScript
414 lines
9.4 KiB
TypeScript
/**
|
|
* Rule names for code analysis
|
|
*/
|
|
export const RULES = {
|
|
CLEAN_ARCHITECTURE: "clean-architecture",
|
|
HARDCODED_VALUE: "hardcoded-value",
|
|
CIRCULAR_DEPENDENCY: "circular-dependency",
|
|
NAMING_CONVENTION: "naming-convention",
|
|
FRAMEWORK_LEAK: "framework-leak",
|
|
ENTITY_EXPOSURE: "entity-exposure",
|
|
DEPENDENCY_DIRECTION: "dependency-direction",
|
|
REPOSITORY_PATTERN: "repository-pattern",
|
|
AGGREGATE_BOUNDARY: "aggregate-boundary",
|
|
} as const
|
|
|
|
/**
|
|
* Hardcode types
|
|
*/
|
|
export const HARDCODE_TYPES = {
|
|
MAGIC_NUMBER: "magic-number",
|
|
MAGIC_STRING: "magic-string",
|
|
MAGIC_CONFIG: "magic-config",
|
|
} as const
|
|
|
|
/**
|
|
* Layer names
|
|
*/
|
|
export const LAYERS = {
|
|
DOMAIN: "domain",
|
|
APPLICATION: "application",
|
|
INFRASTRUCTURE: "infrastructure",
|
|
SHARED: "shared",
|
|
} as const
|
|
|
|
/**
|
|
* Naming convention violation types
|
|
*/
|
|
export const NAMING_VIOLATION_TYPES = {
|
|
WRONG_SUFFIX: "wrong-suffix",
|
|
WRONG_PREFIX: "wrong-prefix",
|
|
WRONG_CASE: "wrong-case",
|
|
FORBIDDEN_PATTERN: "forbidden-pattern",
|
|
WRONG_VERB_NOUN: "wrong-verb-noun",
|
|
} as const
|
|
|
|
/**
|
|
* Naming patterns for each layer
|
|
*/
|
|
export const NAMING_PATTERNS = {
|
|
DOMAIN: {
|
|
ENTITY: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*\.ts$/,
|
|
description: "PascalCase noun (User.ts, Order.ts)",
|
|
forbidden: ["Dto", "Request", "Response", "Controller"],
|
|
},
|
|
SERVICE: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*Service\.ts$/,
|
|
description: "*Service suffix (UserService.ts)",
|
|
},
|
|
VALUE_OBJECT: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*\.ts$/,
|
|
description: "PascalCase noun (Email.ts, Money.ts)",
|
|
},
|
|
REPOSITORY_INTERFACE: {
|
|
pattern: /^I[A-Z][a-zA-Z0-9]*Repository\.ts$/,
|
|
description: "I*Repository prefix (IUserRepository.ts)",
|
|
},
|
|
},
|
|
APPLICATION: {
|
|
USE_CASE: {
|
|
pattern: /^[A-Z][a-z]+[A-Z][a-zA-Z0-9]*\.ts$/,
|
|
description: "Verb in PascalCase (CreateUser.ts, UpdateProfile.ts)",
|
|
examples: ["CreateUser.ts", "UpdateProfile.ts", "DeleteOrder.ts"],
|
|
},
|
|
DTO: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*(Dto|Request|Response)\.ts$/,
|
|
description: "*Dto, *Request, *Response suffix",
|
|
examples: ["UserResponseDto.ts", "CreateUserRequest.ts"],
|
|
},
|
|
MAPPER: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*Mapper\.ts$/,
|
|
description: "*Mapper suffix (UserMapper.ts)",
|
|
},
|
|
},
|
|
INFRASTRUCTURE: {
|
|
CONTROLLER: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*Controller\.ts$/,
|
|
description: "*Controller suffix (UserController.ts)",
|
|
},
|
|
REPOSITORY_IMPL: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*Repository\.ts$/,
|
|
description: "*Repository suffix (PrismaUserRepository.ts, MongoUserRepository.ts)",
|
|
},
|
|
SERVICE: {
|
|
pattern: /^[A-Z][a-zA-Z0-9]*(Service|Adapter)\.ts$/,
|
|
description: "*Service or *Adapter suffix (EmailService.ts, S3StorageAdapter.ts)",
|
|
},
|
|
},
|
|
} as const
|
|
|
|
/**
|
|
* Common verbs for use cases
|
|
*/
|
|
export const USE_CASE_VERBS = [
|
|
"Analyze",
|
|
"Create",
|
|
"Update",
|
|
"Delete",
|
|
"Get",
|
|
"Find",
|
|
"List",
|
|
"Search",
|
|
"Validate",
|
|
"Calculate",
|
|
"Generate",
|
|
"Send",
|
|
"Fetch",
|
|
"Process",
|
|
"Execute",
|
|
"Handle",
|
|
"Register",
|
|
"Authenticate",
|
|
"Authorize",
|
|
"Import",
|
|
"Export",
|
|
"Place",
|
|
"Cancel",
|
|
"Approve",
|
|
"Reject",
|
|
"Confirm",
|
|
] as const
|
|
|
|
/**
|
|
* Framework-specific packages that should not be imported in domain layer
|
|
* These frameworks create tight coupling and violate Clean Architecture principles
|
|
*/
|
|
export const FRAMEWORK_PACKAGES = {
|
|
ORM: [
|
|
"@prisma/client",
|
|
"prisma",
|
|
"typeorm",
|
|
"mongoose",
|
|
"sequelize",
|
|
"@mikro-orm/core",
|
|
"@mikro-orm/mongodb",
|
|
"@mikro-orm/postgresql",
|
|
"drizzle-orm",
|
|
"knex",
|
|
"objection",
|
|
"bookshelf",
|
|
"waterline",
|
|
"massive",
|
|
"pg",
|
|
"mysql",
|
|
"mysql2",
|
|
"sqlite3",
|
|
"better-sqlite3",
|
|
"mongodb",
|
|
"monk",
|
|
"tingodb",
|
|
"nedb",
|
|
"levelup",
|
|
"cassandra-driver",
|
|
"couchbase",
|
|
"redis-om",
|
|
],
|
|
WEB_FRAMEWORK: [
|
|
"express",
|
|
"fastify",
|
|
"koa",
|
|
"@koa/router",
|
|
"koa-router",
|
|
"@nestjs/common",
|
|
"@nestjs/core",
|
|
"@nestjs/platform-express",
|
|
"@nestjs/platform-fastify",
|
|
"hapi",
|
|
"@hapi/hapi",
|
|
"restify",
|
|
"polka",
|
|
"micro",
|
|
"next",
|
|
"nuxt",
|
|
"sails",
|
|
"adonis",
|
|
"@adonisjs/core",
|
|
"loopback",
|
|
"@loopback/core",
|
|
"feathers",
|
|
"@feathersjs/feathers",
|
|
"meteor",
|
|
"strapi",
|
|
"@strapi/strapi",
|
|
"total.js",
|
|
"actionhero",
|
|
],
|
|
HTTP_CLIENT: [
|
|
"axios",
|
|
"node-fetch",
|
|
"got",
|
|
"superagent",
|
|
"request",
|
|
"request-promise",
|
|
"request-promise-native",
|
|
"needle",
|
|
"bent",
|
|
"phin",
|
|
"ky",
|
|
"undici",
|
|
"@apollo/client",
|
|
"graphql-request",
|
|
"urql",
|
|
"isomorphic-fetch",
|
|
"cross-fetch",
|
|
"fetch-retry",
|
|
"wretch",
|
|
"httpie",
|
|
],
|
|
VALIDATION: [
|
|
"joi",
|
|
"yup",
|
|
"zod",
|
|
"class-validator",
|
|
"ajv",
|
|
"validator",
|
|
"express-validator",
|
|
"celebrate",
|
|
"superstruct",
|
|
"io-ts",
|
|
"runtypes",
|
|
"valibot",
|
|
"fastest-validator",
|
|
"validatorjs",
|
|
"vine",
|
|
"@vinejs/vine",
|
|
"vest",
|
|
"json-schema",
|
|
"jsonschema",
|
|
],
|
|
DI_CONTAINER: [
|
|
"inversify",
|
|
"tsyringe",
|
|
"awilix",
|
|
"typedi",
|
|
"bottlejs",
|
|
"injection-js",
|
|
"vue-di",
|
|
"angular",
|
|
"@angular/core",
|
|
"di-ts",
|
|
"power-di",
|
|
],
|
|
LOGGER: [
|
|
"winston",
|
|
"pino",
|
|
"bunyan",
|
|
"log4js",
|
|
"morgan",
|
|
"signale",
|
|
"consola",
|
|
"roarr",
|
|
"loglevel",
|
|
"debug",
|
|
"npmlog",
|
|
"@nestjs/logger",
|
|
"fancy-log",
|
|
"tracer",
|
|
"electron-log",
|
|
"simple-node-logger",
|
|
],
|
|
CACHE: [
|
|
"redis",
|
|
"ioredis",
|
|
"memcached",
|
|
"node-cache",
|
|
"cache-manager",
|
|
"keyv",
|
|
"flat-cache",
|
|
"lru-cache",
|
|
"node-cache-manager",
|
|
"quick-lru",
|
|
"mem",
|
|
"memoizee",
|
|
"micro-memoize",
|
|
"async-cache",
|
|
"cacache",
|
|
],
|
|
MESSAGE_QUEUE: [
|
|
"amqplib",
|
|
"bull",
|
|
"bullmq",
|
|
"bee-queue",
|
|
"kafkajs",
|
|
"rabbitmq",
|
|
"amqp",
|
|
"aws-sdk",
|
|
"@aws-sdk/client-sqs",
|
|
"@aws-sdk/client-sns",
|
|
"@azure/service-bus",
|
|
"azure-sb",
|
|
"@google-cloud/pubsub",
|
|
"rsmq",
|
|
"mqtt",
|
|
"rhea",
|
|
"stompit",
|
|
"activemq",
|
|
"zeromq",
|
|
"nanomsg",
|
|
"kue",
|
|
"agenda",
|
|
"bree",
|
|
],
|
|
EMAIL: [
|
|
"nodemailer",
|
|
"sendgrid",
|
|
"@sendgrid/mail",
|
|
"mailgun-js",
|
|
"mailgun.js",
|
|
"postmark",
|
|
"sparkpost",
|
|
"ses",
|
|
"@aws-sdk/client-ses",
|
|
"emailjs",
|
|
"email-templates",
|
|
"mjml",
|
|
"pug",
|
|
"handlebars",
|
|
"sendmail",
|
|
"smtp-server",
|
|
"mailparser",
|
|
"imap",
|
|
"imap-simple",
|
|
],
|
|
STORAGE: [
|
|
"aws-sdk",
|
|
"@aws-sdk/client-s3",
|
|
"@aws-sdk/s3-request-presigner",
|
|
"multer",
|
|
"multer-s3",
|
|
"@google-cloud/storage",
|
|
"@azure/storage-blob",
|
|
"azure-storage",
|
|
"minio",
|
|
"formidable",
|
|
"busboy",
|
|
"multiparty",
|
|
"express-fileupload",
|
|
"gridfs-stream",
|
|
"s3-upload-stream",
|
|
"knox",
|
|
"pkgcloud",
|
|
"@supabase/storage-js",
|
|
"cloudinary",
|
|
],
|
|
TESTING: [
|
|
"jest",
|
|
"@jest/globals",
|
|
"mocha",
|
|
"chai",
|
|
"sinon",
|
|
"supertest",
|
|
"cypress",
|
|
"@cypress/vue",
|
|
"playwright",
|
|
"@playwright/test",
|
|
"vitest",
|
|
"@vitest/ui",
|
|
"ava",
|
|
"tap",
|
|
"tape",
|
|
"jasmine",
|
|
"@testing-library/react",
|
|
"@testing-library/vue",
|
|
"enzyme",
|
|
],
|
|
TEMPLATE_ENGINE: [
|
|
"ejs",
|
|
"pug",
|
|
"jade",
|
|
"handlebars",
|
|
"mustache",
|
|
"nunjucks",
|
|
"dot",
|
|
"underscore",
|
|
"lodash.template",
|
|
"hogan.js",
|
|
"swig",
|
|
"twig",
|
|
"marko",
|
|
"squirrelly",
|
|
"eta",
|
|
],
|
|
} as const
|
|
|
|
/**
|
|
* Error messages for framework leak violations
|
|
*/
|
|
export const FRAMEWORK_LEAK_MESSAGES = {
|
|
DOMAIN_IMPORT:
|
|
'Domain layer imports framework-specific package "{package}". Use interfaces and dependency injection instead.',
|
|
SUGGESTION: "Create an interface in domain layer and implement it in infrastructure layer.",
|
|
PACKAGE_PLACEHOLDER: "{package}",
|
|
} as const
|
|
|
|
/**
|
|
* Repository pattern violation types
|
|
*/
|
|
export const REPOSITORY_VIOLATION_TYPES = {
|
|
ORM_TYPE_IN_INTERFACE: "orm-type-in-interface",
|
|
CONCRETE_REPOSITORY_IN_USE_CASE: "concrete-repository-in-use-case",
|
|
NEW_REPOSITORY_IN_USE_CASE: "new-repository-in-use-case",
|
|
NON_DOMAIN_METHOD_NAME: "non-domain-method-name",
|
|
} as const
|