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,683 @@
1
+ # Entity Lifecycle Hooks
2
+
3
+ Entity Lifecycle Hooks provide a powerful way to execute business logic at specific points during an entity's lifecycle. They enable you to react to entity creation, updates, and deletion, as well as component changes, making it easy to implement cross-cutting concerns like auditing, notifications, and data validation.
4
+
5
+ ## 🎯 What are Lifecycle Hooks?
6
+
7
+ Lifecycle hooks are functions that automatically execute when specific events occur in an entity's lifecycle. They allow you to:
8
+
9
+ - **Validate data** before saving
10
+ - **Send notifications** when entities change
11
+ - **Update related entities** automatically
12
+ - **Log audit trails** for compliance
13
+ - **Enforce business rules** across your application
14
+ - **Trigger background tasks** based on entity changes
15
+
16
+ ### Hook Types
17
+
18
+ - **Entity Hooks**: Triggered by entity lifecycle events (create, update, delete)
19
+ - **Component Hooks**: Triggered by component changes on entities
20
+ - **Component-Targeted Hooks**: Entity hooks with fine-grained component-based filtering
21
+ - **Lifecycle Hooks**: Hooks that listen to all lifecycle events
22
+
23
+ ## 🏗️ Basic Hook Implementation
24
+
25
+ ### Entity Lifecycle Hooks
26
+
27
+ ```typescript
28
+ import { EntityHook, registerDecoratedHooks } from 'bunsane/decorators/EntityHooks';
29
+ import { EntityCreatedEvent, EntityUpdatedEvent, EntityDeletedEvent } from 'bunsane/events/EntityLifecycleEvents';
30
+
31
+ export class UserService {
32
+ @EntityHook('entity.created')
33
+ async onUserCreated(event: EntityCreatedEvent) {
34
+ console.log('New user created:', event.getEntity().id);
35
+
36
+ // Send welcome email
37
+ await this.sendWelcomeEmail(event.getEntity());
38
+
39
+ // Create user stats
40
+ // Implementation here...
41
+ }
42
+
43
+ @EntityHook('entity.updated')
44
+ async onUserUpdated(event: EntityUpdatedEvent) {
45
+ console.log('User updated:', event.getEntity().id);
46
+
47
+ // Log the change
48
+ await this.logUserChange(event);
49
+ }
50
+
51
+ @EntityHook('entity.deleted')
52
+ async onUserDeleted(event: EntityDeletedEvent) {
53
+ console.log('User deleted:', event.getEntity().id);
54
+
55
+ // Clean up related data
56
+ await this.cleanupUserData(event.getEntity().id);
57
+ }
58
+
59
+ // Register hooks when service is instantiated
60
+ constructor() {
61
+ registerDecoratedHooks(this);
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### Component Lifecycle Hooks
67
+
68
+ ```typescript
69
+ import { ComponentHook } from 'bunsane/decorators/EntityHooks';
70
+ import { ComponentAddedEvent, ComponentUpdatedEvent, ComponentRemovedEvent } from 'bunsane/events/EntityLifecycleEvents';
71
+
72
+ export class UserService {
73
+ @ComponentHook('component.added')
74
+ async onComponentAdded(event: ComponentAddedEvent) {
75
+ if (event.getComponentType() === 'EmailComponent') {
76
+ console.log('Email component added to entity:', event.getEntity().id);
77
+
78
+ // Validate email format
79
+ const emailComponent = event.getComponent();
80
+ if (!this.isValidEmail(emailComponent.value)) {
81
+ throw new Error('Invalid email format');
82
+ }
83
+ }
84
+ }
85
+
86
+ @ComponentHook('component.updated')
87
+ async onComponentUpdated(event: ComponentUpdatedEvent) {
88
+ if (event.getComponentType() === 'EmailComponent') {
89
+ console.log('Email component updated');
90
+
91
+ // Check for email changes
92
+ const oldEmail = event.getOldData()?.value;
93
+ const newEmail = event.getNewData()?.value;
94
+
95
+ if (oldEmail !== newEmail) {
96
+ // Send email verification
97
+ await this.sendEmailVerification(newEmail);
98
+ }
99
+ }
100
+ }
101
+
102
+ @ComponentHook('component.removed')
103
+ async onComponentRemoved(event: ComponentRemovedEvent) {
104
+ if (event.getComponentType() === 'ProfileComponent') {
105
+ console.log('Profile component removed');
106
+
107
+ // Handle profile removal
108
+ await this.handleProfileRemoval(event.getEntity().id);
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## 🎭 Component-Targeted Hooks
115
+
116
+ ### Archetype-Targeted Hooks
117
+
118
+ ```typescript
119
+ import { ComponentTargetHook } from 'bunsane/decorators/EntityHooks';
120
+
121
+ export class ContentService {
122
+ @ComponentTargetHook('entity.created', {
123
+ includeComponents: [BlogPost, AuthorComponent]
124
+ })
125
+ async onBlogPostCreated(event: EntityCreatedEvent) {
126
+ console.log('New blog post created');
127
+
128
+ // Extract post data
129
+ const postData = await event.getEntity().get(BlogPost);
130
+ const authorData = await event.getEntity().get(AuthorComponent);
131
+
132
+ // Notify followers
133
+ await this.notifyFollowers(authorData.authorId, {
134
+ type: 'new_post',
135
+ postId: event.getEntity().id,
136
+ title: postData.title
137
+ });
138
+
139
+ // Update author stats
140
+ await this.incrementAuthorPostCount(authorData.authorId);
141
+ }
142
+
143
+ @ComponentTargetHook('entity.updated', {
144
+ includeComponents: [PublishedStatus],
145
+ requireAllIncluded: true
146
+ })
147
+ async onContentPublished(event: EntityUpdatedEvent) {
148
+ const entity = event.getEntity();
149
+
150
+ // Check if this is a publish event
151
+ if (entity.has(PublishedStatus)) {
152
+ const status = await entity.get(PublishedStatus);
153
+
154
+ if (status.isPublished && !status.wasPublished) {
155
+ // Content was just published
156
+ await this.onContentPublished(entity);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### Conditional Component Targeting
164
+
165
+ ```typescript
166
+ export class NotificationService {
167
+ @ComponentTargetHook('entity.created', {
168
+ includeComponents: [UserProfile],
169
+ excludeComponents: [GuestUser]
170
+ })
171
+ async onRegularUserCreated(event: EntityCreatedEvent) {
172
+ // Only triggered for regular users, not guests
173
+ await this.sendWelcomeEmail(event.getEntity());
174
+ }
175
+
176
+ @ComponentTargetHook('entity.updated', {
177
+ includeComponents: [UserProfile, EmailComponent],
178
+ requireAllIncluded: true
179
+ })
180
+ async onUserEmailChanged(event: EntityUpdatedEvent) {
181
+ // Only triggered when both UserProfile and EmailComponent are present
182
+ const profile = await event.getEntity().get(UserProfile);
183
+ const email = await event.getEntity().get(EmailComponent);
184
+
185
+ await this.sendEmailChangeNotification(profile.email, email.value);
186
+ }
187
+ }
188
+ ```
189
+
190
+ ## 🔧 Hook Configuration and Options
191
+
192
+ ### Hook Priority and Execution Order
193
+
194
+ ```typescript
195
+ export class OrderedHookService {
196
+ @EntityHook('entity.created', { priority: 1 })
197
+ async validateEntity(event: EntityCreatedEvent) {
198
+ // High priority validation (executes first)
199
+ if (!this.isValidEntity(event.getEntity())) {
200
+ throw new Error('Entity validation failed');
201
+ }
202
+ }
203
+
204
+ @EntityHook('entity.created', { priority: 10 })
205
+ async sendWelcomeEmail(event: EntityCreatedEvent) {
206
+ // Lower priority - runs after validation
207
+ await this.sendEmail(event.getEntity());
208
+ }
209
+
210
+ @EntityHook('entity.created', { priority: 5 })
211
+ async createDefaultComponents(event: EntityCreatedEvent) {
212
+ // Medium priority
213
+ await this.addDefaultComponents(event.getEntity());
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### Async Hooks and Timeouts
219
+
220
+ ```typescript
221
+ export class AsyncHookService {
222
+ @EntityHook('entity.created', { async: true, timeout: 5000 })
223
+ async sendWelcomeEmailAsync(event: EntityCreatedEvent) {
224
+ // This hook runs asynchronously with 5 second timeout
225
+ try {
226
+ await this.sendWelcomeEmail(event.getEntity());
227
+ } catch (error) {
228
+ console.error('Failed to send welcome email:', error);
229
+ }
230
+ }
231
+
232
+ @ComponentHook('component.updated', { timeout: 2000 })
233
+ async validateComponentUpdate(event: ComponentUpdatedEvent) {
234
+ // 2 second timeout for validation
235
+ const isValid = await this.validateUpdate(event);
236
+ if (!isValid) {
237
+ throw new Error('Component update validation failed');
238
+ }
239
+ }
240
+ }
241
+ ```
242
+
243
+ ### Hook Filtering
244
+
245
+ ```typescript
246
+ export class FilteredHookService {
247
+ @EntityHook('entity.created', {
248
+ filter: (event) => event.getEntity().has(PremiumFeature)
249
+ })
250
+ async onPremiumUserCreated(event: EntityCreatedEvent) {
251
+ // Only executes for premium users
252
+ await this.setupPremiumFeatures(event.getEntity());
253
+ }
254
+
255
+ @ComponentHook('component.updated', {
256
+ filter: (event) => {
257
+ const oldData = event.getOldData();
258
+ const newData = event.getNewData();
259
+ return oldData?.status !== newData?.status; // Only status changes
260
+ }
261
+ })
262
+ async onStatusChanged(event: ComponentUpdatedEvent) {
263
+ // Only executes when status actually changes
264
+ await this.handleStatusChange(event);
265
+ }
266
+ }
267
+ ```
268
+
269
+ ## 🎯 Real-World Examples
270
+
271
+ ### Audit Logging (from examples/hooks/audit-logger.ts)
272
+
273
+ ```typescript
274
+ import { EntityHook, ComponentHook } from 'bunsane/decorators/EntityHooks';
275
+
276
+ export class AuditLogger {
277
+ @EntityHook("entity.created")
278
+ async handleEntityCreated(event: EntityCreatedEvent) {
279
+ const entry = {
280
+ id: this.generateId(),
281
+ timestamp: new Date(),
282
+ action: 'create',
283
+ entityId: event.getEntity().id,
284
+ entityType: this.getEntityType(event.getEntity()),
285
+ userId: this.getCurrentUserId(),
286
+ newData: await this.extractEntityData(event.getEntity())
287
+ };
288
+
289
+ await this.storeLogEntry(entry);
290
+ }
291
+
292
+ @EntityHook("entity.updated")
293
+ async handleEntityUpdated(event: EntityUpdatedEvent) {
294
+ const entry = {
295
+ id: this.generateId(),
296
+ timestamp: new Date(),
297
+ action: 'update',
298
+ entityId: event.getEntity().id,
299
+ changedComponents: event.getChangedComponents()
300
+ };
301
+
302
+ await this.storeLogEntry(entry);
303
+ }
304
+
305
+ @ComponentHook("component.added")
306
+ async handleComponentAdded(event: ComponentAddedEvent) {
307
+ const entry = {
308
+ id: this.generateId(),
309
+ action: 'add_component',
310
+ entityId: event.getEntity().id,
311
+ componentType: event.getComponentType(),
312
+ newData: event.getComponent().data()
313
+ };
314
+
315
+ await this.storeLogEntry(entry);
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### User Service Hooks (from UserService.ts)
321
+
322
+ ```typescript
323
+ export class UserService extends BaseService {
324
+ @ComponentTargetHook("entity.created", {
325
+ includeComponents: [UserTag, EmailComponent]
326
+ })
327
+ async onUserCreate(event: EntityCreatedEvent) {
328
+ const emailComp = await event.entity.get(EmailComponent);
329
+ logger.info(`New user created with email: ${emailComp?.value}`);
330
+ // Here you could add logic to send a welcome email, etc.
331
+ }
332
+ }
333
+ ```
334
+
335
+ ## 🔄 Hook Registration and Management
336
+
337
+ ### Automatic Hook Registration
338
+
339
+ ```typescript
340
+ import { registerDecoratedHooks } from 'bunsane/decorators/EntityHooks';
341
+
342
+ export class MyService {
343
+ @EntityHook('entity.created')
344
+ async handleCreation(event: EntityCreatedEvent) {
345
+ // Hook implementation
346
+ }
347
+
348
+ constructor() {
349
+ // Automatically register all decorated hooks
350
+ registerDecoratedHooks(this);
351
+ }
352
+ }
353
+ ```
354
+
355
+ ### Manual Hook Registration
356
+
357
+ ```typescript
358
+ import EntityHookManager from 'bunsane/core/EntityHookManager';
359
+
360
+ class CustomNotificationService {
361
+ async sendNotification(entityId: string, message: string) {
362
+ // Implementation
363
+ }
364
+
365
+ registerHooks() {
366
+ // Register entity hooks
367
+ EntityHookManager.registerEntityHook(
368
+ 'entity.created',
369
+ async (event: EntityCreatedEvent) => {
370
+ if (event.getEntity().has(UserProfile)) {
371
+ await this.sendNotification(
372
+ event.getEntity().id,
373
+ 'Welcome to our platform!'
374
+ );
375
+ }
376
+ },
377
+ { priority: 5 }
378
+ );
379
+
380
+ // Register component hooks
381
+ EntityHookManager.registerComponentHook(
382
+ 'component.updated',
383
+ async (event: ComponentUpdatedEvent) => {
384
+ if (event.getComponentType() === 'UserProfile') {
385
+ await this.sendNotification(
386
+ event.getEntity().id,
387
+ 'Your profile has been updated'
388
+ );
389
+ }
390
+ }
391
+ );
392
+ }
393
+ }
394
+ ```
395
+
396
+ ## 🎯 Advanced Hook Patterns
397
+
398
+ ### Batch Event Processing
399
+
400
+ ```typescript
401
+ export class BatchProcessor {
402
+ private eventBuffer: LifecycleEvent[] = [];
403
+ private processingTimer: NodeJS.Timeout | null = null;
404
+
405
+ @EntityHook('entity.created', { async: true })
406
+ async bufferEvent(event: EntityCreatedEvent) {
407
+ this.eventBuffer.push(event);
408
+
409
+ // Process in batches of 10 or after 5 seconds
410
+ if (this.eventBuffer.length >= 10) {
411
+ await this.processBatch();
412
+ } else if (!this.processingTimer) {
413
+ this.processingTimer = setTimeout(() => this.processBatch(), 5000);
414
+ }
415
+ }
416
+
417
+ private async processBatch() {
418
+ if (this.processingTimer) {
419
+ clearTimeout(this.processingTimer);
420
+ this.processingTimer = null;
421
+ }
422
+
423
+ const events = [...this.eventBuffer];
424
+ this.eventBuffer = [];
425
+
426
+ // Process events in batch
427
+ await this.bulkProcessEvents(events);
428
+ }
429
+ }
430
+ ```
431
+
432
+ ### Saga Pattern with Hooks
433
+
434
+ ```typescript
435
+ export class OrderSagaService {
436
+ private sagas = new Map<string, SagaState>();
437
+
438
+ @EntityHook('entity.created')
439
+ async startOrderSaga(event: EntityCreatedEvent) {
440
+ if (event.getEntity().has(OrderComponent)) {
441
+ const sagaId = `order-${event.getEntity().id}`;
442
+
443
+ this.sagas.set(sagaId, {
444
+ id: sagaId,
445
+ steps: ['validate', 'charge', 'ship', 'complete'],
446
+ currentStep: 0,
447
+ entityId: event.getEntity().id
448
+ });
449
+
450
+ await this.executeSagaStep(sagaId);
451
+ }
452
+ }
453
+
454
+ @EntityHook('entity.updated')
455
+ async continueOrderSaga(event: EntityUpdatedEvent) {
456
+ const sagaId = `order-${event.getEntity().id}`;
457
+ const saga = this.sagas.get(sagaId);
458
+
459
+ if (saga && saga.currentStep < saga.steps.length) {
460
+ await this.executeSagaStep(sagaId);
461
+ }
462
+ }
463
+
464
+ private async executeSagaStep(sagaId: string) {
465
+ const saga = this.sagas.get(sagaId);
466
+ if (!saga) return;
467
+
468
+ const step = saga.steps[saga.currentStep];
469
+ try {
470
+ await this.executeStep(step, saga.entityId);
471
+ saga.currentStep++;
472
+
473
+ if (saga.currentStep >= saga.steps.length) {
474
+ // Saga completed
475
+ this.sagas.delete(sagaId);
476
+ }
477
+ } catch (error) {
478
+ // Saga failed - execute compensation
479
+ await this.compensateSaga(saga);
480
+ this.sagas.delete(sagaId);
481
+ }
482
+ }
483
+ }
484
+ ```
485
+
486
+ ## ⚡ Performance Optimization
487
+
488
+ ### Efficient Hook Execution
489
+
490
+ ```typescript
491
+ export class EfficientHookService {
492
+ private cache = new Map<string, any>();
493
+
494
+ @EntityHook('entity.created')
495
+ async onEntityCreated(event: EntityCreatedEvent) {
496
+ // Cache expensive operations
497
+ const cacheKey = `entity:${event.getEntity().id}`;
498
+ if (this.cache.has(cacheKey)) {
499
+ return; // Already processed
500
+ }
501
+
502
+ // Perform expensive operation
503
+ await this.expensiveOperation(event.getEntity());
504
+
505
+ // Cache result
506
+ this.cache.set(cacheKey, true);
507
+
508
+ // Clean up cache periodically
509
+ if (this.cache.size > 1000) {
510
+ this.clearOldCacheEntries();
511
+ }
512
+ }
513
+
514
+ @ComponentTargetHook('entity.updated', {
515
+ includeComponents: [FrequentlyUpdatedComponent],
516
+ requireAllIncluded: true
517
+ })
518
+ async onFrequentUpdate(event: EntityUpdatedEvent) {
519
+ // Use component targeting to avoid unnecessary executions
520
+ await this.handleFrequentUpdate(event.getEntity());
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### Hook Metrics and Monitoring
526
+
527
+ ```typescript
528
+ export class MonitoredHookService {
529
+ private metrics = new Map<string, number[]>();
530
+
531
+ @EntityHook('entity.created')
532
+ async monitoredHook(event: EntityCreatedEvent) {
533
+ const startTime = performance.now();
534
+
535
+ try {
536
+ await this.doWork(event);
537
+ } finally {
538
+ const duration = performance.now() - startTime;
539
+ this.recordMetric('entity.created', duration);
540
+ }
541
+ }
542
+
543
+ private recordMetric(hookName: string, duration: number) {
544
+ if (!this.metrics.has(hookName)) {
545
+ this.metrics.set(hookName, []);
546
+ }
547
+
548
+ const timings = this.metrics.get(hookName)!;
549
+ timings.push(duration);
550
+
551
+ // Keep only last 100 measurements
552
+ if (timings.length > 100) {
553
+ timings.shift();
554
+ }
555
+ }
556
+
557
+ getMetrics() {
558
+ const result: Record<string, any> = {};
559
+
560
+ for (const [hookName, timings] of this.metrics) {
561
+ const avg = timings.reduce((a, b) => a + b, 0) / timings.length;
562
+ const max = Math.max(...timings);
563
+ const min = Math.min(...timings);
564
+
565
+ result[hookName] = { avg, max, min, count: timings.length };
566
+ }
567
+
568
+ return result;
569
+ }
570
+ }
571
+ ```
572
+
573
+ ## 🔧 Best Practices
574
+
575
+ ### Error Handling in Hooks
576
+
577
+ ```typescript
578
+ export class RobustHookService {
579
+ @EntityHook('entity.created')
580
+ async onEntityCreated(event: EntityCreatedEvent) {
581
+ try {
582
+ await this.processEntityCreation(event);
583
+ } catch (error) {
584
+ // Log error but don't prevent entity creation
585
+ console.error('Hook processing failed:', error);
586
+
587
+ // Optionally send to error tracking service
588
+ await this.reportError(error, {
589
+ hook: 'entity.created',
590
+ entityId: event.getEntity().id
591
+ });
592
+ }
593
+ }
594
+
595
+ @EntityHook('entity.created')
596
+ async criticalValidation(event: EntityCreatedEvent) {
597
+ // For critical validations, let errors propagate
598
+ // This will prevent entity creation if validation fails
599
+ if (!this.isValidEntity(event.getEntity())) {
600
+ throw new Error('Critical validation failed - entity creation blocked');
601
+ }
602
+ }
603
+ }
604
+ ```
605
+
606
+ ### Hook Testing
607
+
608
+ ```typescript
609
+ import { describe, test, expect, beforeEach } from 'bun:test';
610
+ import { Entity, EntityCreatedEvent } from 'bunsane';
611
+
612
+ describe('User Creation Hooks', () => {
613
+ let hookService: UserService;
614
+ let mockEmailService: any;
615
+
616
+ beforeEach(() => {
617
+ mockEmailService = {
618
+ sendWelcomeEmail: jest.fn(),
619
+ sendVerificationEmail: jest.fn()
620
+ };
621
+
622
+ hookService = new UserService(mockEmailService);
623
+ });
624
+
625
+ test('should send welcome email on user creation', async () => {
626
+ // Create test entity
627
+ const userEntity = UserArcheType.fill({
628
+ userProfile: { name: 'Test User', email: 'test@example.com' }
629
+ }).createEntity();
630
+
631
+ // Create event
632
+ const event = new EntityCreatedEvent(userEntity);
633
+
634
+ // Trigger hook manually for testing
635
+ await hookService.onUserCreated(event);
636
+
637
+ // Verify email was sent
638
+ expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith(
639
+ 'test@example.com'
640
+ );
641
+ });
642
+ });
643
+ ```
644
+
645
+ ## 🚀 Event Types Reference
646
+
647
+ ### Entity Events
648
+
649
+ - **`entity.created`**: Fired when an entity is created (first save)
650
+ - **`entity.updated`**: Fired when an entity is updated (subsequent saves)
651
+ - **`entity.deleted`**: Fired when an entity is deleted
652
+
653
+ ### Component Events
654
+
655
+ - **`component.added`**: Fired when a component is added to an entity
656
+ - **`component.updated`**: Fired when a component data is updated
657
+ - **`component.removed`**: Fired when a component is removed from an entity
658
+
659
+ ### Event Properties
660
+
661
+ All events provide:
662
+ - `eventType`: String identifier of the event type
663
+ - `timestamp`: When the event occurred
664
+ - `entity`: The entity associated with the event
665
+
666
+ Entity events additionally provide:
667
+ - `isNew`: Boolean indicating if this is a new entity
668
+
669
+ Component events additionally provide:
670
+ - `component`: The component instance
671
+ - `componentType`: String identifier of the component type
672
+
673
+ ## 🚀 What's Next?
674
+
675
+ Now that you understand Lifecycle Hooks, let's explore:
676
+
677
+ - **[Services](services.md)** - Using hooks in services
678
+ - **[Query System](query.md)** - Advanced querying with hooks
679
+ - **[API Reference](../api/)** - Complete hook API documentation
680
+
681
+ ---
682
+
683
+ *Ready to add dynamic behavior to your entities? Let's look at [Advanced Features](../advanced/) next!* 🚀