bunsane 0.1.1 → 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.
@@ -4,6 +4,7 @@ import type { ComponentDataType } from "../core/Components";
4
4
  import { Entity } from "../core/Entity";
5
5
  import App from "../core/App";
6
6
  import ComponentRegistry from "../core/ComponentRegistry";
7
+ import db from "../database";
7
8
 
8
9
  // Test component with 'value' attribute (standard assumption)
9
10
  @Component
@@ -50,6 +51,13 @@ class BooleanComponent extends BaseComponent {
50
51
  enabled: boolean = false;
51
52
  }
52
53
 
54
+ let app: App;
55
+
56
+ beforeAll(async () => {
57
+ app = new App();
58
+ await app.waitForAppReady();
59
+ });
60
+
53
61
  describe("Component Edge Cases and Attribute Handling", () => {
54
62
  describe("Component with standard 'value' attribute", () => {
55
63
  test("should correctly identify and return value property", () => {
@@ -202,4 +210,129 @@ describe("Component Edge Cases and Attribute Handling", () => {
202
210
  expect(data.value).toBe(""); // Default value
203
211
  });
204
212
  });
205
- });
213
+ });
214
+
215
+ describe("Component Removal from Entities", () => {
216
+ test("should successfully remove an existing component from an entity", () => {
217
+ const entity = Entity.Create();
218
+ entity.add(ValueComponent, { value: "test" });
219
+
220
+ // Verify component is added
221
+ expect(entity.componentList()).toHaveLength(1);
222
+ expect(entity.componentList()[0]).toBeInstanceOf(ValueComponent);
223
+
224
+ // Remove the component
225
+ const removed = entity.remove(ValueComponent);
226
+ expect(removed).toBe(true);
227
+
228
+ // Verify component is removed
229
+ expect(entity.componentList()).toHaveLength(0);
230
+ });
231
+
232
+ test("should return false when trying to remove a non-existent component", () => {
233
+ const entity = Entity.Create();
234
+
235
+ // Try to remove a component that was never added
236
+ const removed = entity.remove(ValueComponent);
237
+ expect(removed).toBe(false);
238
+
239
+ // Entity should still have no components
240
+ expect(entity.componentList()).toHaveLength(0);
241
+ });
242
+
243
+ test("should remove only the specified component type, leaving others intact", () => {
244
+ const entity = Entity.Create();
245
+ entity.add(ValueComponent, { value: "value comp" });
246
+ entity.add(CustomAttributeComponent, { customData: "custom comp" });
247
+
248
+ // Verify both components are added
249
+ expect(entity.componentList()).toHaveLength(2);
250
+
251
+ // Remove only the ValueComponent
252
+ const removed = entity.remove(ValueComponent);
253
+ expect(removed).toBe(true);
254
+
255
+ // Verify only CustomAttributeComponent remains
256
+ expect(entity.componentList()).toHaveLength(1);
257
+ expect(entity.componentList()[0]).toBeInstanceOf(CustomAttributeComponent);
258
+ });
259
+
260
+ test("should mark entity as dirty after component removal", () => {
261
+ const entity = Entity.Create();
262
+ entity.add(ValueComponent, { value: "test" });
263
+
264
+ // Entity should be dirty after adding
265
+ expect((entity as any)._dirty).toBe(true);
266
+
267
+ // Save to make it clean
268
+ // Note: We can't actually save without database, but we can set it manually for test
269
+ entity.setDirty(false);
270
+ expect((entity as any)._dirty).toBe(false);
271
+
272
+ // Remove component
273
+ entity.remove(ValueComponent);
274
+
275
+ // Entity should be dirty again
276
+ expect((entity as any)._dirty).toBe(true);
277
+ });
278
+
279
+ test("should persist component addition and removal to database", async () => {
280
+ const entity = Entity.Create();
281
+ entity.add(ValueComponent, { value: "test value" });
282
+
283
+ // Save to database
284
+ const saveResult = await entity.save();
285
+ expect(saveResult).toBe(true);
286
+
287
+ // Verify component exists in database
288
+ const componentsAfterAdd = await db`SELECT id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
289
+ expect(componentsAfterAdd.length).toBe(1);
290
+ expect(componentsAfterAdd[0].data.value).toBe("test value");
291
+
292
+ // Remove the component
293
+ const removed = entity.remove(ValueComponent);
294
+ expect(removed).toBe(true);
295
+
296
+ // Save again to persist removal
297
+ const saveResult2 = await entity.save();
298
+ expect(saveResult2).toBe(true);
299
+
300
+ // Verify component is removed from database
301
+ const componentsAfterRemove = await db`SELECT id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
302
+ expect(componentsAfterRemove.length).toBe(0);
303
+ });
304
+
305
+ test("should handle multiple component additions and removals in database", async () => {
306
+ const entity = Entity.Create();
307
+ entity.add(ValueComponent, { value: "value comp" });
308
+ entity.add(CustomAttributeComponent, { customData: "custom comp" });
309
+
310
+ // Save to database
311
+ await entity.save();
312
+
313
+ // Verify both components exist
314
+ const componentsAfterAdd = await db`SELECT type_id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL ORDER BY type_id`;
315
+ expect(componentsAfterAdd.length).toBe(2);
316
+ const valueComp = componentsAfterAdd.find((c: any) => c.data.value === "value comp");
317
+ const customComp = componentsAfterAdd.find((c: any) => c.data.customData === "custom comp");
318
+ expect(valueComp).toBeDefined();
319
+ expect(customComp).toBeDefined();
320
+
321
+ // Remove one component
322
+ entity.remove(ValueComponent);
323
+ await entity.save();
324
+
325
+ // Verify only custom component remains
326
+ const componentsAfterRemove = await db`SELECT type_id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
327
+ expect(componentsAfterRemove.length).toBe(1);
328
+ expect(componentsAfterRemove[0].data.customData).toBe("custom comp");
329
+
330
+ // Remove the last component
331
+ entity.remove(CustomAttributeComponent);
332
+ await entity.save();
333
+
334
+ // Verify no components remain
335
+ const componentsAfterRemoveAll = await db`SELECT id FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
336
+ expect(componentsAfterRemoveAll.length).toBe(0);
337
+ });
338
+ });
@@ -1,228 +0,0 @@
1
- # Entity Lifecycle Hooks Examples
2
-
3
- This directory contains example implementations of common use cases for the Entity Lifecycle Hooks system. Each example demonstrates best practices for hook implementation, error handling, and performance optimization.
4
-
5
- ## Examples Overview
6
-
7
- ### 1. Audit Logging (`audit-logger.ts`)
8
- Demonstrates comprehensive audit logging for entity lifecycle events with different storage backends.
9
-
10
- ### 2. Cache Management (`cache-manager.ts`)
11
- Shows how to implement intelligent cache invalidation and population using entity hooks.
12
-
13
- ### 3. Search Indexing (`search-indexer.ts`)
14
- Illustrates real-time search index updates for entities and components.
15
-
16
- ### 4. Business Rules (`business-rules.ts`)
17
- Examples of business rule validation and enforcement using component hooks.
18
-
19
- ### 5. Notifications (`notification-service.ts`)
20
- Demonstrates event-driven notification systems for user actions.
21
-
22
- ### 6. Data Synchronization (`data-sync.ts`)
23
- Shows cross-system data synchronization patterns.
24
-
25
- ### 7. Metrics Collection (`metrics-collector.ts`)
26
- Performance monitoring and metrics collection for entity operations.
27
-
28
- ### 8. Security (`security-hooks.ts`)
29
- Security-related hooks for access control and data protection.
30
-
31
- ## Usage
32
-
33
- Each example can be used as a starting point for your own implementations. Import the example classes and register them with the hook system:
34
-
35
- ```typescript
36
- import { AuditLogger } from "./examples/hooks/audit-logger";
37
- import { registerDecoratedHooks } from "bunsane";
38
-
39
- // Register example hooks
40
- const auditLogger = new AuditLogger();
41
- registerDecoratedHooks(auditLogger);
42
- ```
43
-
44
- ## Best Practices Demonstrated
45
-
46
- - **Error Isolation**: Each hook handles its own errors without affecting others
47
- - **Performance Optimization**: Use of filters, batching, and async operations
48
- - **Resource Management**: Proper cleanup and resource handling
49
- - **Type Safety**: Full TypeScript support with proper typing
50
- - **Monitoring**: Built-in metrics and logging
51
- - **Configuration**: Environment-based configuration
52
- - **Testing**: Testable hook implementations
53
-
54
- ## Common Patterns
55
-
56
- ### Conditional Execution
57
- ```typescript
58
- @EntityHook("entity.created")
59
- async handleEntityCreated(event: EntityCreatedEvent) {
60
- // Only process specific entity types
61
- if (!event.getEntity().has(TargetComponent)) return;
62
-
63
- // Process the entity
64
- await this.processEntity(event.getEntity());
65
- }
66
- ```
67
-
68
- ### Batch Processing
69
- ```typescript
70
- private pendingEvents: EntityCreatedEvent[] = [];
71
-
72
- @EntityHook("entity.created")
73
- handleEntityCreated(event: EntityCreatedEvent) {
74
- this.pendingEvents.push(event);
75
-
76
- // Process in batches to improve performance
77
- if (this.pendingEvents.length >= 10) {
78
- this.processBatch();
79
- }
80
- }
81
- ```
82
-
83
- ### Error Handling
84
- ```typescript
85
- @EntityHook("entity.created")
86
- async handleEntityCreated(event: EntityCreatedEvent) {
87
- try {
88
- await this.unreliableOperation();
89
- } catch (error) {
90
- // Log error but don't throw - preserve other hooks
91
- this.logger.error("Hook execution failed:", error);
92
- }
93
- }
94
- ```
95
-
96
- ### Resource Cleanup
97
- ```typescript
98
- @EntityHook("entity.deleted")
99
- handleEntityDeleted(event: EntityDeletedEvent) {
100
- // Clean up resources associated with the entity
101
- this.cache.delete(event.getEntity().id);
102
- this.pendingOperations.delete(event.getEntity().id);
103
- }
104
- ```
105
-
106
- ## Configuration
107
-
108
- Most examples support configuration through environment variables or constructor parameters:
109
-
110
- ```typescript
111
- // Environment-based configuration
112
- const auditLogger = new AuditLogger({
113
- enabled: process.env.AUDIT_ENABLED === 'true',
114
- level: process.env.AUDIT_LEVEL || 'info',
115
- storage: process.env.AUDIT_STORAGE || 'database'
116
- });
117
- ```
118
-
119
- ## Testing
120
-
121
- Each example includes testing patterns:
122
-
123
- ```typescript
124
- describe("AuditLogger", () => {
125
- let auditLogger: AuditLogger;
126
-
127
- beforeEach(() => {
128
- auditLogger = new AuditLogger();
129
- registerDecoratedHooks(auditLogger);
130
- });
131
-
132
- afterEach(() => {
133
- EntityHookManager.clearAllHooks();
134
- });
135
-
136
- test("should log entity creation", async () => {
137
- const entity = Entity.Create();
138
- await entity.save();
139
-
140
- // Verify audit log was created
141
- expect(auditLogger.getLogs()).toContain(/* expected log entry */);
142
- });
143
- });
144
- ```
145
-
146
- ## Performance Considerations
147
-
148
- Examples demonstrate performance optimization techniques:
149
-
150
- - **Filtering**: Reduce unnecessary hook execution
151
- - **Batching**: Group operations for efficiency
152
- - **Async Processing**: Use async hooks for I/O operations
153
- - **Caching**: Cache frequently accessed data
154
- - **Timeouts**: Prevent hanging operations
155
-
156
- ## Integration
157
-
158
- Examples show how to integrate with existing systems:
159
-
160
- - **Database**: Entity persistence and queries
161
- - **Cache**: Redis, Memcached, or in-memory caching
162
- - **Search**: Elasticsearch, Algolia, or custom search
163
- - **Queues**: Background job processing
164
- - **Monitoring**: Metrics collection and alerting
165
- - **Logging**: Structured logging with context
166
-
167
- ## Customization
168
-
169
- Examples are designed to be easily customizable:
170
-
171
- ```typescript
172
- // Extend base examples
173
- class CustomAuditLogger extends AuditLogger {
174
- @EntityHook("entity.updated")
175
- async handleCustomUpdate(event: EntityUpdatedEvent) {
176
- // Custom logic
177
- await super.handleEntityUpdated(event);
178
-
179
- // Additional processing
180
- await this.customProcessing(event);
181
- }
182
- }
183
- ```
184
-
185
- ## Monitoring
186
-
187
- Examples include monitoring and health checks:
188
-
189
- ```typescript
190
- // Health check endpoint
191
- app.get('/health/hooks', async (req, res) => {
192
- const metrics = EntityHookManager.getMetrics();
193
- const health = {
194
- status: metrics.errorCount > 10 ? 'unhealthy' : 'healthy',
195
- metrics,
196
- timestamp: new Date().toISOString()
197
- };
198
-
199
- res.json(health);
200
- });
201
- ```
202
-
203
- ## Troubleshooting
204
-
205
- Common issues and solutions:
206
-
207
- 1. **Hooks not executing**: Check `EntityHookManager.waitForReady()`
208
- 2. **Performance issues**: Use `EntityHookManager.getMetrics()` to identify bottlenecks
209
- 3. **Memory leaks**: Ensure proper cleanup in deletion hooks
210
- 4. **Error propagation**: Handle errors gracefully to prevent hook isolation issues
211
-
212
- ## Contributing
213
-
214
- When adding new examples:
215
-
216
- 1. Follow the established patterns and best practices
217
- 2. Include comprehensive error handling
218
- 3. Add performance optimizations
219
- 4. Provide configuration options
220
- 5. Include tests and documentation
221
- 6. Demonstrate integration patterns
222
-
223
- ## Related Documentation
224
-
225
- - [Hooks Documentation](../HOOKS_DOCUMENTATION.md)
226
- - [Migration Guide](../HOOKS_MIGRATION_GUIDE.md)
227
- - [Performance Guide](../HOOKS_PERFORMANCE_GUIDE.md)
228
- - [API Reference](../HOOKS_DOCUMENTATION.md#api-reference)