npm package: @remade/nestjs-approvals
Description: PR-style approval workflow system for NestJS with TypeORM
Author: remade
Latest version: 0.1.0

# @app/approvals

A domain-agnostic, PR-style approval system for NestJS applications. Attaches structured approval workflows to any entity via an opaque `externalRefId`. Supports parallel and sequential review stages, blocking comment threads, cancellation, and a full audit trail. Ships as a NestJS dynamic module backed by TypeORM on PostgreSQL.

---

## Quick Start

```typescript
ApprovalsModule.forRoot({
  controllers: { enabled: true },
  connectionName: 'default',
})
```

Set `controllers.enabled` to `false` to register only services and engine without exposing REST endpoints.

## Documentation

| Document | Description |
|---|---|
| [Overview & Architecture](./docs/README.md) | Quick start, module options, architecture, design decisions, exports |
| [Lifecycle & Engine](./docs/lifecycle-and-engine.md) | State machine, transitions, PolicyResolver, BlockingThreadChecker |
| [Entities](./docs/entities.md) | All 6 entities with fields, relationships, and constraints |
| [Approval Policy](./docs/approval-policy.md) | Policy configuration, resolution algorithm, defaults |
| [Workflow Modes](./docs/workflow-modes.md) | Parallel, sequential, blocking threads |
| [API Reference](./docs/api-reference.md) | REST endpoints, DTOs, service methods |
| [Events](./docs/events.md) | Domain events, enums, event string keys |

## Directory Structure

```
libs/approvals/src/
├── approvals.module.ts          # Dynamic module (forRoot / forRootAsync)
├── approvals.constants.ts       # Injection tokens
├── index.ts                     # Barrel export
├── config/                      # Default approval policy
├── interfaces/                  # Module options, ApprovalPolicy type
├── enums/                       # 6 enums (status, roles, decisions, etc.)
├── entities/                    # 6 TypeORM entities
├── engine/                      # StateMachine, PolicyResolver, BlockingThreadChecker
├── events/                      # 20 domain event classes
├── dto/                         # Request DTOs with class-validator
├── services/                    # 5 services
└── controllers/                 # 5 REST controllers
```

## Entities

| Entity | Table | Purpose |
|---|---|---|
| `ApprovalRequest` | `approvals_requests` | Central workflow record with denormalized policy |
| `Reviewer` | `approvals_reviewers` | Reviewer assignment with role, state, and stage |
| `Review` | `approvals_reviews` | Immutable decision record |
| `CommentThread` | `approvals_comment_threads` | Threaded discussion, optionally blocking |
| `Comment` | `approvals_comments` | Individual comment with soft-delete |
| `AuditEntry` | `approvals_audit_entries` | Immutable action log |

## API Endpoints

All routes are prefixed with `approval-requests`.

| Method | Path | Description |
|---|---|---|
| POST | `/approval-requests` | Create request |
| GET | `/approval-requests` | List requests |
| GET | `/approval-requests/:id` | Get request by ID |
| PATCH | `/approval-requests/:id` | Update request |
| POST | `/approval-requests/:id/submit` | Submit for review |
| POST | `/approval-requests/:id/close` | Close |
| POST | `/approval-requests/:id/reopen` | Reopen |
| POST | `/approval-requests/:id/cancel` | Cancel (creator only) |
| POST | `/approval-requests/:id/reviewers` | Add reviewer |
| DELETE | `/approval-requests/:id/reviewers/:userId` | Remove reviewer |
| GET | `/approval-requests/:id/reviewers` | List reviewers |
| POST | `/approval-requests/:id/reviews` | Submit review |
| GET | `/approval-requests/:id/reviews` | List reviews |
| POST | `/approval-requests/:id/reviews/:reviewId/dismiss` | Dismiss review |
| POST | `/approval-requests/:id/threads` | Create thread |
| GET | `/approval-requests/:id/threads` | List threads |
| POST | `/approval-requests/:id/threads/:threadId/comments` | Reply |
| PATCH | `/approval-requests/:id/threads/:threadId/comments/:commentId` | Edit comment |
| DELETE | `/approval-requests/:id/threads/:threadId/comments/:commentId` | Delete comment |
| POST | `/approval-requests/:id/threads/:threadId/resolve` | Resolve thread |
| POST | `/approval-requests/:id/threads/:threadId/unresolve` | Unresolve thread |
| GET | `/approval-requests/:id/audit` | Audit trail |

## Exported Services

| Service | Purpose |
|---|---|
| `ApprovalRequestService` | CRUD, submit, close, reopen, cancel |
| `ReviewerService` | Add/remove reviewers, policy re-evaluation |
| `ReviewService` | Submit/dismiss reviews, stage advancement |
| `CommentService` | Threads, replies, resolve/unresolve |
| `AuditService` | Immutable action logging |

## Key Design Decisions

1. **Policy denormalization** -- JSONB policy frozen at request creation; in-flight workflows are immune to changes.
2. **Pure engine** -- `StateMachine` and `PolicyResolver` are DB-free and deterministic.
3. **Optimistic locking** -- `@VersionColumn()` on `ApprovalRequest` prevents concurrent write conflicts.
4. **Transactional writes** -- All mutations use `DataSource.transaction()` with audit entries in the same transaction.
5. **Reviewer-centric stages** -- No separate stage entity; stages are a reviewer property.
6. **Blocking threads** -- Boolean flag on comment threads reuses thread/resolve infrastructure.