guardian-framework 0.1.10 → 0.1.12

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.
@@ -0,0 +1,411 @@
1
+ # TypeScript/Node.js Enterprise Code Generation — DDD + Clean Architecture
2
+
3
+ > Canonical skill for generating production-grade TypeScript code following Clean Architecture with DDD.
4
+ > All code MUST follow these patterns. Validators enforce compliance.
5
+ >
6
+ > Source: Clean Architecture principles + TypeScript best practices + DDD patterns from context7.
7
+
8
+ ---
9
+
10
+ ## 1. Project Structure — Clean Architecture with DDD
11
+
12
+ Every bounded context follows the same 4-layer structure:
13
+
14
+ ```
15
+ module/
16
+ ├── domain/ # Pure domain entities, value objects, events
17
+ │ ├── index.ts # Re-exports
18
+ │ ├── entity.ts # Aggregate roots and entities
19
+ │ ├── value.ts # Value objects
20
+ │ ├── event.ts # Domain event payloads
21
+ │ ├── repository.ts # Repository interfaces
22
+ │ └── errors.ts # Typed error classes
23
+ ├── application/ # Service interfaces and DTOs
24
+ │ ├── service.ts # Service interface definitions
25
+ │ └── dto.ts # Input/Output DTOs with validation
26
+ ├── infrastructure/ # Repository implementations, external adapters
27
+ │ ├── repository/ # Repository implementations
28
+ │ │ ├── typeorm-repo.ts
29
+ │ │ └── in-memory-repo.ts
30
+ │ └── persistence/ # Database connection, migrations
31
+ └── interfaces/ # API contracts (HTTP)
32
+ ├── controller.ts # Route handler
33
+ ├── middleware.ts # Auth, validation middleware
34
+ └── dto.ts # Request/Response schemas
35
+ ```
36
+
37
+ ### Dependency Direction Rule (Inward Dependency)
38
+
39
+ ```
40
+ domain → application → infrastructure → interfaces
41
+ ↑ ↑
42
+ └── inner layers never depend on outer layers
43
+ ```
44
+
45
+ - **domain/** — depends on nothing except TS stdlib
46
+ - **application/** — depends on domain
47
+ - **infrastructure/** — depends on domain + application
48
+ - **interfaces/** — depends on application
49
+
50
+ ---
51
+
52
+ ## 2. Domain Layer — Entities & Value Objects
53
+
54
+ ### Entity with Encapsulated State
55
+
56
+ ```typescript
57
+ // entity.ts — Domain entity with identity.
58
+
59
+ import { randomUUID } from 'node:crypto';
60
+
61
+ export type Status = 'pending' | 'active' | 'completed' | 'failed';
62
+
63
+ export class Entity {
64
+ private readonly _id: string;
65
+ private _status: Status;
66
+ private readonly _createdAt: Date;
67
+ private _updatedAt: Date;
68
+
69
+ constructor(id?: string) {
70
+ this._id = id ?? randomUUID();
71
+ this._status = 'pending';
72
+ this._createdAt = new Date();
73
+ this._updatedAt = new Date();
74
+ }
75
+
76
+ get id(): string { return this._id; }
77
+ get status(): Status { return this._status; }
78
+ get createdAt(): Date { return this._createdAt; }
79
+
80
+ /** Transition state — throws on invalid transition. */
81
+ transition(newStatus: Status): void {
82
+ if (!this.canTransitionTo(newStatus)) {
83
+ throw new DomainError(
84
+ ErrorCode.InvalidTransition,
85
+ `Cannot transition from ${this._status} to ${newStatus}`,
86
+ );
87
+ }
88
+ this._status = newStatus;
89
+ this._updatedAt = new Date();
90
+ }
91
+
92
+ private canTransitionTo(target: Status): boolean {
93
+ const validTransitions: Record<Status, Status[]> = {
94
+ pending: ['active'],
95
+ active: ['completed', 'failed'],
96
+ completed: [],
97
+ failed: [],
98
+ };
99
+ return validTransitions[this._status].includes(target);
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Value Object
105
+
106
+ ```typescript
107
+ // value.ts — Immutable value object.
108
+
109
+ export class Money {
110
+ public static readonly ZERO = new Money(0, 'USD');
111
+
112
+ private constructor(
113
+ private readonly _amount: number,
114
+ private readonly _currency: string,
115
+ ) { Object.freeze(this); }
116
+
117
+ static create(amount: number, currency: string): Money {
118
+ if (amount < 0) throw new DomainError(ErrorCode.Validation, 'Amount must be non-negative');
119
+ if (currency.length !== 3) throw new DomainError(ErrorCode.Validation, 'Currency must be ISO 4217');
120
+ return new Money(Math.round(amount * 100), currency.toUpperCase());
121
+ }
122
+
123
+ get amount(): number { return this._amount; }
124
+ get currency(): string { return this._currency; }
125
+
126
+ add(other: Money): Money {
127
+ if (this._currency !== other._currency) {
128
+ throw new DomainError(ErrorCode.Validation, 'Currency mismatch');
129
+ }
130
+ return new Money(this._amount + other._amount, this._currency);
131
+ }
132
+
133
+ equals(other: unknown): boolean {
134
+ return other instanceof Money &&
135
+ this._amount === other._amount &&
136
+ this._currency === other._currency;
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### Repository Interface
142
+
143
+ ```typescript
144
+ // repository.ts — Interface defined in domain, implemented in infrastructure.
145
+
146
+ export interface Repository<T extends Entity> {
147
+ findById(id: string): Promise<T | null>;
148
+ save(entity: T): Promise<void>;
149
+ delete(id: string): Promise<void>;
150
+ findByStatus(status: Status): Promise<T[]>;
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 3. Domain Error Handling
157
+
158
+ ```typescript
159
+ // errors.ts — Typed domain errors.
160
+
161
+ export enum ErrorCode {
162
+ NotFound = 'NOT_FOUND',
163
+ InvalidState = 'INVALID_STATE',
164
+ Validation = 'VALIDATION',
165
+ Duplicate = 'DUPLICATE',
166
+ InvalidTransition = 'INVALID_TRANSITION',
167
+ NotFound = 'NOT_FOUND',
168
+ }
169
+
170
+ export class DomainError extends Error {
171
+ constructor(
172
+ public readonly code: ErrorCode,
173
+ message: string,
174
+ public readonly cause?: Error,
175
+ ) {
176
+ super(message);
177
+ this.name = 'DomainError';
178
+ }
179
+
180
+ static notFound(id: string): DomainError {
181
+ return new DomainError(ErrorCode.NotFound, `Resource ${id} not found`);
182
+ }
183
+ }
184
+
185
+ export class NotFoundError extends DomainError {
186
+ constructor(id: string) {
187
+ super(ErrorCode.NotFound, `Resource ${id} not found`);
188
+ this.name = 'NotFoundError';
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Rules
194
+ - ✅ Use typed error classes with `ErrorCode` enum
195
+ - ✅ Extend `Error` with proper `this.name = 'XxxError'`
196
+ - ✅ Create subclasses for common errors (`NotFoundError`, `ValidationError`)
197
+ - ❌ Never use bare strings as errors
198
+ - ❌ Never return `Error` from domain — throw typed errors
199
+
200
+ ---
201
+
202
+ ## 4. Application Layer — Services
203
+
204
+ ```typescript
205
+ // service.ts — Application service.
206
+
207
+ export interface Service {
208
+ execute(cmd: Command): Promise<Entity>;
209
+ getById(id: string): Promise<Entity>;
210
+ }
211
+
212
+ export class EntityService implements Service {
213
+ constructor(
214
+ private readonly repo: Repository,
215
+ private readonly logger: Logger,
216
+ ) {}
217
+
218
+ async execute(cmd: Command): Promise<Entity> {
219
+ const entity = new Entity();
220
+ entity.transition(cmd.desiredStatus);
221
+
222
+ await this.repo.save(entity);
223
+ this.logger.info('Entity executed', { id: entity.id });
224
+ return entity;
225
+ }
226
+
227
+ async getById(id: string): Promise<Entity> {
228
+ const entity = await this.repo.findById(id);
229
+ if (!entity) throw NotFoundError.forId(id);
230
+ return entity;
231
+ }
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 5. Infrastructure Layer
238
+
239
+ ```typescript
240
+ // infrastructure/repository/typeorm-repo.ts
241
+
242
+ export class TypeOrmRepository implements Repository {
243
+ constructor(
244
+ private readonly dataSource: DataSource,
245
+ private readonly logger: Logger,
246
+ ) {}
247
+
248
+ async findById(id: string): Promise<Entity | null> {
249
+ const model = await this.dataSource.getRepository(EntityModel)
250
+ .findOneBy({ id });
251
+ return model ? model.toDomain() : null;
252
+ }
253
+
254
+ async save(entity: Entity): Promise<void> {
255
+ const model = EntityModel.fromDomain(entity);
256
+ await this.dataSource.getRepository(EntityModel).save(model);
257
+ }
258
+
259
+ async delete(id: string): Promise<void> {
260
+ await this.dataSource.getRepository(EntityModel).delete(id);
261
+ }
262
+ }
263
+ ```
264
+
265
+ ---
266
+
267
+ ## 6. Interfaces Layer — HTTP Controllers
268
+
269
+ ```typescript
270
+ // interfaces/controller.ts
271
+
272
+ import { Router, Request, Response } from 'express';
273
+
274
+ export function createRouter(svc: Service): Router {
275
+ const router = Router();
276
+
277
+ router.post('/execute', async (req: Request, res: Response) => {
278
+ try {
279
+ const cmd = ExecuteRequest.parse(req.body); // Zod validation
280
+ const entity = await svc.execute(cmd);
281
+ res.status(201).json(entityToResponse(entity));
282
+ } catch (err) {
283
+ if (err instanceof DomainError) {
284
+ res.status(errorHttpStatus(err.code)).json({ error: err.message });
285
+ return;
286
+ }
287
+ if (err instanceof ZodError) {
288
+ res.status(400).json({ error: 'Validation failed', details: err.errors });
289
+ return;
290
+ }
291
+ console.error('Unexpected error:', err);
292
+ res.status(500).json({ error: 'Internal server error' });
293
+ }
294
+ });
295
+
296
+ return router;
297
+ }
298
+ ```
299
+
300
+ ### Rules
301
+ - ✅ Controllers translate HTTP to application calls — no business logic
302
+ - ✅ Use Zod for request validation
303
+ - ✅ Map domain errors to HTTP status codes
304
+ - ✅ Use Express/Fastify routers, keep handlers thin
305
+
306
+ ---
307
+
308
+ ## 7. Testing Patterns
309
+
310
+ ### Unit Tests
311
+
312
+ ```typescript
313
+ describe('Entity', () => {
314
+ it('transitions from pending to active', () => {
315
+ const entity = new Entity();
316
+ entity.transition('active');
317
+ expect(entity.status).toBe('active');
318
+ });
319
+
320
+ it('throws on invalid transition', () => {
321
+ const entity = new Entity();
322
+ entity.transition('active');
323
+ expect(() => entity.transition('pending')).toThrow(DomainError);
324
+ });
325
+ });
326
+ ```
327
+
328
+ ### Integration Tests
329
+
330
+ ```typescript
331
+ describe('TypeOrmRepository', () => {
332
+ let dataSource: DataSource;
333
+
334
+ beforeAll(async () => {
335
+ dataSource = await setupTestDatabase();
336
+ });
337
+
338
+ it('persists and retrieves entity', async () => {
339
+ const repo = new TypeOrmRepository(dataSource, pino());
340
+ const entity = new Entity();
341
+
342
+ await repo.save(entity);
343
+ const found = await repo.findById(entity.id);
344
+
345
+ expect(found).not.toBeNull();
346
+ expect(found!.id).toBe(entity.id);
347
+ });
348
+ });
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 8. Anti-Patterns — NEVER DO
354
+
355
+ ```typescript
356
+ // ❌ any types
357
+ function process(data: any) // BAD — use proper types
358
+
359
+ // ❌ Business logic in controllers
360
+ router.post('/', (req, res) => {
361
+ // domain logic here // BAD
362
+ })
363
+
364
+ // ❌ Direct DB access from controllers
365
+ const user = await db.users.find() // BAD — use repository
366
+
367
+ // ❌ Mutable value objects
368
+ class Money {
369
+ amount: number // BAD — should be readonly
370
+ }
371
+
372
+ // ❌ Throwing non-Error types
373
+ throw 'something went wrong' // BAD
374
+
375
+ // ❌ Ignoring promise rejections
376
+ processAsync() // BAD — missing await
377
+ ```
378
+
379
+ ---
380
+
381
+ ## 9. Project-Level Structure
382
+
383
+ ```
384
+ src/
385
+ module/ // One directory per bounded context
386
+ domain/
387
+ entity.ts
388
+ value.ts
389
+ repository.ts
390
+ errors.ts
391
+ application/
392
+ service.ts
393
+ dto.ts
394
+ infrastructure/
395
+ repository/
396
+ interfaces/
397
+ controller.ts
398
+ dto.ts
399
+ shared/
400
+ logger.ts
401
+ database.ts
402
+ index.ts
403
+ package.json
404
+ tsconfig.json
405
+ ```
406
+
407
+ ---
408
+
409
+ *Version: 1.0.0*
410
+ *Last updated: 2026-07-03*
411
+ *Source: Guardian DDD patterns + TypeScript best practices + context7 (/microsoft/typescript)*