@corbat-tech/coding-standards-mcp 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -337
- package/dist/agent.d.ts +5 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +95 -217
- package/dist/agent.js.map +1 -1
- package/dist/analysis/code-analyzer.d.ts +44 -0
- package/dist/analysis/code-analyzer.d.ts.map +1 -0
- package/dist/analysis/code-analyzer.js +528 -0
- package/dist/analysis/code-analyzer.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +112 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrails.d.ts +35 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +303 -0
- package/dist/guardrails.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +40 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +97 -0
- package/dist/metrics.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +239 -108
- package/dist/profiles.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/tools/definitions.d.ts +143 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +229 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/get-context.d.ts +12 -0
- package/dist/tools/handlers/get-context.d.ts.map +1 -0
- package/dist/tools/handlers/get-context.js +233 -0
- package/dist/tools/handlers/get-context.js.map +1 -0
- package/dist/tools/handlers/health.d.ts +11 -0
- package/dist/tools/handlers/health.d.ts.map +1 -0
- package/dist/tools/handlers/health.js +57 -0
- package/dist/tools/handlers/health.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +12 -0
- package/dist/tools/handlers/index.d.ts.map +1 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/init.d.ts +12 -0
- package/dist/tools/handlers/init.d.ts.map +1 -0
- package/dist/tools/handlers/init.js +102 -0
- package/dist/tools/handlers/init.js.map +1 -0
- package/dist/tools/handlers/profiles.d.ts +11 -0
- package/dist/tools/handlers/profiles.d.ts.map +1 -0
- package/dist/tools/handlers/profiles.js +25 -0
- package/dist/tools/handlers/profiles.js.map +1 -0
- package/dist/tools/handlers/search.d.ts +12 -0
- package/dist/tools/handlers/search.d.ts.map +1 -0
- package/dist/tools/handlers/search.js +58 -0
- package/dist/tools/handlers/search.js.map +1 -0
- package/dist/tools/handlers/validate.d.ts +15 -0
- package/dist/tools/handlers/validate.d.ts.map +1 -0
- package/dist/tools/handlers/validate.js +71 -0
- package/dist/tools/handlers/validate.js.map +1 -0
- package/dist/tools/handlers/verify.d.ts +38 -0
- package/dist/tools/handlers/verify.d.ts.map +1 -0
- package/dist/tools/handlers/verify.js +172 -0
- package/dist/tools/handlers/verify.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +20 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +141 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +92 -40
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/profiles/examples/microservice-kafka.yaml +122 -0
- package/profiles/examples/startup-fast.yaml +67 -0
- package/profiles/examples/strict-enterprise.yaml +62 -0
- package/profiles/templates/angular.yaml +614 -0
- package/profiles/templates/csharp-dotnet.yaml +529 -0
- package/profiles/templates/flutter.yaml +547 -0
- package/profiles/templates/go.yaml +1276 -0
- package/profiles/templates/java-spring-backend.yaml +326 -0
- package/profiles/templates/kotlin-spring.yaml +417 -0
- package/profiles/templates/nextjs.yaml +536 -0
- package/profiles/templates/nodejs.yaml +594 -0
- package/profiles/templates/python.yaml +546 -0
- package/profiles/templates/react.yaml +456 -0
- package/profiles/templates/rust.yaml +508 -0
- package/profiles/templates/vue.yaml +483 -0
|
@@ -336,3 +336,597 @@ security:
|
|
|
336
336
|
# ├── integration/
|
|
337
337
|
# ├── e2e/
|
|
338
338
|
# └── fixtures/
|
|
339
|
+
|
|
340
|
+
# ----------------------------------------------------------------------------
|
|
341
|
+
# CODE EXAMPLES
|
|
342
|
+
# ----------------------------------------------------------------------------
|
|
343
|
+
codeExamples:
|
|
344
|
+
entity:
|
|
345
|
+
description: "Domain entity with validation"
|
|
346
|
+
code: |
|
|
347
|
+
// domain/entities/order.ts
|
|
348
|
+
import { OrderId } from '../value-objects/order-id.js';
|
|
349
|
+
import { Money } from '../value-objects/money.js';
|
|
350
|
+
import { OrderItem } from './order-item.js';
|
|
351
|
+
import { DomainError } from '../errors/domain-error.js';
|
|
352
|
+
|
|
353
|
+
export class Order {
|
|
354
|
+
private constructor(
|
|
355
|
+
public readonly id: OrderId,
|
|
356
|
+
public readonly customerId: string,
|
|
357
|
+
private readonly items: OrderItem[],
|
|
358
|
+
public readonly status: OrderStatus,
|
|
359
|
+
public readonly createdAt: Date
|
|
360
|
+
) {}
|
|
361
|
+
|
|
362
|
+
static create(customerId: string, items: OrderItem[]): Order {
|
|
363
|
+
if (items.length === 0) {
|
|
364
|
+
throw new DomainError('Order must have at least one item');
|
|
365
|
+
}
|
|
366
|
+
return new Order(
|
|
367
|
+
OrderId.generate(),
|
|
368
|
+
customerId,
|
|
369
|
+
items,
|
|
370
|
+
'PENDING',
|
|
371
|
+
new Date()
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
get total(): Money {
|
|
376
|
+
return this.items.reduce(
|
|
377
|
+
(sum, item) => sum.add(item.subtotal),
|
|
378
|
+
Money.zero('USD')
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
addItem(item: OrderItem): Order {
|
|
383
|
+
if (this.status !== 'PENDING') {
|
|
384
|
+
throw new DomainError('Cannot add items to confirmed order');
|
|
385
|
+
}
|
|
386
|
+
return new Order(
|
|
387
|
+
this.id,
|
|
388
|
+
this.customerId,
|
|
389
|
+
[...this.items, item],
|
|
390
|
+
this.status,
|
|
391
|
+
this.createdAt
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
confirm(): Order {
|
|
396
|
+
if (this.status !== 'PENDING') {
|
|
397
|
+
throw new DomainError('Order is not pending');
|
|
398
|
+
}
|
|
399
|
+
return new Order(
|
|
400
|
+
this.id,
|
|
401
|
+
this.customerId,
|
|
402
|
+
this.items,
|
|
403
|
+
'CONFIRMED',
|
|
404
|
+
this.createdAt
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
valueObject:
|
|
410
|
+
description: "Immutable value object"
|
|
411
|
+
code: |
|
|
412
|
+
// domain/value-objects/money.ts
|
|
413
|
+
import { DomainError } from '../errors/domain-error.js';
|
|
414
|
+
|
|
415
|
+
export class Money {
|
|
416
|
+
private constructor(
|
|
417
|
+
public readonly amount: number,
|
|
418
|
+
public readonly currency: string
|
|
419
|
+
) {
|
|
420
|
+
if (amount < 0) {
|
|
421
|
+
throw new DomainError('Amount cannot be negative');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static of(amount: number, currency: string): Money {
|
|
426
|
+
return new Money(amount, currency);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
static zero(currency: string): Money {
|
|
430
|
+
return new Money(0, currency);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
add(other: Money): Money {
|
|
434
|
+
this.ensureSameCurrency(other);
|
|
435
|
+
return new Money(this.amount + other.amount, this.currency);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
multiply(factor: number): Money {
|
|
439
|
+
return new Money(this.amount * factor, this.currency);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
equals(other: Money): boolean {
|
|
443
|
+
return this.amount === other.amount && this.currency === other.currency;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private ensureSameCurrency(other: Money): void {
|
|
447
|
+
if (this.currency !== other.currency) {
|
|
448
|
+
throw new DomainError(`Currency mismatch: ${this.currency} vs ${other.currency}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
toString(): string {
|
|
453
|
+
return `${this.currency} ${this.amount.toFixed(2)}`;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
useCase:
|
|
458
|
+
description: "Application use case with dependency injection"
|
|
459
|
+
code: |
|
|
460
|
+
// application/use-cases/create-order.use-case.ts
|
|
461
|
+
import { Order } from '../../domain/entities/order.js';
|
|
462
|
+
import { OrderRepository } from '../ports/order-repository.port.js';
|
|
463
|
+
import { EventPublisher } from '../ports/event-publisher.port.js';
|
|
464
|
+
import { Logger } from '../ports/logger.port.js';
|
|
465
|
+
import { CreateOrderDto } from '../dtos/create-order.dto.js';
|
|
466
|
+
|
|
467
|
+
export class CreateOrderUseCase {
|
|
468
|
+
constructor(
|
|
469
|
+
private readonly orderRepository: OrderRepository,
|
|
470
|
+
private readonly eventPublisher: EventPublisher,
|
|
471
|
+
private readonly logger: Logger
|
|
472
|
+
) {}
|
|
473
|
+
|
|
474
|
+
async execute(dto: CreateOrderDto): Promise<Order> {
|
|
475
|
+
this.logger.info('Creating order', { customerId: dto.customerId });
|
|
476
|
+
|
|
477
|
+
const items = dto.items.map(item =>
|
|
478
|
+
OrderItem.create(item.productId, item.quantity, item.unitPrice)
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
const order = Order.create(dto.customerId, items);
|
|
482
|
+
|
|
483
|
+
await this.orderRepository.save(order);
|
|
484
|
+
|
|
485
|
+
await this.eventPublisher.publish({
|
|
486
|
+
type: 'OrderCreated',
|
|
487
|
+
payload: { orderId: order.id.value, customerId: order.customerId }
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
this.logger.info('Order created', { orderId: order.id.value });
|
|
491
|
+
|
|
492
|
+
return order;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
controller:
|
|
497
|
+
description: "HTTP controller with validation"
|
|
498
|
+
code: |
|
|
499
|
+
// infrastructure/http/controllers/order.controller.ts
|
|
500
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
501
|
+
import { z } from 'zod';
|
|
502
|
+
import { CreateOrderUseCase } from '../../../application/use-cases/create-order.use-case.js';
|
|
503
|
+
import { GetOrderUseCase } from '../../../application/use-cases/get-order.use-case.js';
|
|
504
|
+
import { validateBody } from '../middlewares/validation.middleware.js';
|
|
505
|
+
|
|
506
|
+
const createOrderSchema = z.object({
|
|
507
|
+
customerId: z.string().uuid(),
|
|
508
|
+
items: z.array(z.object({
|
|
509
|
+
productId: z.string().uuid(),
|
|
510
|
+
quantity: z.number().int().positive(),
|
|
511
|
+
unitPrice: z.number().positive()
|
|
512
|
+
})).min(1)
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
export class OrderController {
|
|
516
|
+
public readonly router = Router();
|
|
517
|
+
|
|
518
|
+
constructor(
|
|
519
|
+
private readonly createOrderUseCase: CreateOrderUseCase,
|
|
520
|
+
private readonly getOrderUseCase: GetOrderUseCase
|
|
521
|
+
) {
|
|
522
|
+
this.router.post('/', validateBody(createOrderSchema), this.create);
|
|
523
|
+
this.router.get('/:id', this.getById);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private create = async (req: Request, res: Response, next: NextFunction) => {
|
|
527
|
+
try {
|
|
528
|
+
const order = await this.createOrderUseCase.execute(req.body);
|
|
529
|
+
res.status(201).json({
|
|
530
|
+
id: order.id.value,
|
|
531
|
+
status: order.status,
|
|
532
|
+
total: order.total.toString()
|
|
533
|
+
});
|
|
534
|
+
} catch (error) {
|
|
535
|
+
next(error);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
private getById = async (req: Request, res: Response, next: NextFunction) => {
|
|
540
|
+
try {
|
|
541
|
+
const order = await this.getOrderUseCase.execute(req.params.id);
|
|
542
|
+
res.json(order);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
next(error);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
repository:
|
|
550
|
+
description: "Repository implementation with Prisma"
|
|
551
|
+
code: |
|
|
552
|
+
// infrastructure/database/repositories/prisma-order.repository.ts
|
|
553
|
+
import { PrismaClient } from '@prisma/client';
|
|
554
|
+
import { Order } from '../../../domain/entities/order.js';
|
|
555
|
+
import { OrderRepository } from '../../../application/ports/order-repository.port.js';
|
|
556
|
+
import { OrderMapper } from '../mappers/order.mapper.js';
|
|
557
|
+
import { EntityNotFoundError } from '../../../domain/errors/entity-not-found.error.js';
|
|
558
|
+
|
|
559
|
+
export class PrismaOrderRepository implements OrderRepository {
|
|
560
|
+
constructor(private readonly prisma: PrismaClient) {}
|
|
561
|
+
|
|
562
|
+
async save(order: Order): Promise<void> {
|
|
563
|
+
const data = OrderMapper.toPersistence(order);
|
|
564
|
+
|
|
565
|
+
await this.prisma.order.upsert({
|
|
566
|
+
where: { id: order.id.value },
|
|
567
|
+
create: data,
|
|
568
|
+
update: data
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async findById(id: string): Promise<Order | null> {
|
|
573
|
+
const record = await this.prisma.order.findUnique({
|
|
574
|
+
where: { id },
|
|
575
|
+
include: { items: true }
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return record ? OrderMapper.toDomain(record) : null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async findByIdOrFail(id: string): Promise<Order> {
|
|
582
|
+
const order = await this.findById(id);
|
|
583
|
+
if (!order) {
|
|
584
|
+
throw new EntityNotFoundError('Order', id);
|
|
585
|
+
}
|
|
586
|
+
return order;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async findByCustomerId(customerId: string): Promise<Order[]> {
|
|
590
|
+
const records = await this.prisma.order.findMany({
|
|
591
|
+
where: { customerId },
|
|
592
|
+
include: { items: true },
|
|
593
|
+
orderBy: { createdAt: 'desc' }
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
return records.map(OrderMapper.toDomain);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
test:
|
|
601
|
+
description: "Unit test with Vitest"
|
|
602
|
+
code: |
|
|
603
|
+
// domain/entities/order.test.ts
|
|
604
|
+
import { describe, it, expect } from 'vitest';
|
|
605
|
+
import { Order } from './order.js';
|
|
606
|
+
import { OrderItem } from './order-item.js';
|
|
607
|
+
import { Money } from '../value-objects/money.js';
|
|
608
|
+
import { DomainError } from '../errors/domain-error.js';
|
|
609
|
+
|
|
610
|
+
describe('Order', () => {
|
|
611
|
+
const createItem = (price: number, qty: number = 1) =>
|
|
612
|
+
OrderItem.create('product-id', qty, Money.of(price, 'USD'));
|
|
613
|
+
|
|
614
|
+
describe('create', () => {
|
|
615
|
+
it('should create order with items', () => {
|
|
616
|
+
const items = [createItem(100), createItem(50, 2)];
|
|
617
|
+
const order = Order.create('customer-id', items);
|
|
618
|
+
|
|
619
|
+
expect(order.customerId).toBe('customer-id');
|
|
620
|
+
expect(order.status).toBe('PENDING');
|
|
621
|
+
expect(order.total.amount).toBe(200);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('should throw when creating with no items', () => {
|
|
625
|
+
expect(() => Order.create('customer-id', [])).toThrow(DomainError);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
describe('confirm', () => {
|
|
630
|
+
it('should change status to CONFIRMED', () => {
|
|
631
|
+
const order = Order.create('customer-id', [createItem(100)]);
|
|
632
|
+
const confirmed = order.confirm();
|
|
633
|
+
|
|
634
|
+
expect(confirmed.status).toBe('CONFIRMED');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('should throw when confirming non-pending order', () => {
|
|
638
|
+
const order = Order.create('customer-id', [createItem(100)]).confirm();
|
|
639
|
+
expect(() => order.confirm()).toThrow(DomainError);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
describe('addItem', () => {
|
|
644
|
+
it('should add item and update total', () => {
|
|
645
|
+
const order = Order.create('customer-id', [createItem(100)]);
|
|
646
|
+
const updated = order.addItem(createItem(50));
|
|
647
|
+
|
|
648
|
+
expect(updated.total.amount).toBe(150);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
errorHandling:
|
|
654
|
+
description: "Custom error classes and middleware"
|
|
655
|
+
code: |
|
|
656
|
+
// domain/errors/domain-error.ts
|
|
657
|
+
export class DomainError extends Error {
|
|
658
|
+
constructor(message: string) {
|
|
659
|
+
super(message);
|
|
660
|
+
this.name = 'DomainError';
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// domain/errors/entity-not-found.error.ts
|
|
665
|
+
export class EntityNotFoundError extends DomainError {
|
|
666
|
+
constructor(entity: string, id: string) {
|
|
667
|
+
super(`${entity} with id ${id} not found`);
|
|
668
|
+
this.name = 'EntityNotFoundError';
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// infrastructure/http/middlewares/error.middleware.ts
|
|
673
|
+
import { Request, Response, NextFunction } from 'express';
|
|
674
|
+
import { DomainError } from '../../../domain/errors/domain-error.js';
|
|
675
|
+
import { EntityNotFoundError } from '../../../domain/errors/entity-not-found.error.js';
|
|
676
|
+
import { ZodError } from 'zod';
|
|
677
|
+
|
|
678
|
+
export function errorMiddleware(
|
|
679
|
+
error: Error,
|
|
680
|
+
req: Request,
|
|
681
|
+
res: Response,
|
|
682
|
+
_next: NextFunction
|
|
683
|
+
) {
|
|
684
|
+
if (error instanceof EntityNotFoundError) {
|
|
685
|
+
return res.status(404).json({
|
|
686
|
+
type: 'NotFound',
|
|
687
|
+
message: error.message
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (error instanceof DomainError) {
|
|
692
|
+
return res.status(400).json({
|
|
693
|
+
type: 'DomainError',
|
|
694
|
+
message: error.message
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (error instanceof ZodError) {
|
|
699
|
+
return res.status(400).json({
|
|
700
|
+
type: 'ValidationError',
|
|
701
|
+
errors: error.errors
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Log unexpected errors
|
|
706
|
+
console.error('Unexpected error:', error);
|
|
707
|
+
|
|
708
|
+
return res.status(500).json({
|
|
709
|
+
type: 'InternalError',
|
|
710
|
+
message: 'An unexpected error occurred'
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
# ----------------------------------------------------------------------------
|
|
715
|
+
# ANTI-PATTERNS
|
|
716
|
+
# ----------------------------------------------------------------------------
|
|
717
|
+
antiPatterns:
|
|
718
|
+
anyType:
|
|
719
|
+
name: "Using 'any' Type"
|
|
720
|
+
description: "Disabling TypeScript's type safety with 'any'"
|
|
721
|
+
bad: |
|
|
722
|
+
// ❌ any disables type checking
|
|
723
|
+
async function processData(data: any): Promise<any> {
|
|
724
|
+
const result = data.items.map((item: any) => item.value);
|
|
725
|
+
return { success: true, data: result };
|
|
726
|
+
}
|
|
727
|
+
good: |
|
|
728
|
+
// ✅ Proper typing
|
|
729
|
+
interface DataItem { value: number; }
|
|
730
|
+
interface ProcessInput { items: DataItem[]; }
|
|
731
|
+
interface ProcessResult { success: boolean; data: number[]; }
|
|
732
|
+
|
|
733
|
+
async function processData(data: ProcessInput): Promise<ProcessResult> {
|
|
734
|
+
const result = data.items.map(item => item.value);
|
|
735
|
+
return { success: true, data: result };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
callbackHell:
|
|
739
|
+
name: "Callback Hell"
|
|
740
|
+
description: "Nested callbacks instead of async/await"
|
|
741
|
+
bad: |
|
|
742
|
+
// ❌ Callback hell
|
|
743
|
+
function processOrder(orderId: string, callback: (err: Error | null, result?: any) => void) {
|
|
744
|
+
getOrder(orderId, (err, order) => {
|
|
745
|
+
if (err) return callback(err);
|
|
746
|
+
validateOrder(order, (err, valid) => {
|
|
747
|
+
if (err) return callback(err);
|
|
748
|
+
processPayment(order, (err, payment) => {
|
|
749
|
+
if (err) return callback(err);
|
|
750
|
+
callback(null, { order, payment });
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
good: |
|
|
756
|
+
// ✅ async/await
|
|
757
|
+
async function processOrder(orderId: string): Promise<ProcessedOrder> {
|
|
758
|
+
const order = await getOrder(orderId);
|
|
759
|
+
await validateOrder(order);
|
|
760
|
+
const payment = await processPayment(order);
|
|
761
|
+
return { order, payment };
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
swallowingErrors:
|
|
765
|
+
name: "Swallowing Errors"
|
|
766
|
+
description: "Catching errors without proper handling"
|
|
767
|
+
bad: |
|
|
768
|
+
// ❌ Silently ignoring errors
|
|
769
|
+
try {
|
|
770
|
+
await saveOrder(order);
|
|
771
|
+
} catch (error) {
|
|
772
|
+
// Do nothing - bad!
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// ❌ Just logging and continuing
|
|
776
|
+
try {
|
|
777
|
+
await saveOrder(order);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
console.log('Error:', error);
|
|
780
|
+
// Continues as if nothing happened
|
|
781
|
+
}
|
|
782
|
+
good: |
|
|
783
|
+
// ✅ Proper error handling
|
|
784
|
+
try {
|
|
785
|
+
await saveOrder(order);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
logger.error('Failed to save order', { orderId: order.id, error });
|
|
788
|
+
throw new ApplicationError('Order save failed', { cause: error });
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
syncFileOperations:
|
|
792
|
+
name: "Synchronous File Operations"
|
|
793
|
+
description: "Blocking the event loop with sync I/O"
|
|
794
|
+
bad: |
|
|
795
|
+
// ❌ Blocks event loop
|
|
796
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
797
|
+
|
|
798
|
+
function loadConfig(): Config {
|
|
799
|
+
const content = readFileSync('config.json', 'utf-8');
|
|
800
|
+
return JSON.parse(content);
|
|
801
|
+
}
|
|
802
|
+
good: |
|
|
803
|
+
// ✅ Async operations
|
|
804
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
805
|
+
|
|
806
|
+
async function loadConfig(): Promise<Config> {
|
|
807
|
+
const content = await readFile('config.json', 'utf-8');
|
|
808
|
+
return JSON.parse(content);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
businessLogicInController:
|
|
812
|
+
name: "Business Logic in Controllers"
|
|
813
|
+
description: "Putting domain logic in HTTP handlers"
|
|
814
|
+
bad: |
|
|
815
|
+
// ❌ Business logic in controller
|
|
816
|
+
app.post('/orders', async (req, res) => {
|
|
817
|
+
const { customerId, items } = req.body;
|
|
818
|
+
|
|
819
|
+
// Business rules in controller - bad!
|
|
820
|
+
if (items.length === 0) {
|
|
821
|
+
return res.status(400).json({ error: 'No items' });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
let total = 0;
|
|
825
|
+
for (const item of items) {
|
|
826
|
+
total += item.price * item.quantity;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (total > 10000) {
|
|
830
|
+
// Send email to manager...
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const order = await prisma.order.create({ data: { ... } });
|
|
834
|
+
res.json(order);
|
|
835
|
+
});
|
|
836
|
+
good: |
|
|
837
|
+
// ✅ Controller delegates to use case
|
|
838
|
+
app.post('/orders', async (req, res, next) => {
|
|
839
|
+
try {
|
|
840
|
+
const order = await createOrderUseCase.execute(req.body);
|
|
841
|
+
res.status(201).json(OrderMapper.toResponse(order));
|
|
842
|
+
} catch (error) {
|
|
843
|
+
next(error);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
requireInsteadOfImport:
|
|
848
|
+
name: "Using CommonJS require()"
|
|
849
|
+
description: "Using require() instead of ES modules"
|
|
850
|
+
bad: |
|
|
851
|
+
// ❌ CommonJS
|
|
852
|
+
const express = require('express');
|
|
853
|
+
const { readFile } = require('fs');
|
|
854
|
+
|
|
855
|
+
module.exports = { createServer };
|
|
856
|
+
good: |
|
|
857
|
+
// ✅ ES Modules
|
|
858
|
+
import express from 'express';
|
|
859
|
+
import { readFile } from 'fs/promises';
|
|
860
|
+
|
|
861
|
+
export { createServer };
|
|
862
|
+
|
|
863
|
+
hardcodedConfig:
|
|
864
|
+
name: "Hardcoded Configuration"
|
|
865
|
+
description: "Embedding configuration values in code"
|
|
866
|
+
bad: |
|
|
867
|
+
// ❌ Hardcoded values
|
|
868
|
+
const db = new PrismaClient({
|
|
869
|
+
datasources: {
|
|
870
|
+
db: { url: 'postgresql://user:pass@localhost:5432/mydb' }
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
const jwtSecret = 'my-super-secret-key';
|
|
875
|
+
good: |
|
|
876
|
+
// ✅ Environment variables with validation
|
|
877
|
+
import { z } from 'zod';
|
|
878
|
+
|
|
879
|
+
const envSchema = z.object({
|
|
880
|
+
DATABASE_URL: z.string().url(),
|
|
881
|
+
JWT_SECRET: z.string().min(32),
|
|
882
|
+
NODE_ENV: z.enum(['development', 'production', 'test'])
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
export const config = envSchema.parse(process.env);
|
|
886
|
+
|
|
887
|
+
nPlusOneQueries:
|
|
888
|
+
name: "N+1 Query Problem"
|
|
889
|
+
description: "Making separate database queries in a loop"
|
|
890
|
+
bad: |
|
|
891
|
+
// ❌ N+1 queries
|
|
892
|
+
const orders = await prisma.order.findMany();
|
|
893
|
+
for (const order of orders) {
|
|
894
|
+
order.customer = await prisma.customer.findUnique({
|
|
895
|
+
where: { id: order.customerId }
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
good: |
|
|
899
|
+
// ✅ Use include for eager loading
|
|
900
|
+
const orders = await prisma.order.findMany({
|
|
901
|
+
include: { customer: true }
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// ✅ Or batch with findMany
|
|
905
|
+
const orders = await prisma.order.findMany();
|
|
906
|
+
const customerIds = [...new Set(orders.map(o => o.customerId))];
|
|
907
|
+
const customers = await prisma.customer.findMany({
|
|
908
|
+
where: { id: { in: customerIds } }
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
mutatingParameters:
|
|
912
|
+
name: "Mutating Function Parameters"
|
|
913
|
+
description: "Modifying input objects instead of returning new ones"
|
|
914
|
+
bad: |
|
|
915
|
+
// ❌ Mutating input
|
|
916
|
+
function processOrder(order: Order): void {
|
|
917
|
+
order.status = 'PROCESSED';
|
|
918
|
+
order.processedAt = new Date();
|
|
919
|
+
order.items.forEach(item => {
|
|
920
|
+
item.processed = true;
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
good: |
|
|
924
|
+
// ✅ Return new object (immutability)
|
|
925
|
+
function processOrder(order: Order): Order {
|
|
926
|
+
return {
|
|
927
|
+
...order,
|
|
928
|
+
status: 'PROCESSED',
|
|
929
|
+
processedAt: new Date(),
|
|
930
|
+
items: order.items.map(item => ({ ...item, processed: true }))
|
|
931
|
+
};
|
|
932
|
+
}
|