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,666 @@
1
+ import { describe, test, expect, beforeEach, afterEach, beforeAll } from "bun:test";
2
+ import { Entity } from "../core/Entity";
3
+ import hookManager from "../core/EntityHookManager";
4
+ import { EntityCreatedEvent, EntityUpdatedEvent, EntityDeletedEvent, ComponentAddedEvent, ComponentUpdatedEvent, ComponentRemovedEvent } from "../core/events/EntityLifecycleEvents";
5
+ import { BaseComponent, CompData, Component } from "../core/Components";
6
+ import App from "../core/App";
7
+ import { EntityHook, ComponentHook, LifecycleHook, registerDecoratedHooks } from "../core/decorators/EntityHooks";
8
+
9
+ let app: App;
10
+
11
+ beforeAll(async () => {
12
+ app = new App();
13
+ await app.waitForAppReady();
14
+ });
15
+
16
+ @Component
17
+ class TestComponent extends BaseComponent {
18
+ @CompData()
19
+ value: string = "";
20
+ }
21
+
22
+ @Component
23
+ class AnotherTestComponent extends BaseComponent {
24
+ @CompData()
25
+ numberValue: number = 0;
26
+ }
27
+
28
+ describe('Entity Lifecycle Hooks - Phase 1', () => {
29
+ beforeEach(() => {
30
+ hookManager.clearAllHooks();
31
+ });
32
+
33
+ afterEach(() => {
34
+ hookManager.clearAllHooks();
35
+ });
36
+
37
+ test('should register and execute entity created hook', async () => {
38
+ let hookExecuted = false;
39
+ let capturedEvent: EntityCreatedEvent | null = null;
40
+
41
+ const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
42
+ hookExecuted = true;
43
+ capturedEvent = event;
44
+ });
45
+
46
+ expect(hookId).toBeDefined();
47
+ expect(typeof hookId).toBe('string');
48
+
49
+ // Create and save a new entity
50
+ const entity = Entity.Create();
51
+ await entity.save();
52
+
53
+ // Verify hook was executed
54
+ expect(hookExecuted).toBe(true);
55
+ expect(capturedEvent).toBeInstanceOf(EntityCreatedEvent);
56
+ expect((capturedEvent as unknown as EntityCreatedEvent)?.getEntity()).toBe(entity);
57
+ expect((capturedEvent as unknown as EntityCreatedEvent)?.isNew).toBe(true);
58
+ });
59
+
60
+ test('should register and execute entity updated hook', async () => {
61
+ let hookExecuted = false;
62
+ let capturedEvent: EntityUpdatedEvent | null = null;
63
+
64
+ const hookId = hookManager.registerEntityHook("entity.updated", (event: EntityUpdatedEvent) => {
65
+ hookExecuted = true;
66
+ capturedEvent = event;
67
+ });
68
+
69
+ // Create and save a new entity first
70
+ const entity = Entity.Create().add(TestComponent, { value: "initial" });
71
+ await entity.save();
72
+
73
+ // Reset for update test
74
+ hookExecuted = false;
75
+ capturedEvent = null;
76
+
77
+ // Update the entity
78
+ await entity.set(TestComponent, { value: "updated" });
79
+ await entity.save();
80
+
81
+ // Verify hook was executed
82
+ expect(hookExecuted).toBe(true);
83
+ expect(capturedEvent).toBeInstanceOf(EntityUpdatedEvent);
84
+ expect((capturedEvent as unknown as EntityUpdatedEvent)?.getEntity()).toBe(entity);
85
+ expect((capturedEvent as unknown as EntityUpdatedEvent)?.isNew).toBe(false);
86
+ expect((capturedEvent as unknown as EntityUpdatedEvent)?.getChangedComponents()).toHaveLength(1);
87
+ });
88
+
89
+ test('should register and execute entity deleted hook', async () => {
90
+ let hookExecuted = false;
91
+ let capturedEvent: EntityDeletedEvent | null = null;
92
+
93
+ const hookId = hookManager.registerEntityHook("entity.deleted", (event: EntityDeletedEvent) => {
94
+ hookExecuted = true;
95
+ capturedEvent = event;
96
+ });
97
+
98
+ // Create and save a new entity first
99
+ const entity = Entity.Create().add(TestComponent, { value: "test" });
100
+ await entity.save();
101
+
102
+ // Delete the entity
103
+ await entity.delete();
104
+
105
+ // Verify hook was executed
106
+ expect(hookExecuted).toBe(true);
107
+ expect(capturedEvent).toBeInstanceOf(EntityDeletedEvent);
108
+ expect((capturedEvent as unknown as EntityDeletedEvent)?.getEntity()).toBe(entity);
109
+ expect((capturedEvent as unknown as EntityDeletedEvent)?.isSoftDelete).toBe(true);
110
+ });
111
+
112
+ test('should execute multiple hooks for the same event', async () => {
113
+ let hook1Executed = false;
114
+ let hook2Executed = false;
115
+
116
+ hookManager.registerEntityHook("entity.created", () => {
117
+ hook1Executed = true;
118
+ });
119
+
120
+ hookManager.registerEntityHook("entity.created", () => {
121
+ hook2Executed = true;
122
+ });
123
+
124
+ // Create and save a new entity
125
+ const entity = Entity.Create();
126
+ await entity.save();
127
+
128
+ // Verify both hooks were executed
129
+ expect(hook1Executed).toBe(true);
130
+ expect(hook2Executed).toBe(true);
131
+ });
132
+
133
+ test('should execute hooks with priority ordering', async () => {
134
+ const executionOrder: number[] = [];
135
+
136
+ hookManager.registerEntityHook("entity.created", () => {
137
+ executionOrder.push(1);
138
+ }, { priority: 1 });
139
+
140
+ hookManager.registerEntityHook("entity.created", () => {
141
+ executionOrder.push(2);
142
+ }, { priority: 2 });
143
+
144
+ hookManager.registerEntityHook("entity.created", () => {
145
+ executionOrder.push(3);
146
+ }, { priority: 0 });
147
+
148
+ // Create and save a new entity
149
+ const entity = Entity.Create();
150
+ await entity.save();
151
+
152
+ // Verify hooks executed in priority order (highest first)
153
+ expect(executionOrder).toEqual([2, 1, 3]);
154
+ });
155
+
156
+ test('should handle hook execution errors gracefully', async () => {
157
+ let goodHookExecuted = false;
158
+
159
+ hookManager.registerEntityHook("entity.created", () => {
160
+ throw new Error("Hook failed");
161
+ });
162
+
163
+ hookManager.registerEntityHook("entity.created", () => {
164
+ goodHookExecuted = true;
165
+ });
166
+
167
+ // Create and save a new entity
168
+ const entity = Entity.Create();
169
+ await entity.save();
170
+
171
+ // Verify the good hook still executed despite the error
172
+ expect(goodHookExecuted).toBe(true);
173
+ });
174
+
175
+ test('should remove hooks by ID', () => {
176
+ const hookId = hookManager.registerEntityHook("entity.created", () => {
177
+ // This should not execute
178
+ expect(true).toBe(false);
179
+ });
180
+
181
+ const removed = hookManager.removeHook(hookId);
182
+ expect(removed).toBe(true);
183
+
184
+ // Verify hook count decreased
185
+ expect(hookManager.getHookCount("entity.created")).toBe(0);
186
+ });
187
+
188
+ test('should return false when removing non-existent hook', () => {
189
+ const removed = hookManager.removeHook("non-existent-hook-id");
190
+ expect(removed).toBe(false);
191
+ });
192
+
193
+ test('should get correct hook counts', () => {
194
+ expect(hookManager.getHookCount()).toBe(0);
195
+
196
+ hookManager.registerEntityHook("entity.created", () => {});
197
+ hookManager.registerEntityHook("entity.updated", () => {});
198
+ hookManager.registerEntityHook("entity.created", () => {});
199
+
200
+ expect(hookManager.getHookCount()).toBe(3);
201
+ expect(hookManager.getHookCount("entity.created")).toBe(2);
202
+ expect(hookManager.getHookCount("entity.updated")).toBe(1);
203
+ expect(hookManager.getHookCount("entity.deleted")).toBe(0);
204
+ });
205
+
206
+ test('should clear all hooks', () => {
207
+ hookManager.registerEntityHook("entity.created", () => {});
208
+ hookManager.registerEntityHook("entity.updated", () => {});
209
+
210
+ expect(hookManager.getHookCount()).toBe(2);
211
+
212
+ hookManager.clearAllHooks();
213
+
214
+ expect(hookManager.getHookCount()).toBe(0);
215
+ });
216
+ });
217
+
218
+ describe('Component Lifecycle Hooks - Phase 2', () => {
219
+ beforeEach(() => {
220
+ hookManager.clearAllHooks();
221
+ });
222
+
223
+ afterEach(() => {
224
+ hookManager.clearAllHooks();
225
+ });
226
+
227
+ test('should register and execute component added hook', async () => {
228
+ let hookExecuted = false;
229
+ let capturedEvent: ComponentAddedEvent | null = null;
230
+
231
+ const hookId = hookManager.registerComponentHook("component.added", (event: ComponentAddedEvent) => {
232
+ hookExecuted = true;
233
+ capturedEvent = event;
234
+ });
235
+
236
+ expect(hookId).toBeDefined();
237
+ expect(typeof hookId).toBe('string');
238
+
239
+ // Create entity and add component
240
+ const entity = Entity.Create();
241
+ const testComponent = new TestComponent();
242
+ testComponent.value = "test component";
243
+ entity.add(TestComponent, { value: "test component" });
244
+
245
+ // Verify hook was executed
246
+ expect(hookExecuted).toBe(true);
247
+ expect(capturedEvent).toBeInstanceOf(ComponentAddedEvent);
248
+ expect((capturedEvent as unknown as ComponentAddedEvent)?.getEntity()).toBe(entity);
249
+ expect((capturedEvent as unknown as ComponentAddedEvent)?.getComponentType()).toBe(testComponent.getTypeID());
250
+ expect(((capturedEvent as unknown as ComponentAddedEvent)?.getComponent().data() as any).value).toBe("test component");
251
+ });
252
+
253
+ test('should register and execute component updated hook', async () => {
254
+ let hookExecuted = false;
255
+ let capturedEvent: ComponentUpdatedEvent | null = null;
256
+
257
+ const hookId = hookManager.registerComponentHook("component.updated", (event: ComponentUpdatedEvent) => {
258
+ hookExecuted = true;
259
+ capturedEvent = event;
260
+ });
261
+
262
+ // Create entity and add component first
263
+ const entity = Entity.Create();
264
+ entity.add(TestComponent, { value: "initial value" });
265
+
266
+ // Reset for update test
267
+ hookExecuted = false;
268
+ capturedEvent = null;
269
+
270
+ // Update the component
271
+ await entity.set(TestComponent, { value: "updated value" });
272
+
273
+ // Verify hook was executed
274
+ expect(hookExecuted).toBe(true);
275
+ expect(capturedEvent).toBeInstanceOf(ComponentUpdatedEvent);
276
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getEntity()).toBe(entity);
277
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getComponentType()).toBe((new TestComponent()).getTypeID());
278
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getOldData()?.value).toBe("initial value");
279
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getNewData()?.value).toBe("updated value");
280
+ });
281
+
282
+ test('should register and execute component removed hook', async () => {
283
+ let hookExecuted = false;
284
+ let capturedEvent: ComponentRemovedEvent | null = null;
285
+
286
+ const hookId = hookManager.registerComponentHook("component.removed", (event: ComponentRemovedEvent) => {
287
+ hookExecuted = true;
288
+ capturedEvent = event;
289
+ });
290
+
291
+ // Create entity and add component first
292
+ const entity = Entity.Create();
293
+ entity.add(TestComponent, { value: "test component" });
294
+
295
+ // Remove the component
296
+ const removed = entity.remove(TestComponent);
297
+
298
+ // Verify removal was successful
299
+ expect(removed).toBe(true);
300
+
301
+ // Verify hook was executed
302
+ expect(hookExecuted).toBe(true);
303
+ expect(capturedEvent).toBeInstanceOf(ComponentRemovedEvent);
304
+ expect((capturedEvent as unknown as ComponentRemovedEvent)?.getEntity()).toBe(entity);
305
+ expect((capturedEvent as unknown as ComponentRemovedEvent)?.getComponentType()).toBe((new TestComponent()).getTypeID());
306
+ expect(((capturedEvent as unknown as ComponentRemovedEvent)?.getComponent().data() as any).value).toBe("test component");
307
+ });
308
+
309
+ test('should execute multiple component hooks for the same event', async () => {
310
+ let hook1Executed = false;
311
+ let hook2Executed = false;
312
+
313
+ hookManager.registerComponentHook("component.added", () => {
314
+ hook1Executed = true;
315
+ });
316
+
317
+ hookManager.registerComponentHook("component.added", () => {
318
+ hook2Executed = true;
319
+ });
320
+
321
+ // Create entity and add component
322
+ const entity = Entity.Create();
323
+ entity.add(TestComponent, { value: "test" });
324
+
325
+ // Verify both hooks were executed
326
+ expect(hook1Executed).toBe(true);
327
+ expect(hook2Executed).toBe(true);
328
+ });
329
+
330
+ test('should execute component hooks with priority ordering', async () => {
331
+ const executionOrder: number[] = [];
332
+
333
+ hookManager.registerComponentHook("component.added", () => {
334
+ executionOrder.push(1);
335
+ }, { priority: 1 });
336
+
337
+ hookManager.registerComponentHook("component.added", () => {
338
+ executionOrder.push(2);
339
+ }, { priority: 2 });
340
+
341
+ hookManager.registerComponentHook("component.added", () => {
342
+ executionOrder.push(3);
343
+ }, { priority: 0 });
344
+
345
+ // Create entity and add component
346
+ const entity = Entity.Create();
347
+ entity.add(TestComponent, { value: "test" });
348
+
349
+ // Verify hooks executed in priority order (highest first)
350
+ expect(executionOrder).toEqual([2, 1, 3]);
351
+ });
352
+
353
+ test('should handle component hook execution errors gracefully', async () => {
354
+ let goodHookExecuted = false;
355
+
356
+ hookManager.registerComponentHook("component.added", () => {
357
+ throw new Error("Component hook failed");
358
+ });
359
+
360
+ hookManager.registerComponentHook("component.added", () => {
361
+ goodHookExecuted = true;
362
+ });
363
+
364
+ // Create entity and add component
365
+ const entity = Entity.Create();
366
+ entity.add(TestComponent, { value: "test" });
367
+
368
+ // Verify the good hook still executed despite the error
369
+ expect(goodHookExecuted).toBe(true);
370
+ });
371
+
372
+ test('should return false when removing non-existent component', () => {
373
+ const entity = Entity.Create();
374
+
375
+ // Try to remove a component that doesn't exist
376
+ const removed = entity.remove(TestComponent);
377
+
378
+ expect(removed).toBe(false);
379
+ });
380
+
381
+ test('should fire component added event when set() adds new component', async () => {
382
+ let hookExecuted = false;
383
+ let capturedEvent: ComponentAddedEvent | null = null;
384
+
385
+ const hookId = hookManager.registerComponentHook("component.added", (event: ComponentAddedEvent) => {
386
+ hookExecuted = true;
387
+ capturedEvent = event;
388
+ });
389
+
390
+ // Create entity and use set() to add new component
391
+ const entity = Entity.Create();
392
+ await entity.set(TestComponent, { value: "new component" });
393
+
394
+ // Verify hook was executed (set() should fire added event for new components)
395
+ expect(hookExecuted).toBe(true);
396
+ expect(capturedEvent).toBeInstanceOf(ComponentAddedEvent);
397
+ expect(((capturedEvent as unknown as ComponentAddedEvent)?.getComponent().data() as any).value).toBe("new component");
398
+ });
399
+
400
+ test('should fire component updated event when set() updates existing component', async () => {
401
+ let hookExecuted = false;
402
+ let capturedEvent: ComponentUpdatedEvent | null = null;
403
+
404
+ const hookId = hookManager.registerComponentHook("component.updated", (event: ComponentUpdatedEvent) => {
405
+ hookExecuted = true;
406
+ capturedEvent = event;
407
+ });
408
+
409
+ // Create entity and add component first
410
+ const entity = Entity.Create();
411
+ entity.add(TestComponent, { value: "initial" });
412
+
413
+ // Reset for update test
414
+ hookExecuted = false;
415
+ capturedEvent = null;
416
+
417
+ // Use set() to update existing component
418
+ await entity.set(TestComponent, { value: "updated" });
419
+
420
+ // Verify hook was executed
421
+ expect(hookExecuted).toBe(true);
422
+ expect(capturedEvent).toBeInstanceOf(ComponentUpdatedEvent);
423
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getOldData()?.value).toBe("initial");
424
+ expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getNewData()?.value).toBe("updated");
425
+ });
426
+
427
+ test('should get correct hook counts for component events', () => {
428
+ expect(hookManager.getHookCount()).toBe(0);
429
+
430
+ hookManager.registerComponentHook("component.added", () => {});
431
+ hookManager.registerComponentHook("component.updated", () => {});
432
+ hookManager.registerComponentHook("component.added", () => {});
433
+
434
+ expect(hookManager.getHookCount()).toBe(3);
435
+ expect(hookManager.getHookCount("component.added")).toBe(2);
436
+ expect(hookManager.getHookCount("component.updated")).toBe(1);
437
+ expect(hookManager.getHookCount("component.removed")).toBe(0);
438
+ });
439
+ });
440
+
441
+ describe('Advanced Features - Phase 3', () => {
442
+ beforeEach(() => {
443
+ hookManager.clearAllHooks();
444
+ hookManager.resetMetrics();
445
+ });
446
+
447
+ afterEach(() => {
448
+ hookManager.clearAllHooks();
449
+ hookManager.resetMetrics();
450
+ });
451
+
452
+ test('should execute async hooks properly', async () => {
453
+ let hookExecuted = false;
454
+ let executionOrder: string[] = [];
455
+
456
+ const hookId = hookManager.registerEntityHook("entity.created", async (event: EntityCreatedEvent) => {
457
+ executionOrder.push('start');
458
+ await new Promise(resolve => setTimeout(resolve, 10));
459
+ hookExecuted = true;
460
+ executionOrder.push('end');
461
+ }, { async: true });
462
+
463
+ // Create entity and save
464
+ const entity = Entity.Create();
465
+ await entity.save();
466
+
467
+ // Verify hook was executed asynchronously
468
+ expect(hookExecuted).toBe(true);
469
+ expect(executionOrder).toEqual(['start', 'end']);
470
+ });
471
+
472
+ test('should handle hook timeouts', async () => {
473
+ let hookExecuted = false;
474
+ let timeoutErrorThrown = false;
475
+
476
+ const hookId = hookManager.registerEntityHook("entity.created", async (event: EntityCreatedEvent) => {
477
+ await new Promise(resolve => setTimeout(resolve, 100)); // Longer than timeout
478
+ hookExecuted = true;
479
+ }, { async: true, timeout: 50 });
480
+
481
+ // Create entity and save
482
+ const entity = Entity.Create();
483
+ await entity.save();
484
+
485
+ // Wait a bit for timeout to occur and be logged
486
+ await new Promise(resolve => setTimeout(resolve, 60));
487
+
488
+ // Hook should have executed (timeout doesn't prevent execution, just logs error)
489
+ expect(hookExecuted).toBe(true);
490
+ // The timeout error should have been logged (we can't easily test the exact log output in this test)
491
+ });
492
+
493
+ test('should filter hooks based on conditions', async () => {
494
+ let executedHooks: string[] = [];
495
+
496
+ // Hook that only executes for new entities
497
+ hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
498
+ executedHooks.push('new-only');
499
+ }, {
500
+ filter: (event) => event instanceof EntityCreatedEvent && event.isNew
501
+ });
502
+
503
+ // Hook that executes for all created events
504
+ hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
505
+ executedHooks.push('all');
506
+ });
507
+
508
+ // Create and save entity (should be new)
509
+ const entity = Entity.Create();
510
+ await entity.save();
511
+
512
+ // Verify both hooks executed (filter should pass for new entity)
513
+ expect(executedHooks).toContain('new-only');
514
+ expect(executedHooks).toContain('all');
515
+ });
516
+
517
+ test('should collect performance metrics', async () => {
518
+ const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
519
+ // Small delay to simulate work
520
+ });
521
+
522
+ // Create entity and save
523
+ const entity = Entity.Create();
524
+ await entity.save();
525
+
526
+ // Check metrics
527
+ const metrics = hookManager.getMetrics("entity.created");
528
+ expect(metrics.totalExecutions).toBe(1);
529
+ expect(metrics.averageExecutionTime).toBeGreaterThan(0);
530
+ expect(metrics.errorCount).toBe(0);
531
+ });
532
+
533
+ test('should reset metrics correctly', async () => {
534
+ const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
535
+ // Do nothing
536
+ });
537
+
538
+ // Execute hook
539
+ const entity = Entity.Create();
540
+ await entity.save();
541
+
542
+ // Verify metrics exist
543
+ expect(hookManager.getMetrics("entity.created").totalExecutions).toBe(1);
544
+
545
+ // Reset metrics
546
+ hookManager.resetMetrics("entity.created");
547
+
548
+ // Verify metrics are reset
549
+ expect(hookManager.getMetrics("entity.created").totalExecutions).toBe(0);
550
+ });
551
+
552
+ test('should execute hooks in batch', async () => {
553
+ let executionCount = 0;
554
+
555
+ const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
556
+ executionCount++;
557
+ });
558
+
559
+ // Create multiple events
560
+ const events = [
561
+ new EntityCreatedEvent(Entity.Create()),
562
+ new EntityCreatedEvent(Entity.Create()),
563
+ new EntityCreatedEvent(Entity.Create())
564
+ ];
565
+
566
+ // Execute in batch
567
+ await hookManager.executeHooksBatch(events);
568
+
569
+ // Verify all hooks were executed
570
+ expect(executionCount).toBe(3);
571
+ });
572
+
573
+ test('should work with decorator-based hooks', async () => {
574
+ let hookExecuted = false;
575
+
576
+ class TestService {
577
+ @EntityHook("entity.created")
578
+ async handleEntityCreated(event: EntityCreatedEvent) {
579
+ hookExecuted = true;
580
+ }
581
+ }
582
+
583
+ const service = new TestService();
584
+ registerDecoratedHooks(service);
585
+
586
+ // Create entity and save
587
+ const entity = Entity.Create();
588
+ await entity.save();
589
+
590
+ // Verify decorated hook was executed
591
+ expect(hookExecuted).toBe(true);
592
+ });
593
+
594
+ test('should work with component decorator hooks', async () => {
595
+ let hookExecuted = false;
596
+
597
+ class TestService {
598
+ @ComponentHook("component.added")
599
+ async handleComponentAdded(event: ComponentAddedEvent) {
600
+ hookExecuted = true;
601
+ }
602
+ }
603
+
604
+ const service = new TestService();
605
+ registerDecoratedHooks(service);
606
+
607
+ // Create entity and add component
608
+ const entity = Entity.Create();
609
+ entity.add(TestComponent, { value: "test" });
610
+
611
+ // Verify decorated hook was executed
612
+ expect(hookExecuted).toBe(true);
613
+ });
614
+
615
+ test('should work with lifecycle decorator hooks', async () => {
616
+ let hookExecuted = false;
617
+
618
+ class TestService {
619
+ @LifecycleHook()
620
+ async handleAnyLifecycleEvent(event: any) {
621
+ if (event.getEventType() === "entity.created") {
622
+ hookExecuted = true;
623
+ }
624
+ }
625
+ }
626
+
627
+ const service = new TestService();
628
+ registerDecoratedHooks(service);
629
+
630
+ // Create entity and save
631
+ const entity = Entity.Create();
632
+ await entity.save();
633
+
634
+ // Verify decorated hook was executed
635
+ expect(hookExecuted).toBe(true);
636
+ });
637
+
638
+ test('should work with multiple decorator hooks on same service', async () => {
639
+ let entityHookExecuted = false;
640
+ let componentHookExecuted = false;
641
+
642
+ class TestService {
643
+ @EntityHook("entity.created")
644
+ async handleEntityCreated(event: EntityCreatedEvent) {
645
+ entityHookExecuted = true;
646
+ }
647
+
648
+ @ComponentHook("component.added")
649
+ async handleComponentAdded(event: ComponentAddedEvent) {
650
+ componentHookExecuted = true;
651
+ }
652
+ }
653
+
654
+ const service = new TestService();
655
+ registerDecoratedHooks(service);
656
+
657
+ // Create entity and add component
658
+ const entity = Entity.Create();
659
+ entity.add(TestComponent, { value: "test" });
660
+ await entity.save();
661
+
662
+ // Verify both decorated hooks were executed
663
+ expect(entityHookExecuted).toBe(true);
664
+ expect(componentHookExecuted).toBe(true);
665
+ });
666
+ });