@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.
Files changed (99) hide show
  1. package/README.md +233 -337
  2. package/dist/agent.d.ts +5 -6
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +95 -217
  5. package/dist/agent.js.map +1 -1
  6. package/dist/analysis/code-analyzer.d.ts +44 -0
  7. package/dist/analysis/code-analyzer.d.ts.map +1 -0
  8. package/dist/analysis/code-analyzer.js +528 -0
  9. package/dist/analysis/code-analyzer.js.map +1 -0
  10. package/dist/errors.d.ts +58 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +112 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/guardrails.d.ts +35 -0
  15. package/dist/guardrails.d.ts.map +1 -0
  16. package/dist/guardrails.js +303 -0
  17. package/dist/guardrails.js.map +1 -0
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/logger.d.ts +36 -0
  21. package/dist/logger.d.ts.map +1 -0
  22. package/dist/logger.js +63 -0
  23. package/dist/logger.js.map +1 -0
  24. package/dist/metrics.d.ts +40 -0
  25. package/dist/metrics.d.ts.map +1 -0
  26. package/dist/metrics.js +97 -0
  27. package/dist/metrics.js.map +1 -0
  28. package/dist/profiles.d.ts +1 -1
  29. package/dist/profiles.d.ts.map +1 -1
  30. package/dist/profiles.js +239 -108
  31. package/dist/profiles.js.map +1 -1
  32. package/dist/prompts.js +1 -1
  33. package/dist/prompts.js.map +1 -1
  34. package/dist/tools/definitions.d.ts +143 -0
  35. package/dist/tools/definitions.d.ts.map +1 -0
  36. package/dist/tools/definitions.js +229 -0
  37. package/dist/tools/definitions.js.map +1 -0
  38. package/dist/tools/handlers/get-context.d.ts +12 -0
  39. package/dist/tools/handlers/get-context.d.ts.map +1 -0
  40. package/dist/tools/handlers/get-context.js +233 -0
  41. package/dist/tools/handlers/get-context.js.map +1 -0
  42. package/dist/tools/handlers/health.d.ts +11 -0
  43. package/dist/tools/handlers/health.d.ts.map +1 -0
  44. package/dist/tools/handlers/health.js +57 -0
  45. package/dist/tools/handlers/health.js.map +1 -0
  46. package/dist/tools/handlers/index.d.ts +12 -0
  47. package/dist/tools/handlers/index.d.ts.map +1 -0
  48. package/dist/tools/handlers/index.js +12 -0
  49. package/dist/tools/handlers/index.js.map +1 -0
  50. package/dist/tools/handlers/init.d.ts +12 -0
  51. package/dist/tools/handlers/init.d.ts.map +1 -0
  52. package/dist/tools/handlers/init.js +102 -0
  53. package/dist/tools/handlers/init.js.map +1 -0
  54. package/dist/tools/handlers/profiles.d.ts +11 -0
  55. package/dist/tools/handlers/profiles.d.ts.map +1 -0
  56. package/dist/tools/handlers/profiles.js +25 -0
  57. package/dist/tools/handlers/profiles.js.map +1 -0
  58. package/dist/tools/handlers/search.d.ts +12 -0
  59. package/dist/tools/handlers/search.d.ts.map +1 -0
  60. package/dist/tools/handlers/search.js +58 -0
  61. package/dist/tools/handlers/search.js.map +1 -0
  62. package/dist/tools/handlers/validate.d.ts +15 -0
  63. package/dist/tools/handlers/validate.d.ts.map +1 -0
  64. package/dist/tools/handlers/validate.js +71 -0
  65. package/dist/tools/handlers/validate.js.map +1 -0
  66. package/dist/tools/handlers/verify.d.ts +38 -0
  67. package/dist/tools/handlers/verify.d.ts.map +1 -0
  68. package/dist/tools/handlers/verify.js +172 -0
  69. package/dist/tools/handlers/verify.js.map +1 -0
  70. package/dist/tools/index.d.ts +22 -0
  71. package/dist/tools/index.d.ts.map +1 -0
  72. package/dist/tools/index.js +75 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/tools/schemas.d.ts +29 -0
  75. package/dist/tools/schemas.d.ts.map +1 -0
  76. package/dist/tools/schemas.js +20 -0
  77. package/dist/tools/schemas.js.map +1 -0
  78. package/dist/tools.js +2 -2
  79. package/dist/tools.js.map +1 -1
  80. package/dist/types.d.ts +141 -71
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js +92 -40
  83. package/dist/types.js.map +1 -1
  84. package/package.json +2 -2
  85. package/profiles/examples/microservice-kafka.yaml +122 -0
  86. package/profiles/examples/startup-fast.yaml +67 -0
  87. package/profiles/examples/strict-enterprise.yaml +62 -0
  88. package/profiles/templates/angular.yaml +614 -0
  89. package/profiles/templates/csharp-dotnet.yaml +529 -0
  90. package/profiles/templates/flutter.yaml +547 -0
  91. package/profiles/templates/go.yaml +1276 -0
  92. package/profiles/templates/java-spring-backend.yaml +326 -0
  93. package/profiles/templates/kotlin-spring.yaml +417 -0
  94. package/profiles/templates/nextjs.yaml +536 -0
  95. package/profiles/templates/nodejs.yaml +594 -0
  96. package/profiles/templates/python.yaml +546 -0
  97. package/profiles/templates/react.yaml +456 -0
  98. package/profiles/templates/rust.yaml +508 -0
  99. package/profiles/templates/vue.yaml +483 -0
