# ADR-059: Dependency Modernization Plan

**Status**: Accepted
**Date**: 2026-02-12
**Implemented**: 2026-02-12

## Context

NPM audit and security scanning identified deprecated dependencies requiring modernization:

| Dependency      | Current | Issue                   | Replacement                             |
| --------------- | ------- | ----------------------- | --------------------------------------- |
| `swagger-jsdoc` | 6.2.8   | Unmaintained since 2022 | `tsoa` (decorator-based OpenAPI)        |
| `rimraf`        | 6.1.2   | Legacy cleanup utility  | Node.js `fs.rm()` (native since v14.14) |

**Constraints**:

- Existing `@openapi` JSDoc annotations in 20 route files
- ADR-018 compliance (API documentation strategy)
- Zero-downtime migration (phased approach)
- Must maintain Express 5.x compatibility

## Decision

### 1. swagger-jsdoc → tsoa Migration

**Architecture**: tsoa controller classes + Express integration (no replacement of Express routing layer).

```text
Current:  Route Files → JSDoc Annotations → swagger-jsdoc → OpenAPI Spec
Future:   Controller Classes → @Route/@Get decorators → tsoa → OpenAPI Spec + Route Registration
```

**Controller Pattern**: Base controller providing common utilities:

```typescript
// src/controllers/base.controller.ts
export abstract class BaseController {
  protected sendSuccess<T>(res: Response, data: T, status = 200) {
    return sendSuccess(res, data, status);
  }
  protected sendError(
    res: Response,
    code: ErrorCode,
    msg: string,
    status: number,
    details?: unknown,
  ) {
    return sendError(res, code, msg, status, details);
  }
}
```

**Express Integration Strategy**: tsoa generates routes.ts; wrap with Express middleware pipeline:

```typescript
// server.ts integration
import { RegisterRoutes } from './src/generated/routes';
RegisterRoutes(app); // tsoa registers routes with existing Express app
```

### 2. rimraf → fs.rm() Migration

**Change**: Replace `rimraf coverage .coverage` script with Node.js native API.

```json
// package.json (before)
"clean": "rimraf coverage .coverage"

// package.json (after)
"clean": "node -e \"import('fs/promises').then(fs => Promise.all([fs.rm('coverage', {recursive:true,force:true}), fs.rm('.coverage', {recursive:true,force:true})]))\""
```

**Alternative**: Create `scripts/clean.mjs` for maintainability:

```javascript
// scripts/clean.mjs
import { rm } from 'fs/promises';
await Promise.all([
  rm('coverage', { recursive: true, force: true }),
  rm('.coverage', { recursive: true, force: true }),
]);
```

## Implementation Plan

### Phase 1: Infrastructure (Tasks 1-4)

| Task | Description                                    | Dependencies |
| ---- | ---------------------------------------------- | ------------ |
| 1    | Install tsoa, configure tsoa.json              | None         |
| 2    | Create BaseController with utility methods     | Task 1       |
| 3    | Configure Express integration (RegisterRoutes) | Task 2       |
| 4    | Set up tsoa spec generation in build pipeline  | Task 3       |

### Phase 2: Controller Migration (Tasks 5-14)

Priority order matches ADR-018:

| Task | Route File                                                                                                        | Controller Class       | Dependencies |
| ---- | ----------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------ |
| 5    | health.routes.ts                                                                                                  | HealthController       | Task 4       |
| 6    | auth.routes.ts                                                                                                    | AuthController         | Task 4       |
| 7    | gamification.routes.ts                                                                                            | AchievementsController | Task 4       |
| 8    | flyer.routes.ts                                                                                                   | FlyersController       | Task 4       |
| 9    | user.routes.ts                                                                                                    | UsersController        | Task 4       |
| 10   | budget.routes.ts                                                                                                  | BudgetController       | Task 4       |
| 11   | recipe.routes.ts                                                                                                  | RecipeController       | Task 4       |
| 12   | store.routes.ts                                                                                                   | StoreController        | Task 4       |
| 13   | admin.routes.ts                                                                                                   | AdminController        | Task 4       |
| 14   | Remaining routes (deals, price, upc, inventory, ai, receipt, category, stats, personalization, reactions, system) | Various                | Task 4       |

### Phase 3: Cleanup and rimraf (Tasks 15-18)

| Task | Description                      | Dependencies        |
| ---- | -------------------------------- | ------------------- |
| 15   | Create scripts/clean.mjs         | None                |
| 16   | Update package.json clean script | Task 15             |
| 17   | Remove rimraf dependency         | Task 16             |
| 18   | Remove swagger-jsdoc + types     | Tasks 5-14 complete |

### Phase 4: Verification (Tasks 19-24)

| Task | Description                       | Dependencies |
| ---- | --------------------------------- | ------------ |
| 19   | Run type-check                    | Tasks 15-18  |
| 20   | Run unit tests                    | Task 19      |
| 21   | Run integration tests             | Task 20      |
| 22   | Verify OpenAPI spec completeness  | Task 21      |
| 23   | Update ADR-018 (reference tsoa)   | Task 22      |
| 24   | Update CLAUDE.md (swagger → tsoa) | Task 23      |

### Task Dependency Graph

