@groundbrick/service-base 0.0.1

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.
Files changed (54) hide show
  1. package/README.md +713 -0
  2. package/dist/core/BaseService.d.ts +55 -0
  3. package/dist/core/BaseService.d.ts.map +1 -0
  4. package/dist/core/BaseService.js +123 -0
  5. package/dist/core/BaseService.js.map +1 -0
  6. package/dist/email/EmailConfigurationService.d.ts +14 -0
  7. package/dist/email/EmailConfigurationService.d.ts.map +1 -0
  8. package/dist/email/EmailConfigurationService.js +60 -0
  9. package/dist/email/EmailConfigurationService.js.map +1 -0
  10. package/dist/email/EmailService.d.ts +15 -0
  11. package/dist/email/EmailService.d.ts.map +1 -0
  12. package/dist/email/EmailService.js +96 -0
  13. package/dist/email/EmailService.js.map +1 -0
  14. package/dist/email/interfaces/EmailInterfaces.d.ts +45 -0
  15. package/dist/email/interfaces/EmailInterfaces.d.ts.map +1 -0
  16. package/dist/email/interfaces/EmailInterfaces.js +2 -0
  17. package/dist/email/interfaces/EmailInterfaces.js.map +1 -0
  18. package/dist/email/providers/SmtpEmailProvider.d.ts +14 -0
  19. package/dist/email/providers/SmtpEmailProvider.d.ts.map +1 -0
  20. package/dist/email/providers/SmtpEmailProvider.js +88 -0
  21. package/dist/email/providers/SmtpEmailProvider.js.map +1 -0
  22. package/dist/index.d.ts +14 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +22 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/types/BusinessError.d.ts +31 -0
  27. package/dist/types/BusinessError.d.ts.map +1 -0
  28. package/dist/types/BusinessError.js +57 -0
  29. package/dist/types/BusinessError.js.map +1 -0
  30. package/dist/types/ServiceOptions.d.ts +14 -0
  31. package/dist/types/ServiceOptions.d.ts.map +1 -0
  32. package/dist/types/ServiceOptions.js +2 -0
  33. package/dist/types/ServiceOptions.js.map +1 -0
  34. package/dist/types/ServiceResult.d.ts +31 -0
  35. package/dist/types/ServiceResult.d.ts.map +1 -0
  36. package/dist/types/ServiceResult.js +61 -0
  37. package/dist/types/ServiceResult.js.map +1 -0
  38. package/dist/validation/ValidationError.d.ts +14 -0
  39. package/dist/validation/ValidationError.d.ts.map +1 -0
  40. package/dist/validation/ValidationError.js +2 -0
  41. package/dist/validation/ValidationError.js.map +1 -0
  42. package/dist/validation/ValidationHelper.d.ts +70 -0
  43. package/dist/validation/ValidationHelper.d.ts.map +1 -0
  44. package/dist/validation/ValidationHelper.js +169 -0
  45. package/dist/validation/ValidationHelper.js.map +1 -0
  46. package/dist/validation/ValidationResult.d.ts +12 -0
  47. package/dist/validation/ValidationResult.d.ts.map +1 -0
  48. package/dist/validation/ValidationResult.js +2 -0
  49. package/dist/validation/ValidationResult.js.map +1 -0
  50. package/dist/validation/Validator.d.ts +35 -0
  51. package/dist/validation/Validator.d.ts.map +1 -0
  52. package/dist/validation/Validator.js +52 -0
  53. package/dist/validation/Validator.js.map +1 -0
  54. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,713 @@
