@leonardocrdso/modular-monolith-mcp 1.0.0 → 1.0.2
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/build/index.js +151 -150
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -19598,7 +19599,7 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
19598
19599
|
};
|
|
19599
19600
|
|
|
19600
19601
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
19601
|
-
import process from "
|
|
19602
|
+
import process from "process";
|
|
19602
19603
|
|
|
19603
19604
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
19604
19605
|
class ReadBuffer {
|
|
@@ -19741,7 +19742,7 @@ var RULES = [
|
|
|
19741
19742
|
note: "Business changes affect a single module instead of multiple layers"
|
|
19742
19743
|
}
|
|
19743
19744
|
],
|
|
19744
|
-
source: "Kamil Grzybek
|
|
19745
|
+
source: "Kamil Grzybek \u2014 Modular Monolith"
|
|
19745
19746
|
},
|
|
19746
19747
|
{
|
|
19747
19748
|
id: "vertical-slice-per-module",
|
|
@@ -19779,7 +19780,7 @@ models/orders.ts
|
|
|
19779
19780
|
note: "Each module has a single business responsibility with all needed layers"
|
|
19780
19781
|
}
|
|
19781
19782
|
],
|
|
19782
|
-
source: "Jimmy Bogard
|
|
19783
|
+
source: "Jimmy Bogard \u2014 Vertical Slice Architecture"
|
|
19783
19784
|
},
|
|
19784
19785
|
{
|
|
19785
19786
|
id: "standard-module-layout",
|
|
@@ -19793,12 +19794,12 @@ models/orders.ts
|
|
|
19793
19794
|
language: "typescript",
|
|
19794
19795
|
code: `// Standard module layout - every module follows this
|
|
19795
19796
|
// modules/{name}/
|
|
19796
|
-
// {name}.service.ts
|
|
19797
|
-
// {name}.repository.ts
|
|
19798
|
-
// {name}.types.ts
|
|
19799
|
-
// {name}.validation.ts
|
|
19800
|
-
// {name}.routes.ts
|
|
19801
|
-
// index.ts
|
|
19797
|
+
// {name}.service.ts \u2014 Business logic
|
|
19798
|
+
// {name}.repository.ts \u2014 Data access
|
|
19799
|
+
// {name}.types.ts \u2014 Domain types & interfaces
|
|
19800
|
+
// {name}.validation.ts \u2014 Zod schemas for input
|
|
19801
|
+
// {name}.routes.ts \u2014 API routes (if applicable)
|
|
19802
|
+
// index.ts \u2014 Public API barrel export`
|
|
19802
19803
|
},
|
|
19803
19804
|
{
|
|
19804
19805
|
label: "Bad",
|
|
@@ -19820,7 +19821,7 @@ modules/users/
|
|
|
19820
19821
|
{
|
|
19821
19822
|
principleId: "pick-one-word-per-concept",
|
|
19822
19823
|
relationship: "extends",
|
|
19823
|
-
note: "Consistent naming across modules
|
|
19824
|
+
note: "Consistent naming across modules \u2014 same file names, same patterns"
|
|
19824
19825
|
}
|
|
19825
19826
|
]
|
|
19826
19827
|
},
|
|
@@ -19848,7 +19849,7 @@ export type { Order, CreateOrderInput } from "./order.types.js";`
|
|
|
19848
19849
|
{
|
|
19849
19850
|
label: "Bad",
|
|
19850
19851
|
language: "typescript",
|
|
19851
|
-
code: `// app-bootstrap.ts
|
|
19852
|
+
code: `// app-bootstrap.ts \u2014 Centralizes ALL module wiring
|
|
19852
19853
|
import { OrderService } from "./modules/orders/order.service.js";
|
|
19853
19854
|
import { OrderRepository } from "./modules/orders/order.repository.js";
|
|
19854
19855
|
import { UserService } from "./modules/users/user.service.js";
|
|
@@ -19883,7 +19884,7 @@ const orderService = new OrderService(orderRepo);
|
|
|
19883
19884
|
{
|
|
19884
19885
|
label: "Good",
|
|
19885
19886
|
language: "typescript",
|
|
19886
|
-
code: `// modules/orders/index.ts
|
|
19887
|
+
code: `// modules/orders/index.ts \u2014 The module's public contract
|
|
19887
19888
|
export { orderService } from "./order.service.js";
|
|
19888
19889
|
export type { Order, CreateOrderInput } from "./order.types.js";
|
|
19889
19890
|
export type { OrderService } from "./order.service.js";
|
|
@@ -19893,7 +19894,7 @@ export type { OrderService } from "./order.service.js";
|
|
|
19893
19894
|
{
|
|
19894
19895
|
label: "Bad",
|
|
19895
19896
|
language: "typescript",
|
|
19896
|
-
code: `// No index.ts
|
|
19897
|
+
code: `// No index.ts \u2014 consumers import directly from internals
|
|
19897
19898
|
import { OrderRepository } from "../orders/order.repository.js";
|
|
19898
19899
|
import { mapOrderToDTO } from "../orders/internal/mappers.js";`
|
|
19899
19900
|
}
|
|
@@ -19903,7 +19904,7 @@ import { mapOrderToDTO } from "../orders/internal/mappers.js";`
|
|
|
19903
19904
|
{
|
|
19904
19905
|
principleId: "data-abstraction",
|
|
19905
19906
|
relationship: "reinforces",
|
|
19906
|
-
note: "Public API abstracts module internals
|
|
19907
|
+
note: "Public API abstracts module internals \u2014 consumers see the interface, not the implementation"
|
|
19907
19908
|
},
|
|
19908
19909
|
{
|
|
19909
19910
|
principleId: "interface-segregation-principle",
|
|
@@ -19911,7 +19912,7 @@ import { mapOrderToDTO } from "../orders/internal/mappers.js";`
|
|
|
19911
19912
|
note: "Module exposes only what consumers need, not everything it contains"
|
|
19912
19913
|
}
|
|
19913
19914
|
],
|
|
19914
|
-
source: "Kamil Grzybek
|
|
19915
|
+
source: "Kamil Grzybek \u2014 Modular Monolith"
|
|
19915
19916
|
},
|
|
19916
19917
|
{
|
|
19917
19918
|
id: "hide-implementation-details",
|
|
@@ -19940,7 +19941,7 @@ import type { Order } from "../orders/index.js";`
|
|
|
19940
19941
|
{
|
|
19941
19942
|
principleId: "law-of-demeter",
|
|
19942
19943
|
relationship: "extends",
|
|
19943
|
-
note: "Don't reach into another module's internals
|
|
19944
|
+
note: "Don't reach into another module's internals \u2014 talk to its public API"
|
|
19944
19945
|
},
|
|
19945
19946
|
{
|
|
19946
19947
|
principleId: "avoid-inappropriate-intimacy",
|
|
@@ -20000,7 +20001,7 @@ export async function getOrder(id: string): Promise<OrderDTO | null> {
|
|
|
20000
20001
|
{
|
|
20001
20002
|
label: "Good",
|
|
20002
20003
|
language: "typescript",
|
|
20003
|
-
code: `// Additive change
|
|
20004
|
+
code: `// Additive change \u2014 backwards compatible
|
|
20004
20005
|
export interface OrderDTO {
|
|
20005
20006
|
id: string;
|
|
20006
20007
|
status: string;
|
|
@@ -20012,11 +20013,11 @@ export interface OrderDTO {
|
|
|
20012
20013
|
{
|
|
20013
20014
|
label: "Bad",
|
|
20014
20015
|
language: "typescript",
|
|
20015
|
-
code: `// Breaking change
|
|
20016
|
+
code: `// Breaking change \u2014 renamed field without migration
|
|
20016
20017
|
export interface OrderDTO {
|
|
20017
20018
|
id: string;
|
|
20018
20019
|
status: string;
|
|
20019
|
-
// Was: totalCents, now: amountInCents
|
|
20020
|
+
// Was: totalCents, now: amountInCents \u2014 breaks all consumers
|
|
20020
20021
|
amountInCents: number;
|
|
20021
20022
|
}`
|
|
20022
20023
|
}
|
|
@@ -20183,7 +20184,7 @@ export class OrderService {
|
|
|
20183
20184
|
name: "Anti-Corruption Layer",
|
|
20184
20185
|
category: "module-communication",
|
|
20185
20186
|
description: "When integrating with another module or external system whose model differs from yours, introduce an Anti-Corruption Layer (ACL) that translates between the foreign model and your internal model.",
|
|
20186
|
-
rationale: "An ACL prevents foreign concepts from leaking into your module. If the external model changes, only the ACL needs updating
|
|
20187
|
+
rationale: "An ACL prevents foreign concepts from leaking into your module. If the external model changes, only the ACL needs updating \u2014 your business logic stays clean.",
|
|
20187
20188
|
examples: [
|
|
20188
20189
|
{
|
|
20189
20190
|
label: "Good",
|
|
@@ -20204,10 +20205,10 @@ export function toShippableItem(order: Order): ShippableItem {
|
|
|
20204
20205
|
{
|
|
20205
20206
|
label: "Bad",
|
|
20206
20207
|
language: "typescript",
|
|
20207
|
-
code: `// Shipping logic uses Order types directly
|
|
20208
|
+
code: `// Shipping logic uses Order types directly \u2014 foreign model leaks in
|
|
20208
20209
|
export class ShippingService {
|
|
20209
20210
|
async ship(order: Order) {
|
|
20210
|
-
// Using order.items[0].product.category
|
|
20211
|
+
// Using order.items[0].product.category \u2014 deep coupling
|
|
20211
20212
|
if (order.items[0].product.category === "fragile") {
|
|
20212
20213
|
// Shipping knows about product categories
|
|
20213
20214
|
}
|
|
@@ -20223,7 +20224,7 @@ export class ShippingService {
|
|
|
20223
20224
|
note: "ACL abstracts the foreign model, exposing only what the module needs"
|
|
20224
20225
|
}
|
|
20225
20226
|
],
|
|
20226
|
-
source: "Eric Evans
|
|
20227
|
+
source: "Eric Evans \u2014 Domain-Driven Design"
|
|
20227
20228
|
},
|
|
20228
20229
|
{
|
|
20229
20230
|
id: "separate-module-data",
|
|
@@ -20251,10 +20252,10 @@ const order = await orderService.getById(orderId);`
|
|
|
20251
20252
|
{
|
|
20252
20253
|
principleId: "avoid-inappropriate-intimacy",
|
|
20253
20254
|
relationship: "extends",
|
|
20254
|
-
note: "Data-level intimacy is the deepest form
|
|
20255
|
+
note: "Data-level intimacy is the deepest form \u2014 modules must not access each other's data stores"
|
|
20255
20256
|
}
|
|
20256
20257
|
],
|
|
20257
|
-
source: "Sam Newman
|
|
20258
|
+
source: "Sam Newman \u2014 Building Microservices"
|
|
20258
20259
|
},
|
|
20259
20260
|
{
|
|
20260
20261
|
id: "no-cross-module-joins",
|
|
@@ -20293,7 +20294,7 @@ const enriched = pendingOrders.map(order => ({
|
|
|
20293
20294
|
{
|
|
20294
20295
|
principleId: "single-responsibility-principle",
|
|
20295
20296
|
relationship: "reinforces",
|
|
20296
|
-
note: "Each module manages its own data access
|
|
20297
|
+
note: "Each module manages its own data access \u2014 no shared queries"
|
|
20297
20298
|
}
|
|
20298
20299
|
]
|
|
20299
20300
|
},
|
|
@@ -20307,7 +20308,7 @@ const enriched = pendingOrders.map(order => ({
|
|
|
20307
20308
|
{
|
|
20308
20309
|
label: "Good",
|
|
20309
20310
|
language: "prisma",
|
|
20310
|
-
code: `// schema.prisma
|
|
20311
|
+
code: `// schema.prisma \u2014 Logical separation with comments
|
|
20311
20312
|
// === MODULE: orders ===
|
|
20312
20313
|
model Order {
|
|
20313
20314
|
id String @id @default(uuid())
|
|
@@ -20327,7 +20328,7 @@ model User {
|
|
|
20327
20328
|
{
|
|
20328
20329
|
label: "Bad",
|
|
20329
20330
|
language: "prisma",
|
|
20330
|
-
code: `// No ownership indication
|
|
20331
|
+
code: `// No ownership indication \u2014 who owns what?
|
|
20331
20332
|
model Order {
|
|
20332
20333
|
id String @id
|
|
20333
20334
|
user User @relation(fields: [userId], references: [id])
|
|
@@ -20366,7 +20367,7 @@ export class UserService {
|
|
|
20366
20367
|
async deactivate(id: string): Promise<void> { /* ... */ }
|
|
20367
20368
|
}
|
|
20368
20369
|
|
|
20369
|
-
// Other modules call the service
|
|
20370
|
+
// Other modules call the service \u2014 never write to user tables directly`
|
|
20370
20371
|
},
|
|
20371
20372
|
{
|
|
20372
20373
|
label: "Bad",
|
|
@@ -20400,7 +20401,7 @@ async deactivateUser(userId: string) {
|
|
|
20400
20401
|
{
|
|
20401
20402
|
label: "Bad",
|
|
20402
20403
|
language: "typescript",
|
|
20403
|
-
code: `// Circular: orders
|
|
20404
|
+
code: `// Circular: orders \u2192 users \u2192 orders
|
|
20404
20405
|
// modules/orders/order.service.ts
|
|
20405
20406
|
import { userService } from "../users/index.js";
|
|
20406
20407
|
|
|
@@ -20429,7 +20430,7 @@ eventBus.on("order.created", async ({ userId }) => {
|
|
|
20429
20430
|
note: "Break cycles by depending on abstractions (interfaces/events) instead of concrete modules"
|
|
20430
20431
|
}
|
|
20431
20432
|
],
|
|
20432
|
-
source: "Robert C. Martin
|
|
20433
|
+
source: "Robert C. Martin \u2014 Clean Architecture"
|
|
20433
20434
|
},
|
|
20434
20435
|
{
|
|
20435
20436
|
id: "depend-on-abstractions",
|
|
@@ -20470,7 +20471,7 @@ export class OrderService {
|
|
|
20470
20471
|
{
|
|
20471
20472
|
principleId: "dependency-inversion-principle",
|
|
20472
20473
|
relationship: "reinforces",
|
|
20473
|
-
note: "High-level modules should not depend on low-level modules
|
|
20474
|
+
note: "High-level modules should not depend on low-level modules \u2014 both depend on abstractions"
|
|
20474
20475
|
},
|
|
20475
20476
|
{
|
|
20476
20477
|
principleId: "open-closed-principle",
|
|
@@ -20483,15 +20484,15 @@ export class OrderService {
|
|
|
20483
20484
|
id: "dependency-direction",
|
|
20484
20485
|
name: "Dependency Direction",
|
|
20485
20486
|
category: "dependency-management",
|
|
20486
|
-
description: "Dependencies should flow in one direction: infrastructure
|
|
20487
|
+
description: "Dependencies should flow in one direction: infrastructure \u2192 application \u2192 domain. Higher-level modules (domain) should never depend on lower-level modules (infrastructure).",
|
|
20487
20488
|
rationale: "Unidirectional dependency flow makes the system predictable. Domain logic stays pure and testable, free from infrastructure concerns.",
|
|
20488
20489
|
examples: [
|
|
20489
20490
|
{
|
|
20490
20491
|
label: "Good",
|
|
20491
20492
|
language: "text",
|
|
20492
20493
|
code: `Dependency flow:
|
|
20493
|
-
Routes (infra)
|
|
20494
|
-
Repository (infra)
|
|
20494
|
+
Routes (infra) \u2192 Service (app) \u2192 Types/Interfaces (domain)
|
|
20495
|
+
Repository (infra) \u2192 implements \u2192 RepositoryInterface (domain)
|
|
20495
20496
|
|
|
20496
20497
|
Domain layer has ZERO external dependencies.`
|
|
20497
20498
|
},
|
|
@@ -20511,10 +20512,10 @@ export interface Order {
|
|
|
20511
20512
|
{
|
|
20512
20513
|
principleId: "separate-construction-from-use",
|
|
20513
20514
|
relationship: "complements",
|
|
20514
|
-
note: "Infrastructure is constructed and injected
|
|
20515
|
+
note: "Infrastructure is constructed and injected \u2014 domain uses abstractions"
|
|
20515
20516
|
}
|
|
20516
20517
|
],
|
|
20517
|
-
source: "Robert C. Martin
|
|
20518
|
+
source: "Robert C. Martin \u2014 Clean Architecture"
|
|
20518
20519
|
},
|
|
20519
20520
|
{
|
|
20520
20521
|
id: "thin-routes",
|
|
@@ -20526,7 +20527,7 @@ export interface Order {
|
|
|
20526
20527
|
{
|
|
20527
20528
|
label: "Good",
|
|
20528
20529
|
language: "typescript",
|
|
20529
|
-
code: `// Thin route: validate
|
|
20530
|
+
code: `// Thin route: validate \u2192 call service \u2192 respond
|
|
20530
20531
|
app.post("/orders", async (req, res) => {
|
|
20531
20532
|
const input = createOrderSchema.parse(req.body);
|
|
20532
20533
|
const order = await orderService.create(input);
|
|
@@ -20580,7 +20581,7 @@ app.post("/orders", async (req, res) => {
|
|
|
20580
20581
|
label: "Good",
|
|
20581
20582
|
language: "typescript",
|
|
20582
20583
|
code: `// modules/orders/order.routes.ts
|
|
20583
|
-
// Only calls orderService
|
|
20584
|
+
// Only calls orderService \u2014 belongs to orders module
|
|
20584
20585
|
app.get("/orders/:id", async (req, res) => {
|
|
20585
20586
|
const order = await orderService.getById(req.params.id);
|
|
20586
20587
|
res.json(order);
|
|
@@ -20608,7 +20609,7 @@ app.post("/checkout", async (req, res) => {
|
|
|
20608
20609
|
{
|
|
20609
20610
|
principleId: "single-responsibility-principle",
|
|
20610
20611
|
relationship: "implements",
|
|
20611
|
-
note: "Each route file has one reason to change
|
|
20612
|
+
note: "Each route file has one reason to change \u2014 its owning module's requirements"
|
|
20612
20613
|
}
|
|
20613
20614
|
]
|
|
20614
20615
|
},
|
|
@@ -20655,7 +20656,7 @@ export class OrderService {
|
|
|
20655
20656
|
{
|
|
20656
20657
|
principleId: "prefer-exceptions-to-error-codes",
|
|
20657
20658
|
relationship: "complements",
|
|
20658
|
-
note: "Zod throws on invalid input
|
|
20659
|
+
note: "Zod throws on invalid input \u2014 clean exception-based validation"
|
|
20659
20660
|
}
|
|
20660
20661
|
]
|
|
20661
20662
|
},
|
|
@@ -20694,10 +20695,10 @@ export class OrderService {
|
|
|
20694
20695
|
{
|
|
20695
20696
|
principleId: "classes-should-be-small",
|
|
20696
20697
|
relationship: "extends",
|
|
20697
|
-
note: "Like classes, the shared kernel should be small
|
|
20698
|
+
note: "Like classes, the shared kernel should be small \u2014 only the essentials"
|
|
20698
20699
|
}
|
|
20699
20700
|
],
|
|
20700
|
-
source: "Eric Evans
|
|
20701
|
+
source: "Eric Evans \u2014 Domain-Driven Design"
|
|
20701
20702
|
},
|
|
20702
20703
|
{
|
|
20703
20704
|
id: "prefer-duplication-over-coupling",
|
|
@@ -20719,7 +20720,7 @@ const emailSchema = z.string().email();
|
|
|
20719
20720
|
{
|
|
20720
20721
|
label: "Bad",
|
|
20721
20722
|
language: "typescript",
|
|
20722
|
-
code: `// shared/validators.ts
|
|
20723
|
+
code: `// shared/validators.ts \u2014 Forces coupling
|
|
20723
20724
|
export const emailSchema = z.string().email().min(5);
|
|
20724
20725
|
// Change here breaks BOTH orders and users
|
|
20725
20726
|
// What if orders needs .min(3) but users needs .min(5)?`
|
|
@@ -20733,19 +20734,19 @@ export const emailSchema = z.string().email().min(5);
|
|
|
20733
20734
|
note: "DRY applies within a module. Across modules, controlled duplication is preferred over coupling"
|
|
20734
20735
|
}
|
|
20735
20736
|
],
|
|
20736
|
-
source: "Sandi Metz
|
|
20737
|
+
source: "Sandi Metz \u2014 The Wrong Abstraction"
|
|
20737
20738
|
},
|
|
20738
20739
|
{
|
|
20739
20740
|
id: "shared-types-only",
|
|
20740
20741
|
name: "Shared Types Only",
|
|
20741
20742
|
category: "shared-kernel",
|
|
20742
|
-
description: "The shared kernel should contain only types, interfaces, and constants
|
|
20743
|
+
description: "The shared kernel should contain only types, interfaces, and constants \u2014 never implementations. If you need shared behavior, use dependency injection or events instead of shared code.",
|
|
20743
20744
|
rationale: "Types are the safest thing to share because they have no behavior. Shared implementations create coupling and make it hard to reason about which module controls which behavior.",
|
|
20744
20745
|
examples: [
|
|
20745
20746
|
{
|
|
20746
20747
|
label: "Good",
|
|
20747
20748
|
language: "typescript",
|
|
20748
|
-
code: `// shared/types.ts
|
|
20749
|
+
code: `// shared/types.ts \u2014 Types only, no implementation
|
|
20749
20750
|
export interface Paginated<T> {
|
|
20750
20751
|
items: T[];
|
|
20751
20752
|
total: number;
|
|
@@ -20762,7 +20763,7 @@ export interface Result<T, E = Error> {
|
|
|
20762
20763
|
{
|
|
20763
20764
|
label: "Bad",
|
|
20764
20765
|
language: "typescript",
|
|
20765
|
-
code: `// shared/pagination.ts
|
|
20766
|
+
code: `// shared/pagination.ts \u2014 Implementation in shared kernel
|
|
20766
20767
|
export function paginate<T>(items: T[], page: number, size: number): Paginated<T> {
|
|
20767
20768
|
const start = (page - 1) * size;
|
|
20768
20769
|
return {
|
|
@@ -20779,7 +20780,7 @@ export function paginate<T>(items: T[], page: number, size: number): Paginated<T
|
|
|
20779
20780
|
{
|
|
20780
20781
|
principleId: "interface-segregation-principle",
|
|
20781
20782
|
relationship: "reinforces",
|
|
20782
|
-
note: "Share only the minimal interface needed
|
|
20783
|
+
note: "Share only the minimal interface needed \u2014 types are inherently segregated"
|
|
20783
20784
|
}
|
|
20784
20785
|
]
|
|
20785
20786
|
},
|
|
@@ -21038,7 +21039,7 @@ export class OrderService {
|
|
|
21038
21039
|
{
|
|
21039
21040
|
label: "Good",
|
|
21040
21041
|
language: "typescript",
|
|
21041
|
-
code: `// Switching from Stripe to PayPal
|
|
21042
|
+
code: `// Switching from Stripe to PayPal \u2014 only payment module changes
|
|
21042
21043
|
// modules/payments/paypal-client.ts (new)
|
|
21043
21044
|
export class PayPalClient implements PaymentGateway {
|
|
21044
21045
|
async charge(amount: number, currency: string): Promise<PaymentResult> {
|
|
@@ -21046,9 +21047,9 @@ export class PayPalClient implements PaymentGateway {
|
|
|
21046
21047
|
}
|
|
21047
21048
|
}
|
|
21048
21049
|
|
|
21049
|
-
// modules/payments/index.ts
|
|
21050
|
+
// modules/payments/index.ts \u2014 swap the implementation
|
|
21050
21051
|
export const paymentGateway: PaymentGateway = new PayPalClient();
|
|
21051
|
-
// All consumers still use PaymentGateway interface
|
|
21052
|
+
// All consumers still use PaymentGateway interface \u2014 zero changes needed`
|
|
21052
21053
|
},
|
|
21053
21054
|
{
|
|
21054
21055
|
label: "Bad",
|
|
@@ -21077,7 +21078,7 @@ export const paymentGateway: PaymentGateway = new PayPalClient();
|
|
|
21077
21078
|
{
|
|
21078
21079
|
label: "Good",
|
|
21079
21080
|
language: "text",
|
|
21080
|
-
code: `Phase 1: New features
|
|
21081
|
+
code: `Phase 1: New features \u2192 proper modules
|
|
21081
21082
|
Phase 2: Extract highest-value legacy area into module
|
|
21082
21083
|
Phase 3: Route traffic to new module, keep legacy as fallback
|
|
21083
21084
|
Phase 4: Remove legacy code when new module is proven
|
|
@@ -21097,10 +21098,10 @@ Phase 5: Repeat for next area`
|
|
|
21097
21098
|
{
|
|
21098
21099
|
principleId: "simple-design-runs-all-tests",
|
|
21099
21100
|
relationship: "complements",
|
|
21100
|
-
note: "Each migration step must pass all tests
|
|
21101
|
+
note: "Each migration step must pass all tests \u2014 incremental confidence"
|
|
21101
21102
|
}
|
|
21102
21103
|
],
|
|
21103
|
-
source: "Martin Fowler
|
|
21104
|
+
source: "Martin Fowler \u2014 Strangler Fig Application"
|
|
21104
21105
|
},
|
|
21105
21106
|
{
|
|
21106
21107
|
id: "extract-module-gradually",
|
|
@@ -21124,7 +21125,7 @@ Step 5: Now refactor internals safely`
|
|
|
21124
21125
|
code: `Step 1: Create modules/orders/
|
|
21125
21126
|
Step 2: Move files AND refactor AND rename AND add tests
|
|
21126
21127
|
AND change the database schema AND...
|
|
21127
|
-
|
|
21128
|
+
\u2192 Massive PR, impossible to review, high risk of bugs`
|
|
21128
21129
|
}
|
|
21129
21130
|
],
|
|
21130
21131
|
tags: ["migration", "extraction", "gradual", "refactoring", "boundary"],
|
|
@@ -21158,7 +21159,7 @@ Month 7+: Extract high-complexity areas into modules
|
|
|
21158
21159
|
Day 2: Realize "orders" and "cart" should be one module
|
|
21159
21160
|
Day 3: Realize "notifications" needs to be split
|
|
21160
21161
|
Day 4: Rewrite module boundaries AGAIN
|
|
21161
|
-
|
|
21162
|
+
\u2192 More time on architecture than features`
|
|
21162
21163
|
}
|
|
21163
21164
|
],
|
|
21164
21165
|
tags: ["migration", "monolith", "start", "premature", "boundaries"],
|
|
@@ -21169,7 +21170,7 @@ Day 4: Rewrite module boundaries AGAIN
|
|
|
21169
21170
|
note: "Minimal design: don't add module boundaries until they're needed"
|
|
21170
21171
|
}
|
|
21171
21172
|
],
|
|
21172
|
-
source: "Martin Fowler
|
|
21173
|
+
source: "Martin Fowler \u2014 MonolithFirst"
|
|
21173
21174
|
},
|
|
21174
21175
|
{
|
|
21175
21176
|
id: "measure-before-splitting",
|
|
@@ -21182,20 +21183,20 @@ Day 4: Rewrite module boundaries AGAIN
|
|
|
21182
21183
|
label: "Good",
|
|
21183
21184
|
language: "text",
|
|
21184
21185
|
code: `Before splitting, analyze:
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
|
|
21186
|
+
\u2713 Import graph: which files depend on which?
|
|
21187
|
+
\u2713 Change coupling: which files change together?
|
|
21188
|
+
\u2713 Team ownership: does one team own this area?
|
|
21189
|
+
\u2713 Deployment: does this area need independent deployment?
|
|
21190
|
+
\u2713 Scale: does this area need independent scaling?`
|
|
21190
21191
|
},
|
|
21191
21192
|
{
|
|
21192
21193
|
label: "Bad",
|
|
21193
21194
|
language: "text",
|
|
21194
21195
|
code: `"Orders feels too big, let's split it into
|
|
21195
21196
|
order-creation, order-fulfillment, and order-history"
|
|
21196
|
-
|
|
21197
|
-
|
|
21198
|
-
|
|
21197
|
+
\u2192 No data to support this split
|
|
21198
|
+
\u2192 High communication overhead between new modules
|
|
21199
|
+
\u2192 Essentially a distributed monolith`
|
|
21199
21200
|
}
|
|
21200
21201
|
],
|
|
21201
21202
|
tags: ["migration", "measure", "coupling", "cohesion", "split", "metrics"],
|
|
@@ -21203,7 +21204,7 @@ Day 4: Rewrite module boundaries AGAIN
|
|
|
21203
21204
|
{
|
|
21204
21205
|
principleId: "high-cohesion",
|
|
21205
21206
|
relationship: "reinforces",
|
|
21206
|
-
note: "Measure cohesion
|
|
21207
|
+
note: "Measure cohesion \u2014 if a module's parts don't belong together, split is warranted"
|
|
21207
21208
|
}
|
|
21208
21209
|
]
|
|
21209
21210
|
}
|
|
@@ -21263,7 +21264,7 @@ await userService.recordLastOrder(userId);`
|
|
|
21263
21264
|
payment.service.ts
|
|
21264
21265
|
notification.service.ts
|
|
21265
21266
|
analytics.service.ts
|
|
21266
|
-
# Everything is in "core"
|
|
21267
|
+
# Everything is in "core" \u2014 it's a monolith inside a monolith`
|
|
21267
21268
|
},
|
|
21268
21269
|
{
|
|
21269
21270
|
label: "Good",
|
|
@@ -21280,7 +21281,7 @@ await userService.recordLastOrder(userId);`
|
|
|
21280
21281
|
{
|
|
21281
21282
|
principleId: "single-responsibility-principle",
|
|
21282
21283
|
relationship: "reinforces",
|
|
21283
|
-
note: "A god module violates SRP at the module level
|
|
21284
|
+
note: "A god module violates SRP at the module level \u2014 multiple reasons to change"
|
|
21284
21285
|
},
|
|
21285
21286
|
{
|
|
21286
21287
|
principleId: "classes-should-be-small",
|
|
@@ -21303,7 +21304,7 @@ import { userService } from "../users/index.js";
|
|
|
21303
21304
|
|
|
21304
21305
|
// modules/users/user.service.ts
|
|
21305
21306
|
import { orderService } from "../orders/index.js";
|
|
21306
|
-
// CIRCULAR: orders
|
|
21307
|
+
// CIRCULAR: orders \u2192 users \u2192 orders`
|
|
21307
21308
|
},
|
|
21308
21309
|
{
|
|
21309
21310
|
label: "Good",
|
|
@@ -21312,7 +21313,7 @@ import { orderService } from "../orders/index.js";
|
|
|
21312
21313
|
// modules/orders/order.service.ts
|
|
21313
21314
|
import { userService } from "../users/index.js"; // OK
|
|
21314
21315
|
|
|
21315
|
-
// modules/users/user.service.ts
|
|
21316
|
+
// modules/users/user.service.ts \u2014 no import from orders
|
|
21316
21317
|
eventBus.on("order.created", async ({ userId }) => {
|
|
21317
21318
|
await userService.incrementOrderCount(userId);
|
|
21318
21319
|
});`
|
|
@@ -21371,7 +21372,7 @@ eventBus.on("order.created", async ({ userId }) => {
|
|
|
21371
21372
|
{
|
|
21372
21373
|
principleId: "keep-functions-small",
|
|
21373
21374
|
relationship: "reinforces",
|
|
21374
|
-
note: "Fat routes are long functions
|
|
21375
|
+
note: "Fat routes are long functions \u2014 extract to service"
|
|
21375
21376
|
}
|
|
21376
21377
|
]
|
|
21377
21378
|
},
|
|
@@ -21388,7 +21389,7 @@ eventBus.on("order.created", async ({ userId }) => {
|
|
|
21388
21389
|
export async function getUser(id: string): Promise<PrismaUser> {
|
|
21389
21390
|
return prisma.user.findUniqueOrThrow({ where: { id } });
|
|
21390
21391
|
}
|
|
21391
|
-
// Consumers now depend on Prisma
|
|
21392
|
+
// Consumers now depend on Prisma \u2014 internal detail`
|
|
21392
21393
|
},
|
|
21393
21394
|
{
|
|
21394
21395
|
label: "Good",
|
|
@@ -21410,7 +21411,7 @@ export async function getUser(id: string): Promise<UserDTO | null> {
|
|
|
21410
21411
|
{
|
|
21411
21412
|
principleId: "data-abstraction",
|
|
21412
21413
|
relationship: "reinforces",
|
|
21413
|
-
note: "Proper abstraction hides implementation
|
|
21414
|
+
note: "Proper abstraction hides implementation \u2014 leaky abstractions expose it"
|
|
21414
21415
|
}
|
|
21415
21416
|
]
|
|
21416
21417
|
},
|
|
@@ -21484,9 +21485,9 @@ const orders = await orderService.getReadyToShip();`
|
|
|
21484
21485
|
label: "Bad",
|
|
21485
21486
|
language: "text",
|
|
21486
21487
|
code: `modules/
|
|
21487
|
-
orders/
|
|
21488
|
-
users/
|
|
21489
|
-
payments/
|
|
21488
|
+
orders/ \u2192 imports from users, payments, inventory, shipping
|
|
21489
|
+
users/ \u2192 imports from orders, payments
|
|
21490
|
+
payments/ \u2192 imports from orders, users
|
|
21490
21491
|
# Every module depends on every other module
|
|
21491
21492
|
# Can't change anything without affecting everything`
|
|
21492
21493
|
},
|
|
@@ -21494,9 +21495,9 @@ const orders = await orderService.getReadyToShip();`
|
|
|
21494
21495
|
label: "Good",
|
|
21495
21496
|
language: "text",
|
|
21496
21497
|
code: `modules/
|
|
21497
|
-
orders/
|
|
21498
|
-
users/
|
|
21499
|
-
payments/
|
|
21498
|
+
orders/ \u2192 depends on: users (read), emits: order.created
|
|
21499
|
+
users/ \u2192 depends on: nothing, listens: order.created
|
|
21500
|
+
payments/ \u2192 depends on: orders (read), emits: payment.completed
|
|
21500
21501
|
# Clear dependency direction, minimal coupling`
|
|
21501
21502
|
}
|
|
21502
21503
|
],
|
|
@@ -21513,7 +21514,7 @@ const orders = await orderService.getReadyToShip();`
|
|
|
21513
21514
|
id: "anemic-module",
|
|
21514
21515
|
name: "Anemic Module",
|
|
21515
21516
|
category: "module-structure",
|
|
21516
|
-
description: "A module with no real business logic
|
|
21517
|
+
description: "A module with no real business logic \u2014 it's just a pass-through that delegates everything to another module or the database. If a module adds no value, it shouldn't exist.",
|
|
21517
21518
|
examples: [
|
|
21518
21519
|
{
|
|
21519
21520
|
label: "Bad",
|
|
@@ -21548,7 +21549,7 @@ export class UserService {
|
|
|
21548
21549
|
{
|
|
21549
21550
|
principleId: "avoid-feature-envy",
|
|
21550
21551
|
relationship: "extends",
|
|
21551
|
-
note: "An anemic module envies no one
|
|
21552
|
+
note: "An anemic module envies no one \u2014 it has no behavior of its own"
|
|
21552
21553
|
}
|
|
21553
21554
|
]
|
|
21554
21555
|
},
|
|
@@ -21568,7 +21569,7 @@ export class ShippingService {
|
|
|
21568
21569
|
const items = await orderService.getItems(orderId);
|
|
21569
21570
|
const address = await orderService.getShippingAddress(orderId);
|
|
21570
21571
|
const weight = await orderService.calculateWeight(orderId);
|
|
21571
|
-
// All data comes from orders
|
|
21572
|
+
// All data comes from orders \u2014 does shipping add any value?
|
|
21572
21573
|
}
|
|
21573
21574
|
}`
|
|
21574
21575
|
},
|
|
@@ -21610,8 +21611,8 @@ const rate = this.calculateRate(shippable.totalWeightGrams, shippable.destinatio
|
|
|
21610
21611
|
language: "text",
|
|
21611
21612
|
code: `Day 1 of project:
|
|
21612
21613
|
"Let's create 12 modules based on our initial design"
|
|
21613
|
-
|
|
21614
|
-
|
|
21614
|
+
\u2192 By month 2, half need to be merged or split
|
|
21615
|
+
\u2192 Wasted effort on wrong boundaries`
|
|
21615
21616
|
},
|
|
21616
21617
|
{
|
|
21617
21618
|
label: "Good",
|
|
@@ -21628,7 +21629,7 @@ const rate = this.calculateRate(shippable.totalWeightGrams, shippable.destinatio
|
|
|
21628
21629
|
{
|
|
21629
21630
|
principleId: "simple-design-minimal",
|
|
21630
21631
|
relationship: "reinforces",
|
|
21631
|
-
note: "Don't add structure you don't need yet
|
|
21632
|
+
note: "Don't add structure you don't need yet \u2014 minimal classes, minimal modules"
|
|
21632
21633
|
}
|
|
21633
21634
|
]
|
|
21634
21635
|
},
|
|
@@ -21719,7 +21720,7 @@ eventBus.emit("orders.order.created", {
|
|
|
21719
21720
|
await userModule.init(); // Must be first
|
|
21720
21721
|
await orderModule.init(); // Depends on users being ready
|
|
21721
21722
|
await paymentModule.init(); // Depends on orders being ready
|
|
21722
|
-
// Swap the order
|
|
21723
|
+
// Swap the order \u2192 runtime crash`
|
|
21723
21724
|
},
|
|
21724
21725
|
{
|
|
21725
21726
|
label: "Good",
|
|
@@ -21807,10 +21808,10 @@ export { mapToDTO } from "./internal/mappers.js";
|
|
|
21807
21808
|
{
|
|
21808
21809
|
label: "Good",
|
|
21809
21810
|
language: "typescript",
|
|
21810
|
-
code: `// modules/orders/index.ts
|
|
21811
|
+
code: `// modules/orders/index.ts \u2014 Clean, consistent boundary
|
|
21811
21812
|
export { orderService } from "./order.service.js";
|
|
21812
21813
|
export type { OrderDTO, CreateOrderInput } from "./order.types.js";
|
|
21813
|
-
// Nothing else exported
|
|
21814
|
+
// Nothing else exported \u2014 boundary is clear and enforced`
|
|
21814
21815
|
}
|
|
21815
21816
|
],
|
|
21816
21817
|
tags: ["anti-pattern", "boundary", "inconsistent", "partial", "leaky"],
|
|
@@ -22011,7 +22012,7 @@ function formatRuleAsMarkdown(rule) {
|
|
|
22011
22012
|
}
|
|
22012
22013
|
function formatAntiPatternAsMarkdown(ap) {
|
|
22013
22014
|
const lines = [
|
|
22014
|
-
`##
|
|
22015
|
+
`## \u26A0 Anti-Pattern: ${ap.name}`,
|
|
22015
22016
|
"",
|
|
22016
22017
|
`**Category:** ${CATEGORY_LABELS[ap.category]}`,
|
|
22017
22018
|
`**ID:** \`${ap.id}\``,
|
|
@@ -22046,7 +22047,7 @@ function formatExample(example) {
|
|
|
22046
22047
|
}
|
|
22047
22048
|
function formatCleanCodeRef(ref) {
|
|
22048
22049
|
return [
|
|
22049
|
-
`- **${ref.relationship}** \`${ref.principleId}\`
|
|
22050
|
+
`- **${ref.relationship}** \`${ref.principleId}\` \u2014 ${ref.note}`,
|
|
22050
22051
|
` _Use: search-principle "${ref.principleId}" in clean-code-mcp_`
|
|
22051
22052
|
];
|
|
22052
22053
|
}
|
|
@@ -22056,7 +22057,7 @@ function formatRuleList(rules) {
|
|
|
22056
22057
|
}
|
|
22057
22058
|
const lines = [`Found **${rules.length}** rule(s):`, ""];
|
|
22058
22059
|
for (const rule of rules) {
|
|
22059
|
-
lines.push(`- **${rule.name}** (\`${rule.id}\`)
|
|
22060
|
+
lines.push(`- **${rule.name}** (\`${rule.id}\`) \u2014 ${truncateDescription(rule.description)}`);
|
|
22060
22061
|
}
|
|
22061
22062
|
return lines.join(`
|
|
22062
22063
|
`);
|
|
@@ -22528,8 +22529,8 @@ function registerSearchByContextTool(server) {
|
|
|
22528
22529
|
});
|
|
22529
22530
|
}
|
|
22530
22531
|
// src/modules/module-validation/validate-module.tool.ts
|
|
22531
|
-
import { readdir } from "
|
|
22532
|
-
import { basename } from "
|
|
22532
|
+
import { readdir } from "fs/promises";
|
|
22533
|
+
import { basename } from "path";
|
|
22533
22534
|
var REQUIRED_FILES = [
|
|
22534
22535
|
{ pattern: ".service.", label: "Service (business logic)", rule: "standard-module-layout" },
|
|
22535
22536
|
{ pattern: ".types.", label: "Types (domain models)", rule: "standard-module-layout" },
|
|
@@ -22579,7 +22580,7 @@ Make sure the path exists and is accessible.`
|
|
|
22579
22580
|
const icon = found ? "PASS" : "FAIL";
|
|
22580
22581
|
if (found)
|
|
22581
22582
|
requiredCount++;
|
|
22582
|
-
lines.push(`- [${icon}] ${req.label}
|
|
22583
|
+
lines.push(`- [${icon}] ${req.label} \u2014 rule: \`${req.rule}\``);
|
|
22583
22584
|
}
|
|
22584
22585
|
let recommendedCount = 0;
|
|
22585
22586
|
lines.push("", "## Recommended Files");
|
|
@@ -22588,7 +22589,7 @@ Make sure the path exists and is accessible.`
|
|
|
22588
22589
|
const icon = found ? "PASS" : "MISSING";
|
|
22589
22590
|
if (found)
|
|
22590
22591
|
recommendedCount++;
|
|
22591
|
-
lines.push(`- [${icon}] ${rec.label}
|
|
22592
|
+
lines.push(`- [${icon}] ${rec.label} \u2014 rule: \`${rec.rule}\``);
|
|
22592
22593
|
}
|
|
22593
22594
|
const totalRequired = REQUIRED_FILES.length;
|
|
22594
22595
|
const totalRecommended = RECOMMENDED_FILES.length;
|
|
@@ -22604,8 +22605,8 @@ Make sure the path exists and is accessible.`
|
|
|
22604
22605
|
});
|
|
22605
22606
|
}
|
|
22606
22607
|
// src/modules/module-validation/check-imports.tool.ts
|
|
22607
|
-
import { readdir as readdir2, readFile } from "
|
|
22608
|
-
import { join, relative, dirname } from "
|
|
22608
|
+
import { readdir as readdir2, readFile } from "fs/promises";
|
|
22609
|
+
import { join, relative, dirname } from "path";
|
|
22609
22610
|
async function walkDir(dir) {
|
|
22610
22611
|
const files = [];
|
|
22611
22612
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
@@ -22716,7 +22717,7 @@ Make sure the path exists and contains module directories.`
|
|
|
22716
22717
|
resultLines.push(`## Result: ${violations.length} violation(s) found`, "", "The following imports bypass module boundaries by importing internal files directly:", "");
|
|
22717
22718
|
const grouped = new Map;
|
|
22718
22719
|
for (const v of violations) {
|
|
22719
|
-
const key = `${v.sourceModule}
|
|
22720
|
+
const key = `${v.sourceModule} \u2192 ${v.targetModule}`;
|
|
22720
22721
|
const list = grouped.get(key) ?? [];
|
|
22721
22722
|
list.push(v);
|
|
22722
22723
|
grouped.set(key, list);
|
|
@@ -22753,18 +22754,18 @@ function generateModuleScaffold(moduleName) {
|
|
|
22753
22754
|
## Directory Structure
|
|
22754
22755
|
\`\`\`
|
|
22755
22756
|
modules/${moduleName}/
|
|
22756
|
-
${moduleName}.service.ts
|
|
22757
|
-
${moduleName}.repository.ts
|
|
22758
|
-
${moduleName}.types.ts
|
|
22759
|
-
${moduleName}.validation.ts
|
|
22760
|
-
${moduleName}.routes.ts
|
|
22761
|
-
index.ts
|
|
22757
|
+
${moduleName}.service.ts \u2014 Business logic (rule: vertical-slice-per-module)
|
|
22758
|
+
${moduleName}.repository.ts \u2014 Data access layer (rule: separate-module-data)
|
|
22759
|
+
${moduleName}.types.ts \u2014 Domain types & interfaces (rule: dto-at-boundaries)
|
|
22760
|
+
${moduleName}.validation.ts \u2014 Zod input schemas (rule: request-validation-at-edge)
|
|
22761
|
+
${moduleName}.routes.ts \u2014 API route handlers (rule: thin-routes)
|
|
22762
|
+
index.ts \u2014 Public API barrel (rule: define-public-api)
|
|
22762
22763
|
\`\`\`
|
|
22763
22764
|
|
|
22764
22765
|
## File: index.ts (Public API)
|
|
22765
22766
|
\`\`\`typescript
|
|
22766
|
-
// Rule: define-public-api
|
|
22767
|
-
// Rule: hide-implementation-details
|
|
22767
|
+
// Rule: define-public-api \u2014 Only export what consumers need
|
|
22768
|
+
// Rule: hide-implementation-details \u2014 Internals stay private
|
|
22768
22769
|
export { ${moduleName.replace(/-/g, "")}Service } from "./${moduleName}.service.js";
|
|
22769
22770
|
export type {
|
|
22770
22771
|
${pascal},
|
|
@@ -22775,7 +22776,7 @@ export type {
|
|
|
22775
22776
|
|
|
22776
22777
|
## File: ${moduleName}.types.ts
|
|
22777
22778
|
\`\`\`typescript
|
|
22778
|
-
// Rule: dto-at-boundaries
|
|
22779
|
+
// Rule: dto-at-boundaries \u2014 Public types are DTOs, not internal models
|
|
22779
22780
|
export interface ${pascal} {
|
|
22780
22781
|
id: string;
|
|
22781
22782
|
createdAt: string;
|
|
@@ -22793,8 +22794,8 @@ export interface Update${pascal}Input {
|
|
|
22793
22794
|
|
|
22794
22795
|
## File: ${moduleName}.service.ts
|
|
22795
22796
|
\`\`\`typescript
|
|
22796
|
-
// Rule: vertical-slice-per-module
|
|
22797
|
-
// Rule: self-contained-initialization
|
|
22797
|
+
// Rule: vertical-slice-per-module \u2014 Service contains business logic
|
|
22798
|
+
// Rule: self-contained-initialization \u2014 Dependencies injected via constructor
|
|
22798
22799
|
import type { ${pascal}, Create${pascal}Input } from "./${moduleName}.types.js";
|
|
22799
22800
|
import type { ${pascal}Repository } from "./${moduleName}.repository.js";
|
|
22800
22801
|
|
|
@@ -22813,8 +22814,8 @@ export class ${pascal}Service {
|
|
|
22813
22814
|
|
|
22814
22815
|
## File: ${moduleName}.repository.ts
|
|
22815
22816
|
\`\`\`typescript
|
|
22816
|
-
// Rule: separate-module-data
|
|
22817
|
-
// Rule: depend-on-abstractions
|
|
22817
|
+
// Rule: separate-module-data \u2014 Only this module accesses its data
|
|
22818
|
+
// Rule: depend-on-abstractions \u2014 Export interface, not just implementation
|
|
22818
22819
|
import type { ${pascal}, Create${pascal}Input } from "./${moduleName}.types.js";
|
|
22819
22820
|
|
|
22820
22821
|
export interface ${pascal}Repository {
|
|
@@ -22825,7 +22826,7 @@ export interface ${pascal}Repository {
|
|
|
22825
22826
|
|
|
22826
22827
|
## File: ${moduleName}.validation.ts
|
|
22827
22828
|
\`\`\`typescript
|
|
22828
|
-
// Rule: request-validation-at-edge
|
|
22829
|
+
// Rule: request-validation-at-edge \u2014 Validate at the API boundary
|
|
22829
22830
|
import { z } from "zod";
|
|
22830
22831
|
|
|
22831
22832
|
export const create${pascal}Schema = z.object({
|
|
@@ -22839,8 +22840,8 @@ export const update${pascal}Schema = z.object({
|
|
|
22839
22840
|
|
|
22840
22841
|
## File: ${moduleName}.routes.ts
|
|
22841
22842
|
\`\`\`typescript
|
|
22842
|
-
// Rule: thin-routes
|
|
22843
|
-
// Rule: one-route-one-module
|
|
22843
|
+
// Rule: thin-routes \u2014 Validate, call service, respond
|
|
22844
|
+
// Rule: one-route-one-module \u2014 Routes belong to this module only
|
|
22844
22845
|
import { ${moduleName.replace(/-/g, "")}Service } from "./index.js";
|
|
22845
22846
|
import { create${pascal}Schema } from "./${moduleName}.validation.js";
|
|
22846
22847
|
|
|
@@ -22864,9 +22865,9 @@ import { create${pascal}Schema } from "./${moduleName}.validation.js";
|
|
|
22864
22865
|
function generateService(moduleName) {
|
|
22865
22866
|
const pascal = toPascalCase(moduleName);
|
|
22866
22867
|
return `// ${moduleName}.service.ts
|
|
22867
|
-
// Rule: vertical-slice-per-module
|
|
22868
|
-
// Rule: self-contained-initialization
|
|
22869
|
-
// Rule: depend-on-abstractions
|
|
22868
|
+
// Rule: vertical-slice-per-module \u2014 Service contains all business logic for this module
|
|
22869
|
+
// Rule: self-contained-initialization \u2014 Dependencies injected via constructor
|
|
22870
|
+
// Rule: depend-on-abstractions \u2014 Depends on repository interface, not implementation
|
|
22870
22871
|
|
|
22871
22872
|
import type { ${pascal}, Create${pascal}Input, Update${pascal}Input } from "./${moduleName}.types.js";
|
|
22872
22873
|
import type { ${pascal}Repository } from "./${moduleName}.repository.js";
|
|
@@ -22911,13 +22912,13 @@ export class ${pascal}Service {
|
|
|
22911
22912
|
function generateRepository(moduleName) {
|
|
22912
22913
|
const pascal = toPascalCase(moduleName);
|
|
22913
22914
|
return `// ${moduleName}.repository.ts
|
|
22914
|
-
// Rule: separate-module-data
|
|
22915
|
-
// Rule: depend-on-abstractions
|
|
22916
|
-
// Rule: data-ownership-principle
|
|
22915
|
+
// Rule: separate-module-data \u2014 Only this module accesses its own data
|
|
22916
|
+
// Rule: depend-on-abstractions \u2014 Export interface for DI and testing
|
|
22917
|
+
// Rule: data-ownership-principle \u2014 This module owns the lifecycle of its data
|
|
22917
22918
|
|
|
22918
22919
|
import type { ${pascal}, Create${pascal}Input, Update${pascal}Input } from "./${moduleName}.types.js";
|
|
22919
22920
|
|
|
22920
|
-
// Interface
|
|
22921
|
+
// Interface \u2014 used by service via dependency injection
|
|
22921
22922
|
export interface ${pascal}Repository {
|
|
22922
22923
|
findById(id: string): Promise<${pascal} | null>;
|
|
22923
22924
|
findAll(): Promise<${pascal}[]>;
|
|
@@ -22926,8 +22927,8 @@ export interface ${pascal}Repository {
|
|
|
22926
22927
|
delete(id: string): Promise<void>;
|
|
22927
22928
|
}
|
|
22928
22929
|
|
|
22929
|
-
// Implementation
|
|
22930
|
-
// Rule: hide-implementation-details
|
|
22930
|
+
// Implementation \u2014 Prisma example (swap for any data source)
|
|
22931
|
+
// Rule: hide-implementation-details \u2014 Implementation is internal to this module
|
|
22931
22932
|
export class Prisma${pascal}Repository implements ${pascal}Repository {
|
|
22932
22933
|
constructor(private readonly prisma: any) {}
|
|
22933
22934
|
|
|
@@ -22958,10 +22959,10 @@ export class Prisma${pascal}Repository implements ${pascal}Repository {
|
|
|
22958
22959
|
function generateTypes(moduleName) {
|
|
22959
22960
|
const pascal = toPascalCase(moduleName);
|
|
22960
22961
|
return `// ${moduleName}.types.ts
|
|
22961
|
-
// Rule: dto-at-boundaries
|
|
22962
|
-
// Rule: stable-contracts
|
|
22962
|
+
// Rule: dto-at-boundaries \u2014 These types are the module's public contract
|
|
22963
|
+
// Rule: stable-contracts \u2014 Changes here affect consumers; use additive changes
|
|
22963
22964
|
|
|
22964
|
-
// Main entity DTO
|
|
22965
|
+
// Main entity DTO \u2014 returned by the module's public API
|
|
22965
22966
|
export interface ${pascal} {
|
|
22966
22967
|
id: string;
|
|
22967
22968
|
// Add domain-specific fields here
|
|
@@ -22972,17 +22973,17 @@ export interface ${pascal} {
|
|
|
22972
22973
|
// Input for creating a new entity
|
|
22973
22974
|
export interface Create${pascal}Input {
|
|
22974
22975
|
// Add required fields for creation
|
|
22975
|
-
// Rule: Do NOT include id, createdAt, updatedAt
|
|
22976
|
+
// Rule: Do NOT include id, createdAt, updatedAt \u2014 those are generated
|
|
22976
22977
|
}
|
|
22977
22978
|
|
|
22978
22979
|
// Input for updating an existing entity
|
|
22979
22980
|
export interface Update${pascal}Input {
|
|
22980
|
-
// All fields optional
|
|
22981
|
-
// Rule: stable-contracts
|
|
22981
|
+
// All fields optional \u2014 partial updates
|
|
22982
|
+
// Rule: stable-contracts \u2014 New optional fields are backwards-compatible
|
|
22982
22983
|
}
|
|
22983
22984
|
|
|
22984
|
-
// Lightweight reference type
|
|
22985
|
-
// Rule: anti-corruption-layer
|
|
22985
|
+
// Lightweight reference type \u2014 for cross-module communication
|
|
22986
|
+
// Rule: anti-corruption-layer \u2014 Other modules use this, not the full entity
|
|
22986
22987
|
export interface ${pascal}Reference {
|
|
22987
22988
|
id: string;
|
|
22988
22989
|
// Include only the minimal fields other modules need
|
|
@@ -22994,8 +22995,8 @@ export interface ${pascal}Reference {
|
|
|
22994
22995
|
function generateValidation(moduleName) {
|
|
22995
22996
|
const pascal = toPascalCase(moduleName);
|
|
22996
22997
|
return `// ${moduleName}.validation.ts
|
|
22997
|
-
// Rule: request-validation-at-edge
|
|
22998
|
-
// Rule: one-route-one-module
|
|
22998
|
+
// Rule: request-validation-at-edge \u2014 Validate input at the API boundary
|
|
22999
|
+
// Rule: one-route-one-module \u2014 Validation schemas belong to the module
|
|
22999
23000
|
|
|
23000
23001
|
import { z } from "zod";
|
|
23001
23002
|
|
|
@@ -23022,7 +23023,7 @@ export const query${pascal}Schema = z.object({
|
|
|
23022
23023
|
// Add module-specific filters
|
|
23023
23024
|
});
|
|
23024
23025
|
|
|
23025
|
-
// Infer types from schemas
|
|
23026
|
+
// Infer types from schemas \u2014 keeps validation and types in sync
|
|
23026
23027
|
export type Create${pascal}Validated = z.infer<typeof create${pascal}Schema>;
|
|
23027
23028
|
export type Update${pascal}Validated = z.infer<typeof update${pascal}Schema>;
|
|
23028
23029
|
export type Query${pascal}Params = z.infer<typeof query${pascal}Schema>;
|
|
@@ -23033,10 +23034,10 @@ export type Query${pascal}Params = z.infer<typeof query${pascal}Schema>;
|
|
|
23033
23034
|
function generatePublicApi(moduleName) {
|
|
23034
23035
|
const pascal = toPascalCase(moduleName);
|
|
23035
23036
|
const camel = toCamelCase(moduleName);
|
|
23036
|
-
return `// index.ts
|
|
23037
|
-
// Rule: define-public-api
|
|
23038
|
-
// Rule: hide-implementation-details
|
|
23039
|
-
// Rule: self-contained-initialization
|
|
23037
|
+
return `// index.ts \u2014 Public API for the ${moduleName} module
|
|
23038
|
+
// Rule: define-public-api \u2014 This file IS the module's contract
|
|
23039
|
+
// Rule: hide-implementation-details \u2014 Only re-export what consumers need
|
|
23040
|
+
// Rule: self-contained-initialization \u2014 Compose and export ready-to-use instances
|
|
23040
23041
|
|
|
23041
23042
|
import { ${pascal}Service } from "./${moduleName}.service.js";
|
|
23042
23043
|
import { Prisma${pascal}Repository } from "./${moduleName}.repository.js";
|
|
@@ -23049,7 +23050,7 @@ import { Prisma${pascal}Repository } from "./${moduleName}.repository.js";
|
|
|
23049
23050
|
// export const ${camel}Service = new ${pascal}Service(repository);
|
|
23050
23051
|
|
|
23051
23052
|
// --- Public Types (the contract) ---
|
|
23052
|
-
// Rule: dto-at-boundaries
|
|
23053
|
+
// Rule: dto-at-boundaries \u2014 Export only DTOs, never internal models
|
|
23053
23054
|
export type {
|
|
23054
23055
|
${pascal},
|
|
23055
23056
|
Create${pascal}Input,
|
|
@@ -23073,9 +23074,9 @@ function generateRouteHandler(moduleName) {
|
|
|
23073
23074
|
const camel = toCamelCase(moduleName);
|
|
23074
23075
|
const urlPath = moduleName;
|
|
23075
23076
|
return `// ${moduleName}.routes.ts
|
|
23076
|
-
// Rule: thin-routes
|
|
23077
|
-
// Rule: one-route-one-module
|
|
23078
|
-
// Rule: request-validation-at-edge
|
|
23077
|
+
// Rule: thin-routes \u2014 Validate input, call service, return response
|
|
23078
|
+
// Rule: one-route-one-module \u2014 These routes belong exclusively to the ${moduleName} module
|
|
23079
|
+
// Rule: request-validation-at-edge \u2014 Zod validation happens here, not in service
|
|
23079
23080
|
|
|
23080
23081
|
import { ${camel}Service } from "./index.js";
|
|
23081
23082
|
import {
|
|
@@ -23142,9 +23143,9 @@ export async function delete${pascal}(req: any, res: any) {
|
|
|
23142
23143
|
function generateIntegrationClient(moduleName) {
|
|
23143
23144
|
const pascal = toPascalCase(moduleName);
|
|
23144
23145
|
return `// ${moduleName}-client.ts
|
|
23145
|
-
// Rule: dedicated-client-wrapper
|
|
23146
|
-
// Rule: isolated-integration-module
|
|
23147
|
-
// Rule: integration-change-isolation
|
|
23146
|
+
// Rule: dedicated-client-wrapper \u2014 Dedicated wrapper for the ${moduleName} external API
|
|
23147
|
+
// Rule: isolated-integration-module \u2014 External API details stay within this module
|
|
23148
|
+
// Rule: integration-change-isolation \u2014 Switching providers only affects this file
|
|
23148
23149
|
|
|
23149
23150
|
// --- Internal types (what YOUR code uses) ---
|
|
23150
23151
|
export interface ${pascal}Request {
|
|
@@ -23167,7 +23168,7 @@ export class ${pascal}Client {
|
|
|
23167
23168
|
}
|
|
23168
23169
|
|
|
23169
23170
|
async execute(request: ${pascal}Request): Promise<${pascal}Response> {
|
|
23170
|
-
// Rule: anti-corruption-layer
|
|
23171
|
+
// Rule: anti-corruption-layer \u2014 Translate internal model to external format
|
|
23171
23172
|
const externalPayload = this.toExternalFormat(request);
|
|
23172
23173
|
|
|
23173
23174
|
const response = await fetch(\`\${this.baseUrl}/endpoint\`, {
|
|
@@ -23186,7 +23187,7 @@ export class ${pascal}Client {
|
|
|
23186
23187
|
}
|
|
23187
23188
|
|
|
23188
23189
|
const externalResponse = await response.json();
|
|
23189
|
-
// Rule: anti-corruption-layer
|
|
23190
|
+
// Rule: anti-corruption-layer \u2014 Translate external response to internal model
|
|
23190
23191
|
return this.toInternalFormat(externalResponse);
|
|
23191
23192
|
}
|
|
23192
23193
|
|
|
@@ -23389,7 +23390,7 @@ Please help design this module by addressing:
|
|
|
23389
23390
|
- What events should this module listen to?
|
|
23390
23391
|
|
|
23391
23392
|
### 5. Dependencies
|
|
23392
|
-
- Map the dependency graph
|
|
23393
|
+
- Map the dependency graph \u2014 which modules does this one depend on?
|
|
23393
23394
|
- Ensure no circular dependencies (rule: \`acyclic-dependency-graph\`)
|
|
23394
23395
|
- Ensure dependency direction flows correctly (rule: \`dependency-direction\`)
|
|
23395
23396
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leonardocrdso/modular-monolith-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server with architectural rules for Modular Monolith — module boundaries, communication patterns, and structural validation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "bun run --watch src/index.ts",
|
|
17
17
|
"start": "bun run src/index.ts",
|
|
18
|
-
"build": "bun build src/index.ts --outdir build --target
|
|
18
|
+
"build": "bun build src/index.ts --outdir build --target bun",
|
|
19
19
|
"prepublishOnly": "bun run build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|