bunsane 0.1.0 → 0.1.2

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 (82) hide show
  1. package/.github/workflows/deploy-docs.yml +57 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +2 -28
  4. package/TODO.md +8 -1
  5. package/bun.lock +3 -0
  6. package/config/upload.config.ts +135 -0
  7. package/core/App.ts +168 -4
  8. package/core/ArcheType.ts +122 -0
  9. package/core/BatchLoader.ts +100 -0
  10. package/core/ComponentRegistry.ts +4 -3
  11. package/core/Components.ts +2 -2
  12. package/core/Decorators.ts +15 -8
  13. package/core/Entity.ts +193 -14
  14. package/core/EntityCache.ts +15 -0
  15. package/core/EntityHookManager.ts +855 -0
  16. package/core/EntityManager.ts +12 -2
  17. package/core/ErrorHandler.ts +64 -7
  18. package/core/FileValidator.ts +284 -0
  19. package/core/Query.ts +503 -85
  20. package/core/RequestContext.ts +24 -0
  21. package/core/RequestLoaders.ts +89 -0
  22. package/core/SchedulerManager.ts +710 -0
  23. package/core/UploadManager.ts +261 -0
  24. package/core/components/UploadComponent.ts +206 -0
  25. package/core/decorators/EntityHooks.ts +190 -0
  26. package/core/decorators/ScheduledTask.ts +83 -0
  27. package/core/events/EntityLifecycleEvents.ts +177 -0
  28. package/core/processors/ImageProcessor.ts +423 -0
  29. package/core/storage/LocalStorageProvider.ts +290 -0
  30. package/core/storage/StorageProvider.ts +112 -0
  31. package/database/DatabaseHelper.ts +183 -58
  32. package/database/index.ts +5 -5
  33. package/database/sqlHelpers.ts +7 -0
  34. package/docs/README.md +149 -0
  35. package/docs/_coverpage.md +36 -0
  36. package/docs/_sidebar.md +23 -0
  37. package/docs/api/core.md +568 -0
  38. package/docs/api/hooks.md +554 -0
  39. package/docs/api/index.md +222 -0
  40. package/docs/api/query.md +678 -0
  41. package/docs/api/service.md +744 -0
  42. package/docs/core-concepts/archetypes.md +512 -0
  43. package/docs/core-concepts/components.md +498 -0
  44. package/docs/core-concepts/entity.md +314 -0
  45. package/docs/core-concepts/hooks.md +683 -0
  46. package/docs/core-concepts/query.md +588 -0
  47. package/docs/core-concepts/services.md +647 -0
  48. package/docs/examples/code-examples.md +425 -0
  49. package/docs/getting-started.md +337 -0
  50. package/docs/index.html +97 -0
  51. package/gql/Generator.ts +58 -35
  52. package/gql/decorators/Upload.ts +176 -0
  53. package/gql/helpers.ts +67 -0
  54. package/gql/index.ts +65 -31
  55. package/gql/types.ts +1 -1
  56. package/index.ts +79 -11
  57. package/package.json +19 -10
  58. package/rest/Generator.ts +3 -0
  59. package/rest/index.ts +22 -0
  60. package/service/Service.ts +1 -1
  61. package/service/ServiceRegistry.ts +10 -6
  62. package/service/index.ts +12 -1
  63. package/tests/bench/insert.bench.ts +59 -0
  64. package/tests/bench/relations.bench.ts +269 -0
  65. package/tests/bench/sorting.bench.ts +415 -0
  66. package/tests/component-hooks.test.ts +1409 -0
  67. package/tests/component.test.ts +338 -0
  68. package/tests/errorHandling.test.ts +155 -0
  69. package/tests/hooks.test.ts +666 -0
  70. package/tests/query-sorting.test.ts +101 -0
  71. package/tests/relations.test.ts +169 -0
  72. package/tests/scheduler.test.ts +724 -0
  73. package/tsconfig.json +35 -34
  74. package/types/graphql.types.ts +87 -0
  75. package/types/hooks.types.ts +141 -0
  76. package/types/scheduler.types.ts +165 -0
  77. package/types/upload.types.ts +184 -0
  78. package/upload/index.ts +140 -0
  79. package/utils/UploadHelper.ts +305 -0
  80. package/utils/cronParser.ts +366 -0
  81. package/utils/errorMessages.ts +151 -0
  82. package/core/Events.ts +0 -0