1
+ # @groundbrick/service-base
2
+
3
+ 🧠 Service layer foundation with validation, logging, error handling, and transaction support.
4
+
5
+
6
+ Service layer base classes and utilities for business logic implementation in layered architecture applications.
7
+
8
+ ## 🚀 Features
9
+
10
+ ### 🎯 **BaseService Class**
11
+ - Integrated logging with service context
12
+ - Database transaction management
13
+ - Input validation framework
14
+ - Standardized error handling
15
+ - Repository integration patterns
16
+
17
+ ### 🔧 **Business Logic Patterns**
18
+ - Input validation before repository calls
19
+ - Business rule enforcement
20
+ - Cross-entity operations coordination
21
+ - Response transformation and mapping
22
+
23
+ ### ⚡ **Error Handling**
24
+ - Business-specific error types (`BusinessError`)
25
+ - Validation error aggregation
26
+ - Repository error transformation
27
+ - Structured error responses for APIs
28
+
29
+ ### ✅ **Validation System**
30
+ - Simple, extensible validation framework
31
+ - Common validation rules included
32
+ - Custom rule registration
33
+ - Field-level error reporting
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install @groundbrick/service-base
39
+ ```
40
+
41
+ ## Dependencies
42
+
43
+ This package requires:
44
+ - `@groundbrick/logger` - Logging functionality
45
+ - `@groundbrick/db-core` - Database interfaces
46
+
47
+ ## Quick Start
48
+
49
+ ### 1. Basic Service Implementation
50
+
51
+ ```typescript
52
+ import { BaseService, BusinessError, ValidationHelper } from '@groundbrick/service-base';
53
+ import { UserRepository } from './UserRepository';
54
+
55
+ interface User {
56
+ id: number;
57
+ name: string;
58
+ email: string;
59
+ active: boolean;
60
+ }
61
+
62
+ interface CreateUserRequest {
63
+ name: string;
64
+ email: string;
65
+ }
66
+
67
+ export class UserService extends BaseService {
68
+ constructor(private userRepository: UserRepository) {
69
+ super();
70
+ }
71
+
72
+ async createUser(userData: CreateUserRequest): Promise<User> {
73
+ const endTimer = this.startOperation('createUser');
74
+
75
+ try {
76
+ // 1. Validate input
77
+ const validationResult = await this.validate(userData, {
78
+ name: [
79
+ ValidationHelper.required(),
80
+ ValidationHelper.minLength(2),
81
+ ValidationHelper.maxLength(100)
82
+ ],
83
+ email: [
84
+ ValidationHelper.required(),
85
+ ValidationHelper.email()
86
+ ]
87
+ });
88
+
89
+ if (!validationResult.isValid) {
90
+ throw new BusinessError(
91
+ 'Invalid user data',
92
+ 'VALIDATION_FAILED',
93
+ undefined,
94
+ { errors: validationResult.errors }
95
+ );
96
+ }
97
+
98
+ // 2. Business logic
99
+ const existingUser = await this.userRepository.findByEmail(userData.email);
100
+ if (existingUser) {
101
+ throw new BusinessError(
102
+ 'User already exists',
103
+ 'USER_EXISTS',
104
+ undefined,
105
+ { email: userData.email }
106
+ );
107
+ }
108
+
109
+ // 3. Create user
110
+ const user = await this.userRepository.create({
111
+ name: userData.name,
112
+ email: userData.email.toLowerCase(),
113
+ active: true
114
+ });
115
+
116
+ this.logger.info('User created successfully', { userId: user.id });
117
+ return user;
118
+
119
+ } catch (error) {
120
+ if (BusinessError.isBusinessError(error)) {
121
+ throw error;
122
+ }
123
+ throw this.handleRepositoryError(error as Error);
124
+ } finally {
125
+ endTimer();
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### 2. Using Transactions
132
+
133
+ ```typescript
134
+ export class OrderService extends BaseService {
135
+ constructor(
136
+ private orderRepository: OrderRepository,
137
+ private inventoryRepository: InventoryRepository,
138
+ options?: ServiceOptions
139
+ ) {
140
+ super(options);
141
+ }
142
+
143
+ async createOrder(orderData: CreateOrderRequest): Promise<Order> {
144
+ // Use transaction for multi-repository operations
145
+ return await this.withTransaction(async (tx) => {
146
+ // Create order
147
+ const order = await this.orderRepository.createWithTransaction(tx, {
148
+ user_id: orderData.user_id,
149
+ total_amount: orderData.total
150
+ });
151
+
152
+ // Update inventory
153
+ for (const item of orderData.items) {
154
+ await this.inventoryRepository.decrementStockWithTransaction(
155
+ tx,
156
+ item.product_id,
157
+ item.quantity
158
+ );
159
+ }
160
+
161
+ return order;
162
+ });
163
+ }
164
+ }
165
+ ```
166
+
167
+ #### 📋 Transaction pattern:
168
+ 1. **Create Parent First, Use Generated ID**
169
+
170
+ ```typescript
171
+ // Inside withTransaction callback
172
+ const newOrder = await this.orderRepository.createWithTransaction(tx, {
173
+ user_id: orderData.user_id,
174
+ total_amount: totalAmount,
175
+ status: 'pending'
176
+ });
177
+
178
+ // newOrder.id is now available (auto-generated by database)
179
+
180
+ // Use the order ID for child records
181
+ for (const item of orderData.items) {
182
+ await this.orderItemRepository.createWithTransaction(tx, {
183
+ order_id: newOrder.id, // 👈 Use the generated ID
184
+ product_id: item.product_id,
185
+ quantity: item.quantity,
186
+ unit_price: item.unit_price
187
+ });
188
+ }
189
+ ```
190
+
191
+ 2. **Alternative: Bulk Creation**
192
+ ```typescript
193
+ // Create all order items at once
194
+ const orderItemsData = orderData.items.map(item => ({
195
+ order_id: newOrder.id, // Same ID for all items
196
+ product_id: item.product_id,
197
+ quantity: item.quantity,
198
+ unit_price: item.unit_price
199
+ }));
200
+
201
+ await this.orderItemRepository.createManyWithTransaction(tx, orderItemsData);
202
+ ```
203
+
204
+ 3. **Complete Example with Proper Flow**
205
+ ```typescript
206
+ async createOrder(orderData: CreateOrderRequest): Promise<Order> {
207
+ return await this.withTransaction(async (tx) => {
208
+ // 1. Validate business rules first
209
+ await this.validateOrderData(orderData);
210
+
211
+ // 2. Calculate total amount
212
+ let totalAmount = 0;
213
+ const validatedItems = [];
214
+
215
+ for (const item of orderData.items) {
216
+ const product = await this.productRepository.findByIdWithTransaction(tx, item.product_id);
217
+ // ... validation logic
218
+ totalAmount += product.price * item.quantity;
219
+ validatedItems.push({
220
+ product_id: item.product_id,
221
+ quantity: item.quantity,
222
+ unit_price: product.price
223
+ });
224
+ }
225
+
226
+ // 3. Create the order (gets auto-generated ID)
227
+ const newOrder = await this.orderRepository.createWithTransaction(tx, {
228
+ user_id: orderData.user_id,
229
+ total_amount: totalAmount,
230
+ status: 'pending',
231
+ created_at: new Date()
232
+ });
233
+
234
+ // 4. Create order items using the order ID
235
+ for (const item of validatedItems) {
236
+ await this.orderItemRepository.createWithTransaction(tx, {
237
+ order_id: newOrder.id, // 👈 Key: Use generated order ID
238
+ product_id: item.product_id,
239
+ quantity: item.quantity,
240
+ unit_price: item.unit_price
241
+ });
242
+ }
243
+
244
+ // 5. Update product inventory
245
+ for (const item of orderData.items) {
246
+ await this.productRepository.decrementStockWithTransaction(
247
+ tx,
248
+ item.product_id,
249
+ item.quantity
250
+ );
251
+ }
252
+
253
+ // 6. Return the complete order (with ID)
254
+ return newOrder;
255
+ });
256
+ }
257
+ ```
258
+
259
+ #### 🔧 Repository Method Requirements:
260
+ Your repositories need to support transaction-aware methods:
261
+ ```typescript
262
+ // In your OrderRepository
263
+ class OrderRepository extends BaseRepository<Order> {
264
+ async createWithTransaction(tx: DatabaseTransaction, data: Partial<Order>): Promise<Order> {
265
+ // Use tx.query() instead of this.db.query()
266
+ const result = await tx.query(
267
+ 'INSERT INTO orders (user_id, total_amount, status, created_at) VALUES (?, ?, ?, ?) RETURNING *',
268
+ [data.user_id, data.total_amount, data.status, data.created_at]
269
+ );
270
+ return result.rows[0];
271
+ }
272
+ }
273
+
274
+ // In your OrderItemRepository
275
+ class OrderItemRepository extends BaseRepository<OrderItem> {
276
+ async createWithTransaction(tx: DatabaseTransaction, data: Partial<OrderItem>): Promise<OrderItem> {
277
+ const result = await tx.query(
278
+ 'INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?) RETURNING *',
279
+ [data.order_id, data.product_id, data.quantity, data.unit_price]
280
+ );
281
+ return result.rows[0];
282
+ }
283
+
284
+ async createManyWithTransaction(tx: DatabaseTransaction, items: Partial<OrderItem>[]): Promise<OrderItem[]> {
285
+ // Bulk insert implementation
286
+ const values = items.map(item => [item.order_id, item.product_id, item.quantity, item.unit_price]);
287
+ // ... bulk insert logic
288
+ }
289
+ }
290
+ ```
291
+ #### 💡 Key Points:
292
+
293
+ - Order matters: Create parent record first to get the ID
294
+ - Use the transaction: All operations must use the same tx parameter
295
+ - ID is immediately available: After createWithTransaction, the returned object has the generated ID
296
+ - All-or-nothing: If any step fails, the entire transaction rolls back
297
+ - Performance: Consider bulk operations for multiple child records
298
+
299
+ ### 3. Custom Validation Rules
300
+
301
+ ```typescript
302
+ export class ProductService extends BaseService {
303
+ constructor(private productRepository: ProductRepository) {
304
+ super();
305
+
306
+ // Register custom validation rules
307
+ this.validator.registerRule('sku', (value) => {
308
+ const skuPattern = /^[A-Z]{2}-\d{4}$/;
309
+ return skuPattern.test(value) ? null : 'SKU must follow format: XX-0000';
310
+ });
311
+ }
312
+
313
+ async createProduct(productData: CreateProductRequest): Promise<Product> {
314
+ const validationResult = await this.validate(productData, {
315
+ name: [ValidationHelper.required(), ValidationHelper.minLength(2)],
316
+ sku: [ValidationHelper.required(), this.validator.getRule('sku')!],
317
+ price: [ValidationHelper.required(), ValidationHelper.numberRange(0.01, 10000)]
318
+ });
319
+
320
+ if (!validationResult.isValid) {
321
+ throw new BusinessError('Invalid product data', 'VALIDATION_FAILED', undefined, {
322
+ errors: validationResult.errors
323
+ });
324
+ }
325
+
326
+ // Create product logic...
327
+ }
328
+ }
329
+ ```
330
+
331
+ ## API Reference
332
+
333
+ ### BaseService
334
+
335
+ The abstract base class that your services should extend.
336
+
337
+ #### Constructor Options
338
+
339
+ ```typescript
340
+ interface ServiceOptions {
341
+ database?: DatabaseClient; // For transaction support
342
+ validator?: Validator; // Custom validator instance
343
+ config?: Record<string, any>; // Service-specific config
344
+ }
345
+ ```
346
+
347
+ #### Protected Methods
348
+
349
+ ```typescript
350
+ // Validation
351
+ protected async validate<T>(data: T, rules: ValidationRules): Promise<ValidationResult>
352
+ protected validateRequired(data: Record<string, any>, fields: string[]): void
353
+
354
+ // Transactions
355
+ protected async withTransaction<T>(callback: (tx: DatabaseTransaction) => Promise<T>): Promise<T>
356
+
357
+ // Error Handling
358
+ protected handleRepositoryError(error: Error): BusinessError
359
+
360
+ // Utilities
361
+ protected startOperation(operation: string, metadata?: Record<string, any>): () => void
362
+ protected safeSerialize(obj: any): any
363
+ ```
364
+
365
+ ### 🛡 BusinessError
366
+
367
+ Custom error class for business logic errors.
368
+
369
+ ```typescript
370
+ class BusinessError extends Error {
371
+ constructor(
372
+ message: string,
373
+ code: string,
374
+ originalError?: Error,
375
+ context?: Record<string, any>
376
+ )
377
+
378
+ // Properties
379
+ readonly code: string
380
+ readonly originalError?: Error
381
+ readonly context?: Record<string, any>
382
+ readonly timestamp: Date
383
+
384
+ // Methods
385
+ toJSON(): Record<string, any>
386
+ toUserResponse(): { error: string; code: string; timestamp: string }
387
+ hasCode(code: string): boolean
388
+ static isBusinessError(error: any): error is BusinessError
389
+ }
390
+ ```
391
+
392
+ ### ValidationHelper
393
+
394
+ Pre-built validation rules for common scenarios.
395
+
396
+ ```typescript
397
+ class ValidationHelper {
398
+ // Basic rules
399
+ static required(): ValidationRule
400
+ static minLength(min: number): ValidationRule
401
+ static maxLength(max: number): ValidationRule
402
+ static email(): ValidationRule
403
+ static numberRange(min?: number, max?: number): ValidationRule
404
+ static pattern(regex: RegExp, message: string): ValidationRule
405
+ static oneOf(options: any[], message?: string): ValidationRule
406
+
407
+ // Array rules
408
+ static arrayMinLength(min: number): ValidationRule
409
+
410
+ // Advanced rules
411
+ static custom(validator: (value: any, data?: any) => string | null): ValidationRule
412
+ static when(condition: (data: any) => boolean, rule: ValidationRule): ValidationRule
413
+
414
+ // Utility methods
415
+ static combine(...rules: ValidationRule[]): ValidationRule[]
416
+ static entityId(): ValidationRule[]
417
+
418
+ // Pre-configured rule sets
419
+ static userCreation(): ValidationRules
420
+ static pagination(): ValidationRules
421
+ }
422
+ ```
423
+
424
+ ## ✅ Validation System
425
+
426
+ ### Basic Validation
427
+
428
+ ```typescript
429
+ const result = await this.validate(data, {
430
+ email: [ValidationHelper.required(), ValidationHelper.email()],
431
+ age: [ValidationHelper.numberRange(18, 120)]
432
+ });
433
+
434
+ if (!result.isValid) {
435
+ // Handle validation errors
436
+ console.log(result.errors); // { email: ['Must be a valid email'], age: ['Must be at least 18'] }
437
+ }
438
+ ```
439
+
440
+ ### 🧠 Custom Validation Rules
441
+
442
+ ```typescript
443
+ // Register a custom rule
444
+ this.validator.registerRule('phone', (value) => {
445
+ const phonePattern = /^\+?[\d\s-()]{10,}$/;
446
+ return phonePattern.test(value) ? null : 'Invalid phone number format';
447
+ });
448
+
449
+ // Use in validation
450
+ const rules = {
451
+ phone: [ValidationHelper.required(), this.validator.getRule('phone')!]
452
+ };
453
+ ```
454
+
455
+ ### Conditional Validation
456
+
457
+ ```typescript
458
+ const rules = {
459
+ email: [ValidationHelper.required(), ValidationHelper.email()],
460
+ password: ValidationHelper.when(
461
+ (data) => data.isNewUser === true,
462
+ ValidationHelper.combine(
463
+ ValidationHelper.required(),
464
+ ValidationHelper.minLength(8)
465
+ )
466
+ )
467
+ };
468
+ ```
469
+
470
+ ## Error Handling Patterns
471
+
472
+ ### 1. Repository Error Transformation
473
+
474
+ The `handleRepositoryError` method automatically transforms common database errors:
475
+
476
+ ```typescript
477
+ // Database constraint violation → BusinessError with DUPLICATE_RESOURCE code
478
+ // Record not found → BusinessError with RESOURCE_NOT_FOUND code
479
+ // Foreign key violation → BusinessError with CONSTRAINT_VIOLATION code
480
+ // Other errors → BusinessError with OPERATION_FAILED code
481
+ ```
482
+
483
+ ### 2. Structured Error Responses
484
+
485
+ ```typescript
486
+ try {
487
+ const user = await userService.createUser(userData);
488
+ return createSuccessResult(user);
489
+ } catch (error) {
490
+ if (BusinessError.isBusinessError(error)) {
491
+ // Handle business logic errors
492
+ return createErrorResult(error);
493
+ }
494
+ // Handle unexpected errors
495
+ throw error;
496
+ }
497
+ ```
498
+
499
+ ### 3. API Integration
500
+
501
+ ```typescript
502
+ export class UserController {
503
+ async createUser(req: any, res: any) {
504
+ try {
505
+ const user = await this.userService.createUser(req.body);
506
+ res.status(201).json(createSuccessResult(user));
507
+ } catch (error) {
508
+ const result = createErrorResult(error as Error);
509
+
510
+ // Map business error codes to HTTP status codes
511
+ let status = 500;
512
+ if (BusinessError.isBusinessError(error)) {
513
+ switch (error.code) {
514
+ case 'VALIDATION_FAILED': status = 400; break;
515
+ case 'USER_EXISTS': status = 409; break;
516
+ case 'USER_NOT_FOUND': status = 404; break;
517
+ }
518
+ }
519
+
520
+ res.status(status).json(result);
521
+ }
522
+ }
523
+ }
524
+ ```
525
+
526
+ ## Transaction Management
527
+
528
+ ### Simple Transaction
529
+
530
+ ```typescript
531
+ async updateUserProfile(userId: number, profileData: any): Promise<User> {
532
+ return await this.withTransaction(async (tx) => {
533
+ // Update user
534
+ const user = await this.userRepository.updateWithTransaction(tx, userId, {
535
+ name: profileData.name,
536
+ email: profileData.email
537
+ });
538
+
539
+ // Update user preferences
540
+ await this.preferencesRepository.updateWithTransaction(tx, userId, {
541
+ theme: profileData.theme,
542
+ language: profileData.language
543
+ });
544
+
545
+ return user;
546
+ });
547
+ }
548
+ ```
549
+
550
+ ### Complex Business Transaction
551
+
552
+ ```typescript
553
+ async processOrder(orderData: CreateOrderRequest): Promise<Order> {
554
+ return await this.withTransaction(async (tx) => {
555
+ // 1. Validate inventory
556
+ for (const item of orderData.items) {
557
+ const product = await this.productRepository.findByIdWithTransaction(tx, item.productId);
558
+ if (product.stock < item.quantity) {
559
+ throw new BusinessError('Insufficient stock', 'INSUFFICIENT_STOCK');
560
+ }
561
+ }
562
+
563
+ // 2. Create order
564
+ const order = await this.orderRepository.createWithTransaction(tx, orderData);
565
+
566
+ // 3. Update inventory
567
+ for (const item of orderData.items) {
568
+ await this.productRepository.decrementStockWithTransaction(
569
+ tx, item.productId, item.quantity
570
+ );
571
+ }
572
+
573
+ // 4. Send notification (example of non-transactional side effect)
574
+ // Note: This should be handled outside the transaction
575
+ // Consider using event-driven patterns for side effects
576
+
577
+ return order;
578
+ });
579
+ }
580
+ ```
581
+
582
+ ## Best Practices
583
+
584
+ ### 1. Keep Services Focused
585
+
586
+ ```typescript
587
+ // ✅ Good - Single responsibility
588
+ class UserService extends BaseService {
589
+ async createUser(userData: CreateUserRequest): Promise<User> { }
590
+ async updateUser(id: number, updates: UpdateUserRequest): Promise<User> { }
591
+ async getUserById(id: number): Promise<User> { }
592
+ }
593
+
594
+ // ❌ Bad - Too many responsibilities
595
+ class UserService extends BaseService {
596
+ async createUser(userData: CreateUserRequest): Promise<User> { }
597
+ async sendWelcomeEmail(user: User): Promise<void> { } // Should be EmailService
598
+ async generateReport(userId: number): Promise<Report> { } // Should be ReportService
599
+ }
600
+ ```
601
+
602
+ ### 2. Validate Input Early
603
+
604
+ ```typescript
605
+ // ✅ Good - Validate first
606
+ async createUser(userData: CreateUserRequest): Promise<User> {
607
+ const validationResult = await this.validate(userData, this.getUserValidationRules());
608
+ if (!validationResult.isValid) {
609
+ throw new BusinessError('Validation failed', 'VALIDATION_FAILED', undefined, {
610
+ errors: validationResult.errors
611
+ });
612
+ }
613
+
614
+ // Continue with business logic...
615
+ }
616
+
617
+ // ❌ Bad - Validation mixed with business logic
618
+ async createUser(userData: CreateUserRequest): Promise<User> {
619
+ const existingUser = await this.userRepository.findByEmail(userData.email);
620
+ if (!userData.email) { // Too late for basic validation
621
+ throw new Error('Email required');
622
+ }
623
+ // ...
624
+ }
625
+ ```
626
+
627
+ ### 3. Use Transactions for Multi-Repository Operations
628
+
629
+ ```typescript
630
+ // ✅ Good - Transaction for consistency
631
+ async transferFunds(fromAccount: number, toAccount: number, amount: number): Promise<void> {
632
+ await this.withTransaction(async (tx) => {
633
+ await this.accountRepository.decrementBalanceWithTransaction(tx, fromAccount, amount);
634
+ await this.accountRepository.incrementBalanceWithTransaction(tx, toAccount, amount);
635
+ await this.transactionRepository.createWithTransaction(tx, {
636
+ from_account: fromAccount,
637
+ to_account: toAccount,
638
+ amount
639
+ });
640
+ });
641
+ }
642
+
643
+ // ❌ Bad - No transaction, inconsistent state possible
644
+ async transferFunds(fromAccount: number, toAccount: number, amount: number): Promise<void> {
645
+ await this.accountRepository.decrementBalance(fromAccount, amount);
646
+ await this.accountRepository.incrementBalance(toAccount, amount); // Could fail, leaving inconsistent state
647
+ await this.transactionRepository.create({ from_account: fromAccount, to_account: toAccount, amount });
648
+ }
649
+ ```
650
+
651
+ ### 4. Handle Errors Appropriately
652
+
653
+ ```typescript
654
+ // ✅ Good - Structured error handling
655
+ async getUserById(id: number): Promise<User> {
656
+ try {
657
+ this.validateRequired({ id }, ['id']);
658
+
659
+ const user = await this.userRepository.findById(id);
660
+ if (!user) {
661
+ throw new BusinessError('User not found', 'USER_NOT_FOUND', undefined, { userId: id });
662
+ }
663
+
664
+ return user;
665
+ } catch (error) {
666
+ if (BusinessError.isBusinessError(error)) {
667
+ throw error; // Re-throw business errors
668
+ }
669
+ // Transform repository errors
670
+ throw this.handleRepositoryError(error as Error);
671
+ }
672
+ }
673
+ ```
674
+
675
+ ## Integration with Other Packages
676
+
677
+ This service layer integrates seamlessly with other microframework packages:
678
+
679
+ ```typescript
680
+ import { createLogger } from '@groundbrick/logger';
681
+ import { DatabaseFactory } from '@groundbrick/db-postgres';
682
+ import { UserRepository } from '@groundbrick/repository-base';
683
+ import { UserService } from './UserService';
684
+
685
+ // Initialize database
686
+ const db = DatabaseFactory.getInstance({
687
+ host: 'localhost',
688
+ database: 'myapp',
689
+ user: 'user',
690
+ password: 'password'
691
+ });
692
+
693
+ // Initialize repository
694
+ const userRepository = new UserRepository(db);
695
+
696
+ // Initialize service with database for transaction support
697
+ const userService = new UserService(userRepository, { database: db });
698
+
699
+ // Use in application
700
+ const user = await userService.createUser({
701
+ name: 'John Doe',
702
+ email: 'john@example.com'
703
+ });
704
+ ```
705
+
706
+
707
+ ## 📜 License
708
+
709
+ MIT
710
+
711
+ ---
712
+
713
+