@remade/nestjs-approvals
v0.1.0
Published
PR-style approval workflow system for NestJS with TypeORM
Maintainers
Readme
@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
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 | Quick start, module options, architecture, design decisions, exports | | Lifecycle & Engine | State machine, transitions, PolicyResolver, BlockingThreadChecker | | Entities | All 6 entities with fields, relationships, and constraints | | Approval Policy | Policy configuration, resolution algorithm, defaults | | Workflow Modes | Parallel, sequential, blocking threads | | API Reference | REST endpoints, DTOs, service methods | | Events | 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 controllersEntities
| 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
- Policy denormalization -- JSONB policy frozen at request creation; in-flight workflows are immune to changes.
- Pure engine --
StateMachineandPolicyResolverare DB-free and deterministic. - Optimistic locking --
@VersionColumn()onApprovalRequestprevents concurrent write conflicts. - Transactional writes -- All mutations use
DataSource.transaction()with audit entries in the same transaction. - Reviewer-centric stages -- No separate stage entity; stages are a reviewer property.
- Blocking threads -- Boolean flag on comment threads reuses thread/resolve infrastructure.