@@ -0,0 +1,647 @@
1
+ # Service System
2
+
3
+ Services are BunSane's business logic layer that provide a clean separation between your application's logic and data layer. Services extend `BaseService` and can integrate with GraphQL resolvers for API endpoints.
4
+
5
+ ## 🎯 What is a Service?
6
+
7
+ A Service is a class that contains your application's business logic. Services extend `BaseService` and work with the service registry for dependency management.
8
+
9
+ ### Key Features
10
+
11
+ - **Business Logic Organization**: Clean separation of concerns
12
+ - **Type Safety**: Full TypeScript integration with compile-time guarantees
13
+ - **Dependency Injection**: Built-in service registry and dependency management
14
+ - **GraphQL Integration**: Can be used with GraphQL resolvers
15
+ - **Validation**: Input validation and error handling
16
+
17
+ ## 🏗️ Creating Services
18
+
19
+ ### Basic Service Structure
20
+
21
+ ```typescript
22
+ import { BaseService, ServiceRegistry } from 'bunsane';
23
+
24
+ export default class UserService extends BaseService {
25
+ async createUser(userData: { name: string; email: string; username: string }) {
26
+ const userEntity = UserArcheType.fill(userData).createEntity();
27
+ await userEntity.save();
28
+ return await UserArcheType.Unwrap(userEntity);
29
+ }
30
+
31
+ async getUser(args: { id: string }) {
32
+ const entity = await Entity.FindById(args.id);
33
+ if (!entity) return null;
34
+ return await UserArcheType.Unwrap(entity);
35
+ }
36
+
37
+ async updateUser(args: any) {
38
+ const entity = await Entity.FindById(args.id);
39
+ if (!entity) throw new Error('User not found');
40
+
41
+ await UserArcheType.updateEntity(entity, args);
42
+ await entity.save();
43
+ return await UserArcheType.Unwrap(entity);
44
+ }
45
+ }
46
+
47
+ // Services are automatically registered when imported
48
+ // No manual registration needed
49
+ ```
50
+
51
+ ### GraphQL Service with Decorators
52
+
53
+ ```typescript
54
+ import { GraphQLObjectType, GraphQLOperation, GraphQLField, GraphQLFieldTypes } from 'bunsane';
55
+
56
+ const userFields = {
57
+ id: GraphQLFieldTypes.ID_REQUIRED,
58
+ name: GraphQLFieldTypes.STRING_OPTIONAL,
59
+ email: GraphQLFieldTypes.STRING_REQUIRED,
60
+ username: GraphQLFieldTypes.STRING_OPTIONAL
61
+ };
62
+
63
+ const userInputs = {
64
+ createUser: {
65
+ name: GraphQLFieldTypes.STRING_REQUIRED,
66
+ email: GraphQLFieldTypes.STRING_REQUIRED,
67
+ username: GraphQLFieldTypes.STRING_REQUIRED
68
+ },
69
+ getUser: {
70
+ id: GraphQLFieldTypes.ID_REQUIRED
71
+ },
72
+ updateUser: {
73
+ id: GraphQLFieldTypes.ID_REQUIRED,
74
+ name: GraphQLFieldTypes.STRING_OPTIONAL,
75
+ email: GraphQLFieldTypes.STRING_OPTIONAL
76
+ }
77
+ };
78
+
79
+ @GraphQLObjectType({
80
+ name: "User",
81
+ fields: userFields
82
+ })
83
+ export default class UserService extends BaseService {
84
+ @GraphQLOperation({
85
+ type: "Mutation",
86
+ input: userInputs.createUser,
87
+ output: "User"
88
+ })
89
+ async createUser(args: { name: string; email: string; username: string }) {
90
+ const userEntity = UserArcheType.fill(args).createEntity();
91
+ await userEntity.save();
92
+ return await UserArcheType.Unwrap(userEntity);
93
+ }
94
+
95
+ @GraphQLOperation({
96
+ type: "Query",
97
+ input: userInputs.getUser,
98
+ output: "User"
99
+ })
100
+ async getUser(args: { id: string }) {
101
+ const entity = await Entity.FindById(args.id);
102
+ if (!entity) return null;
103
+ return await UserArcheType.Unwrap(entity);
104
+ }
105
+
106
+ @GraphQLOperation({
107
+ type: "Mutation",
108
+ input: userInputs.updateUser,
109
+ output: "User"
110
+ })
111
+ async updateUser(args: { id: string; name?: string; email?: string }) {
112
+ const entity = await Entity.FindById(args.id);
113
+ if (!entity) throw new Error('User not found');
114
+
115
+ await UserArcheType.updateEntity(entity, args);
116
+ await entity.save();
117
+ return await UserArcheType.Unwrap(entity);
118
+ }
119
+
120
+ @GraphQLField({ type: "User", field: "id" })
121
+ idResolver(parent: Entity) {
122
+ return parent.id;
123
+ }
124
+
125
+ @GraphQLField({ type: "User", field: "name" })
126
+ async nameResolver(parent: Entity) {
127
+ const profile = await parent.get(UserProfile);
128
+ return profile?.name ?? "";
129
+ }
130
+
131
+ @GraphQLField({ type: "User", field: "email" })
132
+ async emailResolver(parent: Entity) {
133
+ const profile = await parent.get(UserProfile);
134
+ return profile?.email ?? "";
135
+ }
136
+ }
137
+ ```
138
+
139
+ ## 📊 GraphQL Type Definitions
140
+
141
+ ### Field Types
142
+
143
+ BunSane provides predefined GraphQL field types for common use cases:
144
+
145
+ ```typescript
146
+ import { GraphQLFieldTypes } from 'bunsane';
147
+
148
+ // Available field types
149
+ const fieldTypes = {
150
+ // ID fields
151
+ ID_REQUIRED: GraphQLFieldTypes.ID_REQUIRED, // ID!
152
+ ID_OPTIONAL: GraphQLFieldTypes.ID_OPTIONAL, // ID
153
+
154
+ // String fields
155
+ STRING_REQUIRED: GraphQLFieldTypes.STRING_REQUIRED, // String!
156
+ STRING_OPTIONAL: GraphQLFieldTypes.STRING_OPTIONAL, // String
157
+
158
+ // Numeric fields
159
+ INT_REQUIRED: GraphQLFieldTypes.INT_REQUIRED, // Int!
160
+ INT_OPTIONAL: GraphQLFieldTypes.INT_OPTIONAL, // Int
161
+ FLOAT_REQUIRED: GraphQLFieldTypes.FLOAT_REQUIRED, // Float!
162
+ FLOAT_OPTIONAL: GraphQLFieldTypes.FLOAT_OPTIONAL, // Float
163
+
164
+ // Boolean fields
165
+ BOOLEAN_REQUIRED: GraphQLFieldTypes.BOOLEAN_REQUIRED, // Boolean!
166
+ BOOLEAN_OPTIONAL: GraphQLFieldTypes.BOOLEAN_OPTIONAL, // Boolean
167
+
168
+ // Custom types
169
+ JSON: GraphQLFieldTypes.JSON, // JSON (custom scalar)
170
+ DATE: GraphQLFieldTypes.DATE, // Date (custom scalar)
171
+ };
172
+ ```
173
+
174
+ ### Complex Type Definitions
175
+
176
+ ```typescript
177
+ export default class BlogService extends BaseService {
178
+ // Post type definition
179
+ postFields = {
180
+ id: GraphQLFieldTypes.ID_REQUIRED,
181
+ title: GraphQLFieldTypes.STRING_REQUIRED,
182
+ content: GraphQLFieldTypes.STRING_REQUIRED,
183
+ author: 'User', // Reference to another type
184
+ tags: '[String]', // Array of strings
185
+ publishedAt: GraphQLFieldTypes.DATE,
186
+ stats: 'PostStats' // Nested object
187
+ };
188
+
189
+ // Stats nested type
190
+ postStatsFields = {
191
+ viewCount: GraphQLFieldTypes.INT_REQUIRED,
192
+ likeCount: GraphQLFieldTypes.INT_REQUIRED,
193
+ commentCount: GraphQLFieldTypes.INT_REQUIRED
194
+ };
195
+
196
+ // Input definitions
197
+ postInputs = {
198
+ createPost: {
199
+ title: GraphQLFieldTypes.STRING_REQUIRED,
200
+ content: GraphQLFieldTypes.STRING_REQUIRED,
201
+ tags: '[String]'
202
+ },
203
+ updatePost: {
204
+ id: GraphQLFieldTypes.ID_REQUIRED,
205
+ title: GraphQLFieldTypes.STRING_OPTIONAL,
206
+ content: GraphQLFieldTypes.STRING_OPTIONAL,
207
+ tags: '[String]'
208
+ }
209
+ };
210
+ }
211
+ ```
212
+
213
+ ## 🔧 GraphQL Resolvers
214
+
215
+ ### Query Resolvers
216
+
217
+ ```typescript
218
+ export default class UserService extends BaseService {
219
+ // Simple queries
220
+ async getUser(args: { id: string }) {
221
+ const entity = await Entity.FindById(args.id);
222
+ if (!entity) return null;
223
+ return await UserArcheType.Unwrap(entity);
224
+ }
225
+
226
+ async getUsers(args: { limit?: number; offset?: number }) {
227
+ const query = new Query()
228
+ .with(UserProfile)
229
+ .limit(args.limit || 10)
230
+ .offset(args.offset || 0);
231
+
232
+ const entities = await query.exec();
233
+ return await Promise.all(
234
+ entities.map(entity => UserArcheType.Unwrap(entity))
235
+ );
236
+ }
237
+
238
+ // Complex queries with filtering
239
+ async searchUsers(args: { query: string; role?: string }) {
240
+ const searchQuery = new Query().with(UserProfile);
241
+
242
+ if (args.role) {
243
+ searchQuery.with(UserRole).filter('role', args.role);
244
+ }
245
+
246
+ // Add text search if supported
247
+ if (args.query) {
248
+ searchQuery.filter('name', `%${args.query}%`, 'LIKE');
249
+ }
250
+
251
+ const entities = await searchQuery.exec();
252
+ return await Promise.all(
253
+ entities.map(entity => UserArcheType.Unwrap(entity))
254
+ );
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Mutation Resolvers
260
+
261
+ ```typescript
262
+ export default class UserService extends BaseService {
263
+ async createUser(args: { input: any }) {
264
+ // Validate input
265
+ if (!args.input.email || !args.input.name) {
266
+ throw new Error('Name and email are required');
267
+ }
268
+
269
+ // Check for existing user
270
+ const existingQuery = new Query()
271
+ .with(UserProfile)
272
+ .filter('email', args.input.email);
273
+
274
+ const existing = await existingQuery.exec();
275
+ if (existing.length > 0) {
276
+ throw new Error('User with this email already exists');
277
+ }
278
+
279
+ // Create new user
280
+ const userEntity = UserArcheType.fill({
281
+ userProfile: args.input,
282
+ userPreferences: { theme: 'light', notifications: true },
283
+ userStats: { loginCount: 0, lastLogin: new Date() }
284
+ }).createEntity();
285
+
286
+ await userEntity.save();
287
+ return await UserArcheType.Unwrap(userEntity);
288
+ }
289
+
290
+ async updateUser(args: { id: string; input: any }) {
291
+ const entity = await Entity.FindById(args.id);
292
+ if (!entity) {
293
+ throw new Error('User not found');
294
+ }
295
+
296
+ // Update only provided fields
297
+ const updates: any = {};
298
+ if (args.input.name) updates.userProfile = { name: args.input.name };
299
+ if (args.input.email) updates.userProfile = { ...updates.userProfile, email: args.input.email };
300
+
301
+ await UserArcheType.updateEntity(entity, updates);
302
+ await entity.save();
303
+
304
+ return await UserArcheType.Unwrap(entity);
305
+ }
306
+
307
+ async deleteUser(args: { id: string }) {
308
+ const entity = await Entity.FindById(args.id);
309
+ if (!entity) {
310
+ throw new Error('User not found');
311
+ }
312
+
313
+ await entity.delete(true); // Force delete
314
+ return { success: true, message: 'User deleted successfully' };
315
+ }
316
+ }
317
+ ```
318
+
319
+ ## 🔗 Service Relationships
320
+
321
+ ### Service Dependencies
322
+
323
+ ```typescript
324
+ export default class PostService extends BaseService {
325
+ private userService: UserService;
326
+
327
+ async initialize(): Promise<void> {
328
+ await super.initialize();
329
+ // Get service instance from registry
330
+ this.userService = ServiceRegistry.get(UserService);
331
+ }
332
+
333
+ async createPost(args: { input: any }) {
334
+ // Verify author exists
335
+ const author = await this.userService.getUser({ id: args.input.authorId });
336
+ if (!author) {
337
+ throw new Error('Author not found');
338
+ }
339
+
340
+ const postEntity = BlogPostArcheType.fill(args.input).createEntity();
341
+ await postEntity.save();
342
+
343
+ return await BlogPostArcheType.Unwrap(postEntity);
344
+ }
345
+
346
+ async getPostWithAuthor(args: { id: string }) {
347
+ const postEntity = await Entity.FindById(args.id);
348
+ if (!postEntity) return null;
349
+
350
+ const post = await BlogPostArcheType.Unwrap(postEntity);
351
+
352
+ // Fetch author details
353
+ if (post.authorId) {
354
+ post.author = await this.userService.getUser({ id: post.authorId });
355
+ }
356
+
357
+ return post;
358
+ }
359
+ }
360
+ ```
361
+
362
+ ### Cross-Service Queries
363
+
364
+ ```typescript
365
+ export default class AnalyticsService extends BaseService {
366
+ private userService: UserService;
367
+ private postService: PostService;
368
+
369
+ async initialize(): Promise<void> {
370
+ await super.initialize();
371
+ this.userService = ServiceRegistry.get(UserService);
372
+ this.postService = ServiceRegistry.get(PostService);
373
+ }
374
+
375
+ async getUserStats(args: { userId: string }) {
376
+ const user = await this.userService.getUser({ id: args.userId });
377
+ if (!user) return null;
378
+
379
+ // Get user's posts
380
+ const userPosts = await new Query()
381
+ .with(BlogPost)
382
+ .filter('authorId', args.userId)
383
+ .exec();
384
+
385
+ // Calculate stats
386
+ const totalPosts = userPosts.length;
387
+ const totalViews = userPosts.reduce((sum, post) => {
388
+ const stats = post.get(PostStats);
389
+ return sum + (stats?.viewCount || 0);
390
+ }, 0);
391
+
392
+ return {
393
+ user: user,
394
+ stats: {
395
+ totalPosts,
396
+ totalViews,
397
+ averageViews: totalPosts > 0 ? totalViews / totalPosts : 0
398
+ }
399
+ };
400
+ }
401
+ }
402
+ ```
403
+
404
+ ## 🎭 Advanced Service Patterns
405
+
406
+ ### Service with Middleware
407
+
408
+ ```typescript
409
+ export default class SecureService extends BaseService {
410
+ // Middleware for authentication
411
+ async authenticate(context: any, next: Function) {
412
+ const token = context.request.headers.authorization;
413
+ if (!token) {
414
+ throw new Error('Authentication required');
415
+ }
416
+
417
+ // Verify token and set user context
418
+ const user = await this.verifyToken(token);
419
+ context.user = user;
420
+
421
+ return next();
422
+ }
423
+
424
+ // Middleware for authorization
425
+ async authorize(context: any, next: Function) {
426
+ if (!context.user.isAdmin) {
427
+ throw new Error('Admin access required');
428
+ }
429
+
430
+ return next();
431
+ }
432
+
433
+ // Protected resolver
434
+ async adminOnlyAction(args: any, context: any) {
435
+ // This resolver is automatically protected by middleware
436
+ return { success: true, user: context.user };
437
+ }
438
+ }
439
+ ```
440
+
441
+ ### Service with Caching
442
+
443
+ ```typescript
444
+ export default class CachedUserService extends BaseService {
445
+ private cache = new Map<string, any>();
446
+
447
+ async getUser(args: { id: string }) {
448
+ // Check cache first
449
+ const cacheKey = `user:${args.id}`;
450
+ if (this.cache.has(cacheKey)) {
451
+ return this.cache.get(cacheKey);
452
+ }
453
+
454
+ // Fetch from database
455
+ const entity = await Entity.FindById(args.id);
456
+ if (!entity) return null;
457
+
458
+ const user = await UserArcheType.Unwrap(entity);
459
+
460
+ // Cache for 5 minutes
461
+ this.cache.set(cacheKey, user);
462
+ setTimeout(() => {
463
+ this.cache.delete(cacheKey);
464
+ }, 5 * 60 * 1000);
465
+
466
+ return user;
467
+ }
468
+
469
+ // Invalidate cache on updates
470
+ async updateUser(args: any) {
471
+ const result = await super.updateUser(args);
472
+
473
+ // Clear cache
474
+ const cacheKey = `user:${args.id}`;
475
+ this.cache.delete(cacheKey);
476
+
477
+ return result;
478
+ }
479
+ }
480
+ ```
481
+
482
+ ### Batch Operations Service
483
+
484
+ ```typescript
485
+ export default class BatchService extends BaseService {
486
+ async createUsers(args: { inputs: any[] }) {
487
+ const results = [];
488
+ const errors = [];
489
+
490
+ for (const input of args.inputs) {
491
+ try {
492
+ const user = await this.createUser({ input });
493
+ results.push(user);
494
+ } catch (error) {
495
+ errors.push({
496
+ input,
497
+ error: error.message
498
+ });
499
+ }
500
+ }
501
+
502
+ return {
503
+ results,
504
+ errors,
505
+ success: errors.length === 0
506
+ };
507
+ }
508
+
509
+ async bulkUpdateUsers(args: { updates: Array<{ id: string; input: any }> }) {
510
+ const results = [];
511
+ const errors = [];
512
+
513
+ // Process in batches to avoid overwhelming the database
514
+ const batchSize = 10;
515
+ for (let i = 0; i < args.updates.length; i += batchSize) {
516
+ const batch = args.updates.slice(i, i + batchSize);
517
+
518
+ const batchPromises = batch.map(async (update) => {
519
+ try {
520
+ const user = await this.updateUser({
521
+ id: update.id,
522
+ input: update.input
523
+ });
524
+ return { success: true, user };
525
+ } catch (error) {
526
+ return { success: false, error: error.message, id: update.id };
527
+ }
528
+ });
529
+
530
+ const batchResults = await Promise.all(batchPromises);
531
+ results.push(...batchResults);
532
+ }
533
+
534
+ return {
535
+ results,
536
+ errors: results.filter(r => !r.success),
537
+ success: results.every(r => r.success)
538
+ };
539
+ }
540
+ }
541
+ ```
542
+
543
+ ## 🔧 Service Registry
544
+
545
+ ### Automatic Service Registration
546
+
547
+ Services are automatically discovered and registered when:
548
+ 1. They extend `BaseService`
549
+ 2. They are imported in your application entry point
550
+ 3. The application starts via `App.start()`
551
+
552
+ ```typescript
553
+ // app.ts
554
+ import { App } from 'bunsane';
555
+ import UserService from './services/UserService';
556
+ import PostService from './services/PostService';
557
+
558
+ const app = new App();
559
+
560
+ // Services are automatically registered when imported
561
+ // No manual registration required
562
+ app.start();
563
+ ```
564
+
565
+ ### Accessing Services
566
+
567
+ ```typescript
568
+ // Get service instance
569
+ const userService = ServiceRegistry.get(UserService);
570
+ const postService = ServiceRegistry.get(PostService);
571
+
572
+ // Use services in your application
573
+ const user = await userService.getUser({ id: '123' });
574
+ ```
575
+
576
+ ## 📊 Best Practices
577
+
578
+ ### Service Design
579
+
580
+ - **Single Responsibility**: Each service should handle one domain area
581
+ - **Dependency Injection**: Use constructor injection for dependencies
582
+ - **Error Handling**: Provide clear, actionable error messages
583
+ - **Validation**: Validate inputs before processing
584
+ - **Documentation**: Document complex business logic
585
+
586
+ ### Performance Considerations
587
+
588
+ - **Caching**: Implement caching for frequently accessed data
589
+ - **Batch Operations**: Support bulk operations where possible
590
+ - **Lazy Loading**: Only fetch related data when needed
591
+ - **Query Optimization**: Use efficient database queries
592
+ - **Connection Pooling**: Reuse database connections
593
+
594
+ ### Error Handling
595
+
596
+ ```typescript
597
+ export default class RobustService extends BaseService {
598
+ async safeOperation(args: any) {
599
+ try {
600
+ // Validate input
601
+ this.validateInput(args);
602
+
603
+ // Perform operation
604
+ const result = await this.performOperation(args);
605
+
606
+ // Log success
607
+ logger.info('Operation completed successfully', { args, result });
608
+
609
+ return result;
610
+
611
+ } catch (error) {
612
+ // Log error with context
613
+ logger.error('Operation failed', {
614
+ args,
615
+ error: error.message,
616
+ stack: error.stack
617
+ });
618
+
619
+ // Return user-friendly error
620
+ throw new Error('Operation failed. Please try again later.');
621
+ }
622
+ }
623
+
624
+ private validateInput(args: any) {
625
+ if (!args.id) {
626
+ throw new Error('ID is required');
627
+ }
628
+
629
+ if (args.value && args.value < 0) {
630
+ throw new Error('Value must be positive');
631
+ }
632
+ }
633
+ }
634
+ ```
635
+
636
+ ## 🚀 What's Next?
637
+
638
+ Now that you understand Services, let's explore:
639
+
640
+ - **[Query System](query.md)** - Efficient data retrieval
641
+ - **[Lifecycle Hooks](hooks.md)** - Business logic integration
642
+ - **[Entity System](entity.md)** - How entities work with services
643
+ - **[Advanced Features](../advanced/)** - Power user capabilities
644
+
645
+ ---
646
+
647
+ *Ready to build APIs with services? Let's look at the [Query System](query.md) next!* 🚀