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.
- package/core/App.ts +81 -32
- package/core/Entity.ts +34 -2
- package/core/Query.ts +57 -7
- package/core/RequestLoaders.ts +59 -35
- package/database/index.ts +5 -5
- package/gql/Generator.ts +2 -1
- package/gql/index.ts +11 -1
- package/package.json +16 -8
- package/tests/component.test.ts +134 -1
- package/examples/hooks/README.md +0 -228
- package/examples/hooks/audit-logger.ts +0 -495
- package/validate-docs.sh +0 -90
package/tests/component.test.ts
CHANGED
|
@@ -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
|
+
});
|
package/examples/hooks/README.md
DELETED
|
@@ -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)
|