```text
                    [1: Install tsoa]
                           |
                    [2: BaseController]
                           |
                    [3: Express Integration]
                           |
                    [4: Build Pipeline]
                           |
        +------------------+------------------+
        |         |        |        |         |
       [5]       [6]      [7]      [8]    [9-14]
     Health     Auth    Gamif    Flyer   Others
        |         |        |        |         |
        +------------------+------------------+
                           |
                    [18: Remove swagger-jsdoc]
                           |
[15: clean.mjs] -----> [16: Update pkg.json]
                           |
                    [17: Remove rimraf]
                           |
                    [19: type-check]
                           |
                    [20: unit tests]
                           |
                    [21: integration tests]
                           |
                    [22: Verify OpenAPI]
                           |
                    [23: Update ADR-018]
                           |
                    [24: Update CLAUDE.md]
```

### Critical Path

**Minimum time to completion**: Tasks 1 → 2 → 3 → 4 → 5 (or any controller) → 18 → 19 → 20 → 21 → 22 → 23 → 24

**Parallelization opportunities**:

- Tasks 5-14 (all controller migrations) can run in parallel after Task 4
- Tasks 15-17 (rimraf removal) can run in parallel with controller migrations

## Technical Decisions

### tsoa Configuration

```json
// tsoa.json
{
  "entryFile": "server.ts",
  "noImplicitAdditionalProperties": "throw-on-extras",
  "controllerPathGlobs": ["src/controllers/**/*.controller.ts"],
  "spec": {
    "outputDirectory": "src/generated",
    "specVersion": 3,
    "basePath": "/api/v1"
  },
  "routes": {
    "routesDir": "src/generated",
    "middleware": "express"
  }
}
```

### Decorator Migration Example

**Before** (swagger-jsdoc):

```typescript
/**
 * @openapi
 * /health/ping:
 *   get:
 *     summary: Simple ping endpoint
 *     tags: [Health]
 *     responses:
 *       200:
 *         description: Server is responsive
 */
router.get('/ping', validateRequest(emptySchema), handler);
```

**After** (tsoa):

```typescript
@Route('health')
@Tags('Health')
export class HealthController extends BaseController {
  @Get('ping')
  @SuccessResponse(200, 'Server is responsive')
  public async ping(): Promise<{ message: string }> {
    return { message: 'pong' };
  }
}
```

### Zod Integration

tsoa uses its own validation. Options:

1. **Replace Zod with tsoa validation** - Use `@Body`, `@Query`, `@Path` decorators with TypeScript types
2. **Hybrid approach** - Keep Zod schemas, call `validateRequest()` within controller methods
3. **Custom template** - Generate tsoa routes that call Zod validation middleware

**Recommended**: Option 1 for new controllers; gradually migrate existing Zod schemas.

## Risk Mitigation

| Risk                                    | Likelihood | Impact | Mitigation                                  |
| --------------------------------------- | ---------- | ------ | ------------------------------------------- |
| tsoa/Express 5.x incompatibility        | Medium     | High   | Test in dev container before migration      |
| Missing OpenAPI coverage post-migration | Low        | Medium | Compare generated specs before/after        |
| Authentication middleware integration   | Medium     | Medium | Test @Security decorator with passport-jwt  |
| Test regression from route changes      | Low        | High   | Run full test suite after each controller   |
| Build time increase (tsoa generation)   | Low        | Low    | Add to npm run build; cache generated files |

## Consequences

### Positive

- **Type-safe API contracts**: tsoa decorators derive types from TypeScript
- **Reduced duplication**: No more parallel JSDoc + TypeScript type definitions
- **Modern tooling**: Active tsoa community (vs. unmaintained swagger-jsdoc)
- **Native Node.js**: fs.rm() is built-in, no external dependency
- **Smaller dependency tree**: Remove rimraf (5 transitive deps) + swagger-jsdoc (8 transitive deps)

### Negative

- **Learning curve**: Decorator-based controller pattern differs from Express handlers
- **Migration effort**: 20 route files require conversion
- **Generated code**: `src/generated/routes.ts` must be version-controlled or regenerated on build

### Neutral

- **Build step change**: Add `tsoa spec && tsoa routes` to build pipeline
- **Testing approach**: May need to adjust test structure for controller classes

## Alternatives Considered

### 1. Update swagger-jsdoc to fork/successor

**Rejected**: No active fork; community has moved to tsoa, fastify-swagger, or NestJS.

### 2. NestJS migration

**Rejected**: Full framework migration (Express → NestJS) is disproportionate to the problem scope.

### 3. fastify-swagger

**Rejected**: Requires Express → Fastify migration; out of scope.

### 4. Keep rimraf, accept deprecation warning

**Rejected**: Native fs.rm() is trivial replacement; no reason to maintain deprecated dependency.

## Key Files

| File                                 | Purpose                               |
| ------------------------------------ | ------------------------------------- |
| `tsoa.json`                          | tsoa configuration                    |
| `src/controllers/base.controller.ts` | Base controller with utilities        |
| `src/controllers/*.controller.ts`    | Individual domain controllers         |
| `src/generated/routes.ts`            | tsoa-generated Express routes         |
| `src/generated/swagger.json`         | Generated OpenAPI 3.0 spec            |
| `scripts/clean.mjs`                  | Native fs.rm() replacement for rimraf |

## Related ADRs

- [ADR-018](./0018-api-documentation-strategy.md) - API Documentation Strategy (will be updated)
- [ADR-003](./0003-standardized-input-validation-using-middleware.md) - Input Validation (Zod integration)
- [ADR-028](./0028-api-response-standardization.md) - Response Standardization (BaseController pattern)
- [ADR-001](./0001-standardized-error-handling.md) - Error Handling (error utilities in BaseController)
