bunsane 0.1.4 → 0.2.0

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 (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -1,667 +0,0 @@
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
- app.init();
14
- await app.waitForAppReady();
15
- });
16
-
17
- @Component
18
- class TestComponent extends BaseComponent {
19
- @CompData()
20
- value: string = "";
21
- }
22
-
23
- @Component
24
- class AnotherTestComponent extends BaseComponent {
25
- @CompData()
26
- numberValue: number = 0;
27
- }
28
-
29
- describe('Entity Lifecycle Hooks - Phase 1', () => {
30
- beforeEach(() => {
31
- hookManager.clearAllHooks();
32
- });
33
-
34
- afterEach(() => {
35
- hookManager.clearAllHooks();
36
- });
37
-
38
- test('should register and execute entity created hook', async () => {
39
- let hookExecuted = false;
40
- let capturedEvent: EntityCreatedEvent | null = null;
41
-
42
- const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
43
- hookExecuted = true;
44
- capturedEvent = event;
45
- });
46
-
47
- expect(hookId).toBeDefined();
48
- expect(typeof hookId).toBe('string');
49
-
50
- // Create and save a new entity
51
- const entity = Entity.Create();
52
- await entity.save();
53
-
54
- // Verify hook was executed
55
- expect(hookExecuted).toBe(true);
56
- expect(capturedEvent).toBeInstanceOf(EntityCreatedEvent);
57
- expect((capturedEvent as unknown as EntityCreatedEvent)?.getEntity()).toBe(entity);
58
- expect((capturedEvent as unknown as EntityCreatedEvent)?.isNew).toBe(true);
59
- });
60
-
61
- test('should register and execute entity updated hook', async () => {
62
- let hookExecuted = false;
63
- let capturedEvent: EntityUpdatedEvent | null = null;
64
-
65
- const hookId = hookManager.registerEntityHook("entity.updated", (event: EntityUpdatedEvent) => {
66
- hookExecuted = true;
67
- capturedEvent = event;
68
- });
69
-
70
- // Create and save a new entity first
71
- const entity = Entity.Create().add(TestComponent, { value: "initial" });
72
- await entity.save();
73
-
74
- // Reset for update test
75
- hookExecuted = false;
76
- capturedEvent = null;
77
-
78
- // Update the entity
79
- await entity.set(TestComponent, { value: "updated" });
80
- await entity.save();
81
-
82
- // Verify hook was executed
83
- expect(hookExecuted).toBe(true);
84
- expect(capturedEvent).toBeInstanceOf(EntityUpdatedEvent);
85
- expect((capturedEvent as unknown as EntityUpdatedEvent)?.getEntity()).toBe(entity);
86
- expect((capturedEvent as unknown as EntityUpdatedEvent)?.isNew).toBe(false);
87
- expect((capturedEvent as unknown as EntityUpdatedEvent)?.getChangedComponents()).toHaveLength(1);
88
- });
89
-
90
- test('should register and execute entity deleted hook', async () => {
91
- let hookExecuted = false;
92
- let capturedEvent: EntityDeletedEvent | null = null;
93
-
94
- const hookId = hookManager.registerEntityHook("entity.deleted", (event: EntityDeletedEvent) => {
95
- hookExecuted = true;
96
- capturedEvent = event;
97
- });
98
-
99
- // Create and save a new entity first
100
- const entity = Entity.Create().add(TestComponent, { value: "test" });
101
- await entity.save();
102
-
103
- // Delete the entity
104
- await entity.delete();
105
-
106
- // Verify hook was executed
107
- expect(hookExecuted).toBe(true);
108
- expect(capturedEvent).toBeInstanceOf(EntityDeletedEvent);
109
- expect((capturedEvent as unknown as EntityDeletedEvent)?.getEntity()).toBe(entity);
110
- expect((capturedEvent as unknown as EntityDeletedEvent)?.isSoftDelete).toBe(true);
111
- });
112
-
113
- test('should execute multiple hooks for the same event', async () => {
114
- let hook1Executed = false;
115
- let hook2Executed = false;
116
-
117
- hookManager.registerEntityHook("entity.created", () => {
118
- hook1Executed = true;
119
- });
120
-
121
- hookManager.registerEntityHook("entity.created", () => {
122
- hook2Executed = true;
123
- });
124
-
125
- // Create and save a new entity
126
- const entity = Entity.Create();
127
- await entity.save();
128
-
129
- // Verify both hooks were executed
130
- expect(hook1Executed).toBe(true);
131
- expect(hook2Executed).toBe(true);
132
- });
133
-
134
- test('should execute hooks with priority ordering', async () => {
135
- const executionOrder: number[] = [];
136
-
137
- hookManager.registerEntityHook("entity.created", () => {
138
- executionOrder.push(1);
139
- }, { priority: 1 });
140
-
141
- hookManager.registerEntityHook("entity.created", () => {
142
- executionOrder.push(2);
143
- }, { priority: 2 });
144
-
145
- hookManager.registerEntityHook("entity.created", () => {
146
- executionOrder.push(3);
147
- }, { priority: 0 });
148
-
149
- // Create and save a new entity
150
- const entity = Entity.Create();
151
- await entity.save();
152
-
153
- // Verify hooks executed in priority order (highest first)
154
- expect(executionOrder).toEqual([2, 1, 3]);
155
- });
156
-
157
- test('should handle hook execution errors gracefully', async () => {
158
- let goodHookExecuted = false;
159
-
160
- hookManager.registerEntityHook("entity.created", () => {
161
- throw new Error("Hook failed");
162
- });
163
-
164
- hookManager.registerEntityHook("entity.created", () => {
165
- goodHookExecuted = true;
166
- });
167
-
168
- // Create and save a new entity
169
- const entity = Entity.Create();
170
- await entity.save();
171
-
172
- // Verify the good hook still executed despite the error
173
- expect(goodHookExecuted).toBe(true);
174
- });
175
-
176
- test('should remove hooks by ID', () => {
177
- const hookId = hookManager.registerEntityHook("entity.created", () => {
178
- // This should not execute
179
- expect(true).toBe(false);
180
- });
181
-
182
- const removed = hookManager.removeHook(hookId);
183
- expect(removed).toBe(true);
184
-
185
- // Verify hook count decreased
186
- expect(hookManager.getHookCount("entity.created")).toBe(0);
187
- });
188
-
189
- test('should return false when removing non-existent hook', () => {
190
- const removed = hookManager.removeHook("non-existent-hook-id");
191
- expect(removed).toBe(false);
192
- });
193
-
194
- test('should get correct hook counts', () => {
195
- expect(hookManager.getHookCount()).toBe(0);
196
-
197
- hookManager.registerEntityHook("entity.created", () => {});
198
- hookManager.registerEntityHook("entity.updated", () => {});
199
- hookManager.registerEntityHook("entity.created", () => {});
200
-
201
- expect(hookManager.getHookCount()).toBe(3);
202
- expect(hookManager.getHookCount("entity.created")).toBe(2);
203
- expect(hookManager.getHookCount("entity.updated")).toBe(1);
204
- expect(hookManager.getHookCount("entity.deleted")).toBe(0);
205
- });
206
-
207
- test('should clear all hooks', () => {
208
- hookManager.registerEntityHook("entity.created", () => {});
209
- hookManager.registerEntityHook("entity.updated", () => {});
210
-
211
- expect(hookManager.getHookCount()).toBe(2);
212
-
213
- hookManager.clearAllHooks();
214
-
215
- expect(hookManager.getHookCount()).toBe(0);
216
- });
217
- });
218
-
219
- describe('Component Lifecycle Hooks - Phase 2', () => {
220
- beforeEach(() => {
221
- hookManager.clearAllHooks();
222
- });
223
-
224
- afterEach(() => {
225
- hookManager.clearAllHooks();
226
- });
227
-
228
- test('should register and execute component added hook', async () => {
229
- let hookExecuted = false;
230
- let capturedEvent: ComponentAddedEvent | null = null;
231
-
232
- const hookId = hookManager.registerComponentHook("component.added", (event: ComponentAddedEvent) => {
233
- hookExecuted = true;
234
- capturedEvent = event;
235
- });
236
-
237
- expect(hookId).toBeDefined();
238
- expect(typeof hookId).toBe('string');
239
-
240
- // Create entity and add component
241
- const entity = Entity.Create();
242
- const testComponent = new TestComponent();
243
- testComponent.value = "test component";
244
- entity.add(TestComponent, { value: "test component" });
245
-
246
- // Verify hook was executed
247
- expect(hookExecuted).toBe(true);
248
- expect(capturedEvent).toBeInstanceOf(ComponentAddedEvent);
249
- expect((capturedEvent as unknown as ComponentAddedEvent)?.getEntity()).toBe(entity);
250
- expect((capturedEvent as unknown as ComponentAddedEvent)?.getComponentType()).toBe(testComponent.getTypeID());
251
- expect(((capturedEvent as unknown as ComponentAddedEvent)?.getComponent().data() as any).value).toBe("test component");
252
- });
253
-
254
- test('should register and execute component updated hook', async () => {
255
- let hookExecuted = false;
256
- let capturedEvent: ComponentUpdatedEvent | null = null;
257
-
258
- const hookId = hookManager.registerComponentHook("component.updated", (event: ComponentUpdatedEvent) => {
259
- hookExecuted = true;
260
- capturedEvent = event;
261
- });
262
-
263
- // Create entity and add component first
264
- const entity = Entity.Create();
265
- entity.add(TestComponent, { value: "initial value" });
266
-
267
- // Reset for update test
268
- hookExecuted = false;
269
- capturedEvent = null;
270
-
271
- // Update the component
272
- await entity.set(TestComponent, { value: "updated value" });
273
-
274
- // Verify hook was executed
275
- expect(hookExecuted).toBe(true);
276
- expect(capturedEvent).toBeInstanceOf(ComponentUpdatedEvent);
277
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getEntity()).toBe(entity);
278
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getComponentType()).toBe((new TestComponent()).getTypeID());
279
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getOldData()?.value).toBe("initial value");
280
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getNewData()?.value).toBe("updated value");
281
- });
282
-
283
- test('should register and execute component removed hook', async () => {
284
- let hookExecuted = false;
285
- let capturedEvent: ComponentRemovedEvent | null = null;
286
-
287
- const hookId = hookManager.registerComponentHook("component.removed", (event: ComponentRemovedEvent) => {
288
- hookExecuted = true;
289
- capturedEvent = event;
290
- });
291
-
292
- // Create entity and add component first
293
- const entity = Entity.Create();
294
- entity.add(TestComponent, { value: "test component" });
295
-
296
- // Remove the component
297
- const removed = entity.remove(TestComponent);
298
-
299
- // Verify removal was successful
300
- expect(removed).toBe(true);
301
-
302
- // Verify hook was executed
303
- expect(hookExecuted).toBe(true);
304
- expect(capturedEvent).toBeInstanceOf(ComponentRemovedEvent);
305
- expect((capturedEvent as unknown as ComponentRemovedEvent)?.getEntity()).toBe(entity);
306
- expect((capturedEvent as unknown as ComponentRemovedEvent)?.getComponentType()).toBe((new TestComponent()).getTypeID());
307
- expect(((capturedEvent as unknown as ComponentRemovedEvent)?.getComponent().data() as any).value).toBe("test component");
308
- });
309
-
310
- test('should execute multiple component hooks for the same event', async () => {
311
- let hook1Executed = false;
312
- let hook2Executed = false;
313
-
314
- hookManager.registerComponentHook("component.added", () => {
315
- hook1Executed = true;
316
- });
317
-
318
- hookManager.registerComponentHook("component.added", () => {
319
- hook2Executed = true;
320
- });
321
-
322
- // Create entity and add component
323
- const entity = Entity.Create();
324
- entity.add(TestComponent, { value: "test" });
325
-
326
- // Verify both hooks were executed
327
- expect(hook1Executed).toBe(true);
328
- expect(hook2Executed).toBe(true);
329
- });
330
-
331
- test('should execute component hooks with priority ordering', async () => {
332
- const executionOrder: number[] = [];
333
-
334
- hookManager.registerComponentHook("component.added", () => {
335
- executionOrder.push(1);
336
- }, { priority: 1 });
337
-
338
- hookManager.registerComponentHook("component.added", () => {
339
- executionOrder.push(2);
340
- }, { priority: 2 });
341
-
342
- hookManager.registerComponentHook("component.added", () => {
343
- executionOrder.push(3);
344
- }, { priority: 0 });
345
-
346
- // Create entity and add component
347
- const entity = Entity.Create();
348
- entity.add(TestComponent, { value: "test" });
349
-
350
- // Verify hooks executed in priority order (highest first)
351
- expect(executionOrder).toEqual([2, 1, 3]);
352
- });
353
-
354
- test('should handle component hook execution errors gracefully', async () => {
355
- let goodHookExecuted = false;
356
-
357
- hookManager.registerComponentHook("component.added", () => {
358
- throw new Error("Component hook failed");
359
- });
360
-
361
- hookManager.registerComponentHook("component.added", () => {
362
- goodHookExecuted = true;
363
- });
364
-
365
- // Create entity and add component
366
- const entity = Entity.Create();
367
- entity.add(TestComponent, { value: "test" });
368
-
369
- // Verify the good hook still executed despite the error
370
- expect(goodHookExecuted).toBe(true);
371
- });
372
-
373
- test('should return false when removing non-existent component', () => {
374
- const entity = Entity.Create();
375
-
376
- // Try to remove a component that doesn't exist
377
- const removed = entity.remove(TestComponent);
378
-
379
- expect(removed).toBe(false);
380
- });
381
-
382
- test('should fire component added event when set() adds new component', async () => {
383
- let hookExecuted = false;
384
- let capturedEvent: ComponentAddedEvent | null = null;
385
-
386
- const hookId = hookManager.registerComponentHook("component.added", (event: ComponentAddedEvent) => {
387
- hookExecuted = true;
388
- capturedEvent = event;
389
- });
390
-
391
- // Create entity and use set() to add new component
392
- const entity = Entity.Create();
393
- await entity.set(TestComponent, { value: "new component" });
394
-
395
- // Verify hook was executed (set() should fire added event for new components)
396
- expect(hookExecuted).toBe(true);
397
- expect(capturedEvent).toBeInstanceOf(ComponentAddedEvent);
398
- expect(((capturedEvent as unknown as ComponentAddedEvent)?.getComponent().data() as any).value).toBe("new component");
399
- });
400
-
401
- test('should fire component updated event when set() updates existing component', async () => {
402
- let hookExecuted = false;
403
- let capturedEvent: ComponentUpdatedEvent | null = null;
404
-
405
- const hookId = hookManager.registerComponentHook("component.updated", (event: ComponentUpdatedEvent) => {
406
- hookExecuted = true;
407
- capturedEvent = event;
408
- });
409
-
410
- // Create entity and add component first
411
- const entity = Entity.Create();
412
- entity.add(TestComponent, { value: "initial" });
413
-
414
- // Reset for update test
415
- hookExecuted = false;
416
- capturedEvent = null;
417
-
418
- // Use set() to update existing component
419
- await entity.set(TestComponent, { value: "updated" });
420
-
421
- // Verify hook was executed
422
- expect(hookExecuted).toBe(true);
423
- expect(capturedEvent).toBeInstanceOf(ComponentUpdatedEvent);
424
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getOldData()?.value).toBe("initial");
425
- expect((capturedEvent as unknown as ComponentUpdatedEvent)?.getNewData()?.value).toBe("updated");
426
- });
427
-
428
- test('should get correct hook counts for component events', () => {
429
- expect(hookManager.getHookCount()).toBe(0);
430
-
431
- hookManager.registerComponentHook("component.added", () => {});
432
- hookManager.registerComponentHook("component.updated", () => {});
433
- hookManager.registerComponentHook("component.added", () => {});
434
-
435
- expect(hookManager.getHookCount()).toBe(3);
436
- expect(hookManager.getHookCount("component.added")).toBe(2);
437
- expect(hookManager.getHookCount("component.updated")).toBe(1);
438
- expect(hookManager.getHookCount("component.removed")).toBe(0);
439
- });
440
- });
441
-
442
- describe('Advanced Features - Phase 3', () => {
443
- beforeEach(() => {
444
- hookManager.clearAllHooks();
445
- hookManager.resetMetrics();
446
- });
447
-
448
- afterEach(() => {
449
- hookManager.clearAllHooks();
450
- hookManager.resetMetrics();
451
- });
452
-
453
- test('should execute async hooks properly', async () => {
454
- let hookExecuted = false;
455
- let executionOrder: string[] = [];
456
-
457
- const hookId = hookManager.registerEntityHook("entity.created", async (event: EntityCreatedEvent) => {
458
- executionOrder.push('start');
459
- await new Promise(resolve => setTimeout(resolve, 10));
460
- hookExecuted = true;
461
- executionOrder.push('end');
462
- }, { async: true });
463
-
464
- // Create entity and save
465
- const entity = Entity.Create();
466
- await entity.save();
467
-
468
- // Verify hook was executed asynchronously
469
- expect(hookExecuted).toBe(true);
470
- expect(executionOrder).toEqual(['start', 'end']);
471
- });
472
-
473
- test('should handle hook timeouts', async () => {
474
- let hookExecuted = false;
475
- let timeoutErrorThrown = false;
476
-
477
- const hookId = hookManager.registerEntityHook("entity.created", async (event: EntityCreatedEvent) => {
478
- await new Promise(resolve => setTimeout(resolve, 100)); // Longer than timeout
479
- hookExecuted = true;
480
- }, { async: true, timeout: 50 });
481
-
482
- // Create entity and save
483
- const entity = Entity.Create();
484
- await entity.save();
485
-
486
- // Wait a bit for timeout to occur and be logged
487
- await new Promise(resolve => setTimeout(resolve, 60));
488
-
489
- // Hook should have executed (timeout doesn't prevent execution, just logs error)
490
- expect(hookExecuted).toBe(true);
491
- // The timeout error should have been logged (we can't easily test the exact log output in this test)
492
- });
493
-
494
- test('should filter hooks based on conditions', async () => {
495
- let executedHooks: string[] = [];
496
-
497
- // Hook that only executes for new entities
498
- hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
499
- executedHooks.push('new-only');
500
- }, {
501
- filter: (event) => event instanceof EntityCreatedEvent && event.isNew
502
- });
503
-
504
- // Hook that executes for all created events
505
- hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
506
- executedHooks.push('all');
507
- });
508
-
509
- // Create and save entity (should be new)
510
- const entity = Entity.Create();
511
- await entity.save();
512
-
513
- // Verify both hooks executed (filter should pass for new entity)
514
- expect(executedHooks).toContain('new-only');
515
- expect(executedHooks).toContain('all');
516
- });
517
-
518
- test('should collect performance metrics', async () => {
519
- const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
520
- // Small delay to simulate work
521
- });
522
-
523
- // Create entity and save
524
- const entity = Entity.Create();
525
- await entity.save();
526
-
527
- // Check metrics
528
- const metrics = hookManager.getMetrics("entity.created");
529
- expect(metrics.totalExecutions).toBe(1);
530
- expect(metrics.averageExecutionTime).toBeGreaterThan(0);
531
- expect(metrics.errorCount).toBe(0);
532
- });
533
-
534
- test('should reset metrics correctly', async () => {
535
- const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
536
- // Do nothing
537
- });
538
-
539
- // Execute hook
540
- const entity = Entity.Create();
541
- await entity.save();
542
-
543
- // Verify metrics exist
544
- expect(hookManager.getMetrics("entity.created").totalExecutions).toBe(1);
545
-
546
- // Reset metrics
547
- hookManager.resetMetrics("entity.created");
548
-
549
- // Verify metrics are reset
550
- expect(hookManager.getMetrics("entity.created").totalExecutions).toBe(0);
551
- });
552
-
553
- test('should execute hooks in batch', async () => {
554
- let executionCount = 0;
555
-
556
- const hookId = hookManager.registerEntityHook("entity.created", (event: EntityCreatedEvent) => {
557
- executionCount++;
558
- });
559
-
560
- // Create multiple events
561
- const events = [
562
- new EntityCreatedEvent(Entity.Create()),
563
- new EntityCreatedEvent(Entity.Create()),
564
- new EntityCreatedEvent(Entity.Create())
565
- ];
566
-
567
- // Execute in batch
568
- await hookManager.executeHooksBatch(events);
569
-
570
- // Verify all hooks were executed
571
- expect(executionCount).toBe(3);
572
- });
573
-
574
- test('should work with decorator-based hooks', async () => {
575
- let hookExecuted = false;
576
-
577
- class TestService {
578
- @EntityHook("entity.created")
579
- async handleEntityCreated(event: EntityCreatedEvent) {
580
- hookExecuted = true;
581
- }
582
- }
583
-
584
- const service = new TestService();
585
- registerDecoratedHooks(service);
586
-
587
- // Create entity and save
588
- const entity = Entity.Create();
589
- await entity.save();
590
-
591
- // Verify decorated hook was executed
592
- expect(hookExecuted).toBe(true);
593
- });
594
-
595
- test('should work with component decorator hooks', async () => {
596
- let hookExecuted = false;
597
-
598
- class TestService {
599
- @ComponentHook("component.added")
600
- async handleComponentAdded(event: ComponentAddedEvent) {
601
- hookExecuted = true;
602
- }
603
- }
604
-
605
- const service = new TestService();
606
- registerDecoratedHooks(service);
607
-
608
- // Create entity and add component
609
- const entity = Entity.Create();
610
- entity.add(TestComponent, { value: "test" });
611
-
612
- // Verify decorated hook was executed
613
- expect(hookExecuted).toBe(true);
614
- });
615
-
616
- test('should work with lifecycle decorator hooks', async () => {
617
- let hookExecuted = false;
618
-
619
- class TestService {
620
- @LifecycleHook()
621
- async handleAnyLifecycleEvent(event: any) {
622
- if (event.getEventType() === "entity.created") {
623
- hookExecuted = true;
624
- }
625
- }
626
- }
627
-
628
- const service = new TestService();
629
- registerDecoratedHooks(service);
630
-
631
- // Create entity and save
632
- const entity = Entity.Create();
633
- await entity.save();
634
-
635
- // Verify decorated hook was executed
636
- expect(hookExecuted).toBe(true);
637
- });
638
-
639
- test('should work with multiple decorator hooks on same service', async () => {
640
- let entityHookExecuted = false;
641
- let componentHookExecuted = false;
642
-
643
- class TestService {
644
- @EntityHook("entity.created")
645
- async handleEntityCreated(event: EntityCreatedEvent) {
646
- entityHookExecuted = true;
647
- }
648
-
649
- @ComponentHook("component.added")
650
- async handleComponentAdded(event: ComponentAddedEvent) {
651
- componentHookExecuted = true;
652
- }
653
- }
654
-
655
- const service = new TestService();
656
- registerDecoratedHooks(service);
657
-
658
- // Create entity and add component
659
- const entity = Entity.Create();
660
- entity.add(TestComponent, { value: "test" });
661
- await entity.save();
662
-
663
- // Verify both decorated hooks were executed
664
- expect(entityHookExecuted).toBe(true);
665
- expect(componentHookExecuted).toBe(true);
666
- });
667
- });