@@ -338,3 +338,549 @@ dependencies:
338
338
  # pyproject.toml
339
339
  # alembic.ini
340
340
  # Dockerfile
341
+
342
+ # ----------------------------------------------------------------------------
343
+ # CODE EXAMPLES
344
+ # ----------------------------------------------------------------------------
345
+ codeExamples:
346
+ entity:
347
+ description: "Domain entity with Pydantic"
348
+ code: |
349
+ # domain/entities/order.py
350
+ from datetime import datetime
351
+ from decimal import Decimal
352
+ from typing import List
353
+ from uuid import UUID, uuid4
354
+ from pydantic import BaseModel, Field
355
+
356
+ from domain.value_objects.money import Money
357
+ from domain.exceptions import DomainError
358
+
359
+
360
+ class OrderItem(BaseModel):
361
+ product_id: UUID
362
+ quantity: int = Field(gt=0)
363
+ unit_price: Money
364
+
365
+ @property
366
+ def subtotal(self) -> Money:
367
+ return self.unit_price * self.quantity
368
+
369
+
370
+ class Order(BaseModel):
371
+ id: UUID = Field(default_factory=uuid4)
372
+ customer_id: UUID
373
+ items: List[OrderItem] = Field(min_length=1)
374
+ status: str = "PENDING"
375
+ created_at: datetime = Field(default_factory=datetime.utcnow)
376
+
377
+ model_config = {"frozen": True} # Immutable
378
+
379
+ @property
380
+ def total(self) -> Money:
381
+ return sum(
382
+ (item.subtotal for item in self.items),
383
+ start=Money(amount=Decimal("0"), currency="USD")
384
+ )
385
+
386
+ def confirm(self) -> "Order":
387
+ if self.status != "PENDING":
388
+ raise DomainError("Order is not pending")
389
+ return self.model_copy(update={"status": "CONFIRMED"})
390
+
391
+ def add_item(self, item: OrderItem) -> "Order":
392
+ if self.status != "PENDING":
393
+ raise DomainError("Cannot add items to confirmed order")
394
+ return self.model_copy(update={"items": [*self.items, item]})
395
+
396
+ valueObject:
397
+ description: "Immutable value object"
398
+ code: |
399
+ # domain/value_objects/money.py
400
+ from decimal import Decimal
401
+ from pydantic import BaseModel, Field, model_validator
402
+
403
+ from domain.exceptions import DomainError
404
+
405
+
406
+ class Money(BaseModel):
407
+ amount: Decimal = Field(ge=0)
408
+ currency: str = Field(min_length=3, max_length=3)
409
+
410
+ model_config = {"frozen": True}
411
+
412
+ def __add__(self, other: "Money") -> "Money":
413
+ self._ensure_same_currency(other)
414
+ return Money(amount=self.amount + other.amount, currency=self.currency)
415
+
416
+ def __mul__(self, factor: int | Decimal) -> "Money":
417
+ return Money(amount=self.amount * Decimal(str(factor)), currency=self.currency)
418
+
419
+ def _ensure_same_currency(self, other: "Money") -> None:
420
+ if self.currency != other.currency:
421
+ raise DomainError(f"Currency mismatch: {self.currency} vs {other.currency}")
422
+
423
+ def __str__(self) -> str:
424
+ return f"{self.currency} {self.amount:.2f}"
425
+
426
+ useCase:
427
+ description: "Application use case with dependency injection"
428
+ code: |
429
+ # application/use_cases/create_order.py
430
+ from uuid import UUID
431
+ from typing import Protocol
432
+
433
+ from domain.entities.order import Order, OrderItem
434
+ from domain.value_objects.money import Money
435
+ from application.ports.order_repository import OrderRepository
436
+ from application.ports.event_publisher import EventPublisher
437
+
438
+
439
+ class CreateOrderCommand(BaseModel):
440
+ customer_id: UUID
441
+ items: list[dict]
442
+
443
+
444
+ class CreateOrderUseCase:
445
+ def __init__(
446
+ self,
447
+ order_repository: OrderRepository,
448
+ event_publisher: EventPublisher,
449
+ ) -> None:
450
+ self._order_repository = order_repository
451
+ self._event_publisher = event_publisher
452
+
453
+ async def execute(self, command: CreateOrderCommand) -> Order:
454
+ items = [
455
+ OrderItem(
456
+ product_id=item["product_id"],
457
+ quantity=item["quantity"],
458
+ unit_price=Money(
459
+ amount=item["unit_price"],
460
+ currency="USD"
461
+ )
462
+ )
463
+ for item in command.items
464
+ ]
465
+
466
+ order = Order(customer_id=command.customer_id, items=items)
467
+
468
+ await self._order_repository.save(order)
469
+
470
+ await self._event_publisher.publish(
471
+ "OrderCreated",
472
+ {"order_id": str(order.id), "customer_id": str(order.customer_id)}
473
+ )
474
+
475
+ return order
476
+
477
+ router:
478
+ description: "FastAPI router with validation"
479
+ code: |
480
+ # infrastructure/api/routes/orders.py
481
+ from uuid import UUID
482
+ from fastapi import APIRouter, Depends, HTTPException, status
483
+ from pydantic import BaseModel, Field
484
+
485
+ from application.use_cases.create_order import CreateOrderUseCase, CreateOrderCommand
486
+ from infrastructure.api.dependencies import get_create_order_use_case
487
+
488
+
489
+ router = APIRouter(prefix="/orders", tags=["orders"])
490
+
491
+
492
+ class CreateOrderRequest(BaseModel):
493
+ customer_id: UUID
494
+ items: list[dict] = Field(min_length=1)
495
+
496
+
497
+ class OrderResponse(BaseModel):
498
+ id: UUID
499
+ customer_id: UUID
500
+ status: str
501
+ total: str
502
+
503
+
504
+ @router.post("/", response_model=OrderResponse, status_code=status.HTTP_201_CREATED)
505
+ async def create_order(
506
+ request: CreateOrderRequest,
507
+ use_case: CreateOrderUseCase = Depends(get_create_order_use_case),
508
+ ) -> OrderResponse:
509
+ command = CreateOrderCommand(
510
+ customer_id=request.customer_id,
511
+ items=request.items
512
+ )
513
+ order = await use_case.execute(command)
514
+
515
+ return OrderResponse(
516
+ id=order.id,
517
+ customer_id=order.customer_id,
518
+ status=order.status,
519
+ total=str(order.total)
520
+ )
521
+
522
+
523
+ @router.get("/{order_id}", response_model=OrderResponse)
524
+ async def get_order(
525
+ order_id: UUID,
526
+ use_case: GetOrderUseCase = Depends(get_get_order_use_case),
527
+ ) -> OrderResponse:
528
+ order = await use_case.execute(order_id)
529
+ if not order:
530
+ raise HTTPException(
531
+ status_code=status.HTTP_404_NOT_FOUND,
532
+ detail=f"Order {order_id} not found"
533
+ )
534
+ return OrderResponse(...)
535
+
536
+ repository:
537
+ description: "Repository with SQLAlchemy 2.0"
538
+ code: |
539
+ # infrastructure/database/repositories/order_repository.py
540
+ from uuid import UUID
541
+ from typing import Optional
542
+
543
+ from sqlalchemy import select
544
+ from sqlalchemy.ext.asyncio import AsyncSession
545
+ from sqlalchemy.orm import selectinload
546
+
547
+ from domain.entities.order import Order
548
+ from application.ports.order_repository import OrderRepository
549
+ from infrastructure.database.models.order import OrderModel
550
+ from infrastructure.database.mappers.order_mapper import OrderMapper
551
+
552
+
553
+ class SQLAlchemyOrderRepository(OrderRepository):
554
+ def __init__(self, session: AsyncSession) -> None:
555
+ self._session = session
556
+
557
+ async def save(self, order: Order) -> None:
558
+ model = OrderMapper.to_model(order)
559
+ self._session.add(model)
560
+ await self._session.commit()
561
+
562
+ async def find_by_id(self, order_id: UUID) -> Optional[Order]:
563
+ query = (
564
+ select(OrderModel)
565
+ .options(selectinload(OrderModel.items))
566
+ .where(OrderModel.id == order_id)
567
+ )
568
+ result = await self._session.execute(query)
569
+ model = result.scalar_one_or_none()
570
+
571
+ return OrderMapper.to_entity(model) if model else None
572
+
573
+ async def find_by_customer_id(self, customer_id: UUID) -> list[Order]:
574
+ query = (
575
+ select(OrderModel)
576
+ .options(selectinload(OrderModel.items))
577
+ .where(OrderModel.customer_id == customer_id)
578
+ .order_by(OrderModel.created_at.desc())
579
+ )
580
+ result = await self._session.execute(query)
581
+ models = result.scalars().all()
582
+
583
+ return [OrderMapper.to_entity(m) for m in models]
584
+
585
+ test:
586
+ description: "Pytest test with fixtures"
587
+ code: |
588
+ # tests/unit/domain/test_order.py
589
+ from decimal import Decimal
590
+ from uuid import uuid4
591
+
592
+ import pytest
593
+
594
+ from domain.entities.order import Order, OrderItem
595
+ from domain.value_objects.money import Money
596
+ from domain.exceptions import DomainError
597
+
598
+
599
+ @pytest.fixture
600
+ def sample_item() -> OrderItem:
601
+ return OrderItem(
602
+ product_id=uuid4(),
603
+ quantity=2,
604
+ unit_price=Money(amount=Decimal("50.00"), currency="USD")
605
+ )
606
+
607
+
608
+ @pytest.fixture
609
+ def pending_order(sample_item: OrderItem) -> Order:
610
+ return Order(customer_id=uuid4(), items=[sample_item])
611
+
612
+
613
+ class TestOrder:
614
+ def test_should_create_order_with_items(self, sample_item: OrderItem) -> None:
615
+ order = Order(customer_id=uuid4(), items=[sample_item])
616
+
617
+ assert order.status == "PENDING"
618
+ assert order.total == Money(amount=Decimal("100.00"), currency="USD")
619
+
620
+ def test_should_raise_when_creating_without_items(self) -> None:
621
+ with pytest.raises(ValueError):
622
+ Order(customer_id=uuid4(), items=[])
623
+
624
+ def test_should_confirm_pending_order(self, pending_order: Order) -> None:
625
+ confirmed = pending_order.confirm()
626
+
627
+ assert confirmed.status == "CONFIRMED"
628
+ assert confirmed.id == pending_order.id
629
+
630
+ def test_should_raise_when_confirming_non_pending(
631
+ self, pending_order: Order
632
+ ) -> None:
633
+ confirmed = pending_order.confirm()
634
+
635
+ with pytest.raises(DomainError, match="not pending"):
636
+ confirmed.confirm()
637
+
638
+ def test_should_add_item_to_pending_order(
639
+ self, pending_order: Order, sample_item: OrderItem
640
+ ) -> None:
641
+ updated = pending_order.add_item(sample_item)
642
+
643
+ assert len(updated.items) == 2
644
+ assert updated.total.amount == Decimal("200.00")
645
+
646
+ dependencyInjection:
647
+ description: "FastAPI dependency injection"
648
+ code: |
649
+ # infrastructure/api/dependencies.py
650
+ from typing import AsyncGenerator
651
+ from functools import lru_cache
652
+
653
+ from fastapi import Depends
654
+ from sqlalchemy.ext.asyncio import AsyncSession
655
+
656
+ from infrastructure.database.session import get_session
657
+ from infrastructure.database.repositories.order_repository import SQLAlchemyOrderRepository
658
+ from application.use_cases.create_order import CreateOrderUseCase
659
+
660
+
661
+ async def get_order_repository(
662
+ session: AsyncSession = Depends(get_session),
663
+ ) -> SQLAlchemyOrderRepository:
664
+ return SQLAlchemyOrderRepository(session)
665
+
666
+
667
+ async def get_create_order_use_case(
668
+ order_repository: SQLAlchemyOrderRepository = Depends(get_order_repository),
669
+ event_publisher: EventPublisher = Depends(get_event_publisher),
670
+ ) -> CreateOrderUseCase:
671
+ return CreateOrderUseCase(
672
+ order_repository=order_repository,
673
+ event_publisher=event_publisher,
674
+ )
675
+
676
+ # ----------------------------------------------------------------------------
677
+ # ANTI-PATTERNS
678
+ # ----------------------------------------------------------------------------
679
+ antiPatterns:
680
+ noTypeHints:
681
+ name: "Missing Type Hints"
682
+ description: "Functions without type annotations"
683
+ bad: |
684
+ # ❌ No type hints
685
+ def calculate_total(items):
686
+ total = 0
687
+ for item in items:
688
+ total += item['price'] * item['quantity']
689
+ return total
690
+
691
+ def get_user(user_id):
692
+ return db.query(User).get(user_id)
693
+ good: |
694
+ # ✅ With type hints
695
+ from decimal import Decimal
696
+ from typing import Optional
697
+
698
+ def calculate_total(items: list[OrderItem]) -> Decimal:
699
+ return sum(item.subtotal for item in items)
700
+
701
+ def get_user(user_id: UUID) -> Optional[User]:
702
+ return db.query(User).get(user_id)
703
+
704
+ bareExcept:
705
+ name: "Bare Except Clauses"
706
+ description: "Catching all exceptions without specificity"
707
+ bad: |
708
+ # ❌ Bare except catches everything
709
+ try:
710
+ process_order(order)
711
+ except:
712
+ print("Something went wrong")
713
+
714
+ # ❌ Catching generic Exception
715
+ try:
716
+ process_order(order)
717
+ except Exception:
718
+ pass
719
+ good: |
720
+ # ✅ Specific exception handling
721
+ from domain.exceptions import DomainError, ValidationError
722
+
723
+ try:
724
+ process_order(order)
725
+ except ValidationError as e:
726
+ logger.warning("Validation failed", error=str(e))
727
+ raise HTTPException(status_code=400, detail=str(e))
728
+ except DomainError as e:
729
+ logger.error("Domain error", error=str(e))
730
+ raise HTTPException(status_code=422, detail=str(e))
731
+
732
+ printStatements:
733
+ name: "Using print() for Logging"
734
+ description: "Using print instead of structured logging"
735
+ bad: |
736
+ # ❌ print statements
737
+ print(f"Processing order {order_id}")
738
+ print(f"Error: {error}")
739
+ good: |
740
+ # ✅ Structured logging
741
+ import structlog
742
+
743
+ logger = structlog.get_logger()
744
+
745
+ logger.info("processing_order", order_id=str(order_id))
746
+ logger.error("order_failed", order_id=str(order_id), error=str(error))
747
+
748
+ syncInAsync:
749
+ name: "Synchronous Code in Async Functions"
750
+ description: "Blocking the event loop with sync operations"
751
+ bad: |
752
+ # ❌ Blocking async function
753
+ import requests
754
+
755
+ async def fetch_user_data(user_id: str) -> dict:
756
+ response = requests.get(f"/api/users/{user_id}") # Blocks!
757
+ return response.json()
758
+
759
+ async def read_config() -> dict:
760
+ with open("config.json") as f: # Blocks!
761
+ return json.load(f)
762
+ good: |
763
+ # ✅ Async operations
764
+ import httpx
765
+ import aiofiles
766
+
767
+ async def fetch_user_data(user_id: str) -> dict:
768
+ async with httpx.AsyncClient() as client:
769
+ response = await client.get(f"/api/users/{user_id}")
770
+ return response.json()
771
+
772
+ async def read_config() -> dict:
773
+ async with aiofiles.open("config.json") as f:
774
+ content = await f.read()
775
+ return json.loads(content)
776
+
777
+ mutableDefaults:
778
+ name: "Mutable Default Arguments"
779
+ description: "Using mutable objects as default parameters"
780
+ bad: |
781
+ # ❌ Mutable default - shared between calls!
782
+ def add_item(item: str, items: list = []) -> list:
783
+ items.append(item)
784
+ return items
785
+
786
+ def process_config(config: dict = {}) -> dict:
787
+ config["processed"] = True
788
+ return config
789
+ good: |
790
+ # ✅ Use None and create new objects
791
+ def add_item(item: str, items: list | None = None) -> list:
792
+ if items is None:
793
+ items = []
794
+ items.append(item)
795
+ return items
796
+
797
+ def process_config(config: dict | None = None) -> dict:
798
+ if config is None:
799
+ config = {}
800
+ return {**config, "processed": True}
801
+
802
+ businessLogicInRoutes:
803
+ name: "Business Logic in Routes"
804
+ description: "Putting domain logic in API endpoints"
805
+ bad: |
806
+ # ❌ Business logic in route
807
+ @router.post("/orders")
808
+ async def create_order(request: CreateOrderRequest, db: Session = Depends(get_db)):
809
+ # All logic in the route - bad!
810
+ if not request.items:
811
+ raise HTTPException(400, "No items")
812
+
813
+ total = sum(item.price * item.quantity for item in request.items)
814
+
815
+ if total > 10000:
816
+ await send_manager_notification(total)
817
+
818
+ order = OrderModel(customer_id=request.customer_id, total=total)
819
+ db.add(order)
820
+ db.commit()
821
+ return order
822
+ good: |
823
+ # ✅ Route delegates to use case
824
+ @router.post("/orders", response_model=OrderResponse)
825
+ async def create_order(
826
+ request: CreateOrderRequest,
827
+ use_case: CreateOrderUseCase = Depends(get_create_order_use_case),
828
+ ) -> OrderResponse:
829
+ command = CreateOrderCommand(**request.model_dump())
830
+ order = await use_case.execute(command)
831
+ return OrderResponse.from_entity(order)
832
+
833
+ globalState:
834
+ name: "Global Mutable State"
835
+ description: "Using global variables for state management"
836
+ bad: |
837
+ # ❌ Global mutable state
838
+ current_user = None
839
+ db_connection = None
840
+
841
+ def set_user(user):
842
+ global current_user
843
+ current_user = user
844
+
845
+ def get_user():
846
+ return current_user
847
+ good: |
848
+ # ✅ Dependency injection
849
+ from fastapi import Depends, Request
850
+ from typing import Annotated
851
+
852
+ async def get_current_user(request: Request) -> User:
853
+ token = request.headers.get("Authorization")
854
+ return await auth_service.validate_token(token)
855
+
856
+ @router.get("/profile")
857
+ async def get_profile(
858
+ current_user: Annotated[User, Depends(get_current_user)]
859
+ ) -> UserProfile:
860
+ return UserProfile.from_user(current_user)
861
+
862
+ stringConcatenation:
863
+ name: "String Concatenation for SQL"
864
+ description: "Building SQL queries with string concatenation"
865
+ bad: |
866
+ # ❌ SQL injection vulnerability!
867
+ def get_user_by_email(email: str):
868
+ query = f"SELECT * FROM users WHERE email = '{email}'"
869
+ return db.execute(query)
870
+
871
+ def search_products(term: str):
872
+ query = "SELECT * FROM products WHERE name LIKE '%" + term + "%'"
873
+ return db.execute(query)
874
+ good: |
875
+ # ✅ Parameterized queries with SQLAlchemy
876
+ from sqlalchemy import select
877
+
878
+ async def get_user_by_email(email: str) -> User | None:
879
+ query = select(User).where(User.email == email)
880
+ result = await session.execute(query)
881
+ return result.scalar_one_or_none()
882
+
883
+ async def search_products(term: str) -> list[Product]:
884
+ query = select(Product).where(Product.name.ilike(f"%{term}%"))
885
+ result = await session.execute(query)
886
+ return list(result.scalars().all())