mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
feat(guardian): add guardian package - code quality analyzer
Add @puaros/guardian package v0.1.0 - code quality guardian for vibe coders and enterprise teams. Features: - Hardcode detection (magic numbers, magic strings) - Circular dependency detection - Naming convention enforcement (Clean Architecture) - Architecture violation detection - CLI tool with comprehensive reporting - 159 tests with 80%+ coverage - Smart suggestions for fixes - Built for AI-assisted development Built with Clean Architecture and DDD principles. Works with Claude, GPT, Copilot, Cursor, and any AI coding assistant.
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
import { BaseEntity } from "../../../../src/domain/entities/BaseEntity"
|
||||
import { OrderId } from "../value-objects/OrderId"
|
||||
import { UserId } from "../value-objects/UserId"
|
||||
import { OrderStatus } from "../value-objects/OrderStatus"
|
||||
import { Money } from "../value-objects/Money"
|
||||
import { OrderItem } from "../entities/OrderItem"
|
||||
|
||||
/**
|
||||
* Order Aggregate Root
|
||||
*
|
||||
* DDD Patterns:
|
||||
* - Aggregate Root: controls access to OrderItems
|
||||
* - Consistency Boundary: all changes through Order
|
||||
* - Rich Domain Model: contains business logic
|
||||
*
|
||||
* SOLID Principles:
|
||||
* - SRP: manages order lifecycle
|
||||
* - OCP: extensible through status transitions
|
||||
* - ISP: focused interface for order operations
|
||||
*
|
||||
* Business Rules (Invariants):
|
||||
* - Order must have at least one item
|
||||
* - Cannot modify confirmed/paid/shipped orders
|
||||
* - Status transitions must be valid
|
||||
* - Total = sum of all items
|
||||
* - Cannot cancel delivered orders
|
||||
*
|
||||
* Clean Code:
|
||||
* - No magic numbers: MIN_ITEMS constant
|
||||
* - Meaningful names: addItem, removeItem, confirm
|
||||
* - Small methods: each does one thing
|
||||
* - No hardcoded strings: OrderStatus enum
|
||||
*/
|
||||
export class Order extends BaseEntity {
|
||||
private static readonly MIN_ITEMS = 1
|
||||
|
||||
private readonly _orderId: OrderId
|
||||
private readonly _userId: UserId
|
||||
private readonly _items: Map<string, OrderItem>
|
||||
private _status: OrderStatus
|
||||
private readonly _createdAt: Date
|
||||
private _confirmedAt?: Date
|
||||
private _deliveredAt?: Date
|
||||
|
||||
private constructor(
|
||||
orderId: OrderId,
|
||||
userId: UserId,
|
||||
items: OrderItem[],
|
||||
status: OrderStatus,
|
||||
createdAt: Date,
|
||||
confirmedAt?: Date,
|
||||
deliveredAt?: Date,
|
||||
) {
|
||||
super(orderId.value)
|
||||
this._orderId = orderId
|
||||
this._userId = userId
|
||||
this._items = new Map(items.map((item) => [item.id, item]))
|
||||
this._status = status
|
||||
this._createdAt = createdAt
|
||||
this._confirmedAt = confirmedAt
|
||||
this._deliveredAt = deliveredAt
|
||||
|
||||
this.validateInvariants()
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory: Create new order
|
||||
*/
|
||||
public static create(userId: UserId): Order {
|
||||
const orderId = OrderId.create()
|
||||
const now = new Date()
|
||||
|
||||
return new Order(orderId, userId, [], OrderStatus.PENDING, now)
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory: Reconstitute from persistence
|
||||
*/
|
||||
public static reconstitute(
|
||||
orderId: OrderId,
|
||||
userId: UserId,
|
||||
items: OrderItem[],
|
||||
status: OrderStatus,
|
||||
createdAt: Date,
|
||||
confirmedAt?: Date,
|
||||
deliveredAt?: Date,
|
||||
): Order {
|
||||
return new Order(orderId, userId, items, status, createdAt, confirmedAt, deliveredAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Add item to order
|
||||
*
|
||||
* DDD: Only Aggregate Root can modify its entities
|
||||
*/
|
||||
public addItem(productId: string, productName: string, price: Money, quantity: number): void {
|
||||
this.ensureCanModify()
|
||||
|
||||
const existingItem = Array.from(this._items.values()).find(
|
||||
(item) => item.productId === productId,
|
||||
)
|
||||
|
||||
if (existingItem) {
|
||||
existingItem.updateQuantity(existingItem.quantity + quantity)
|
||||
} else {
|
||||
const newItem = OrderItem.create(productId, productName, price, quantity)
|
||||
this._items.set(newItem.id, newItem)
|
||||
}
|
||||
|
||||
this.touch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Remove item from order
|
||||
*/
|
||||
public removeItem(itemId: string): void {
|
||||
this.ensureCanModify()
|
||||
|
||||
if (!this._items.has(itemId)) {
|
||||
throw new Error(`Item not found: ${itemId}`)
|
||||
}
|
||||
|
||||
this._items.delete(itemId)
|
||||
this.touch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Update item quantity
|
||||
*/
|
||||
public updateItemQuantity(itemId: string, newQuantity: number): void {
|
||||
this.ensureCanModify()
|
||||
|
||||
const item = this._items.get(itemId)
|
||||
|
||||
if (!item) {
|
||||
throw new Error(`Item not found: ${itemId}`)
|
||||
}
|
||||
|
||||
item.updateQuantity(newQuantity)
|
||||
this.touch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Confirm order
|
||||
*/
|
||||
public confirm(): void {
|
||||
this.transitionTo(OrderStatus.CONFIRMED)
|
||||
this._confirmedAt = new Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Mark as paid
|
||||
*/
|
||||
public markAsPaid(): void {
|
||||
this.transitionTo(OrderStatus.PAID)
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Ship order
|
||||
*/
|
||||
public ship(): void {
|
||||
this.transitionTo(OrderStatus.SHIPPED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Deliver order
|
||||
*/
|
||||
public deliver(): void {
|
||||
this.transitionTo(OrderStatus.DELIVERED)
|
||||
this._deliveredAt = new Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Operation: Cancel order
|
||||
*/
|
||||
public cancel(): void {
|
||||
if (this._status.isDelivered()) {
|
||||
throw new Error("Cannot cancel delivered order")
|
||||
}
|
||||
|
||||
this.transitionTo(OrderStatus.CANCELLED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Query: Calculate total
|
||||
*/
|
||||
public calculateTotal(): Money {
|
||||
const items = Array.from(this._items.values())
|
||||
|
||||
if (items.length === 0) {
|
||||
return Money.zero("USD")
|
||||
}
|
||||
|
||||
return items.reduce((total, item) => total.add(item.calculateTotal()), Money.zero("USD"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Business Query: Check if order can be modified
|
||||
*/
|
||||
public canModify(): boolean {
|
||||
return this._status.isPending()
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters
|
||||
*/
|
||||
public get orderId(): OrderId {
|
||||
return this._orderId
|
||||
}
|
||||
|
||||
public get userId(): UserId {
|
||||
return this._userId
|
||||
}
|
||||
|
||||
public get items(): readonly OrderItem[] {
|
||||
return Array.from(this._items.values())
|
||||
}
|
||||
|
||||
public get status(): OrderStatus {
|
||||
return this._status
|
||||
}
|
||||
|
||||
public get createdAt(): Date {
|
||||
return this._createdAt
|
||||
}
|
||||
|
||||
public get confirmedAt(): Date | undefined {
|
||||
return this._confirmedAt
|
||||
}
|
||||
|
||||
public get deliveredAt(): Date | undefined {
|
||||
return this._deliveredAt
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helpers
|
||||
*/
|
||||
private ensureCanModify(): void {
|
||||
if (!this.canModify()) {
|
||||
throw new Error(`Cannot modify order in ${this._status.value} status`)
|
||||
}
|
||||
}
|
||||
|
||||
private transitionTo(newStatus: OrderStatus): void {
|
||||
if (!this._status.canTransitionTo(newStatus)) {
|
||||
throw new Error(
|
||||
`Invalid status transition: ${this._status.value} -> ${newStatus.value}`,
|
||||
)
|
||||
}
|
||||
|
||||
this._status = newStatus
|
||||
this.touch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Invariant validation
|
||||
*/
|
||||
private validateInvariants(): void {
|
||||
if (!this._status.isPending() && this._items.size < Order.MIN_ITEMS) {
|
||||
throw new Error(`Order must have at least ${Order.MIN_ITEMS} item(s)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user