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.
@@ -1,495 +0,0 @@
1
- import { EntityHook, ComponentHook } from "../../core/decorators/EntityHooks";
2
- import { EntityCreatedEvent, EntityUpdatedEvent, EntityDeletedEvent, ComponentAddedEvent, ComponentUpdatedEvent, ComponentRemovedEvent } from "../../core/events/EntityLifecycleEvents";
3
- import { Entity } from "../../core/Entity";
4
- import { logger } from "../../core/Logger";
5
-
6
- export interface AuditLogEntry {
7
- id: string;
8
- timestamp: Date;
9
- action: 'create' | 'update' | 'delete' | 'add_component' | 'update_component' | 'remove_component';
10
- entityId: string;
11
- entityType?: string;
12
- componentType?: string;
13
- userId?: string;
14
- oldData?: any;
15
- newData?: any;
16
- metadata?: Record<string, any>;
17
- }
18
-
19
- export interface AuditLoggerConfig {
20
- enabled: boolean;
21
- level: 'debug' | 'info' | 'warn' | 'error';
22
- storage: 'memory' | 'database' | 'file' | 'external';
23
- includeData: boolean;
24
- maxEntries: number;
25
- retentionDays: number;
26
- }
27
-
28
- /**
29
- * AuditLogger - Comprehensive audit logging for entity lifecycle events
30
- *
31
- * Features:
32
- * - Logs all entity and component lifecycle events
33
- * - Multiple storage backends (memory, database, file, external)
34
- * - Configurable data inclusion
35
- * - Automatic cleanup of old entries
36
- * - Performance monitoring
37
- * - User context tracking
38
- */
39
- export class AuditLogger {
40
- private logs: AuditLogEntry[] = [];
41
- private config: AuditLoggerConfig;
42
- private cleanupInterval: NodeJS.Timeout | null = null;
43
-
44
- constructor(config: Partial<AuditLoggerConfig> = {}) {
45
- this.config = {
46
- enabled: true,
47
- level: 'info',
48
- storage: 'memory',
49
- includeData: true,
50
- maxEntries: 10000,
51
- retentionDays: 30,
52
- ...config
53
- };
54
-
55
- if (this.config.enabled) {
56
- this.startCleanupInterval();
57
- }
58
- }
59
-
60
- /**
61
- * Log entity creation events
62
- */
63
- @EntityHook("entity.created")
64
- async handleEntityCreated(event: EntityCreatedEvent) {
65
- if (!this.config.enabled) return;
66
-
67
- const entry: AuditLogEntry = {
68
- id: this.generateId(),
69
- timestamp: new Date(),
70
- action: 'create',
71
- entityId: event.getEntity().id,
72
- entityType: this.getEntityType(event.getEntity()),
73
- userId: this.getCurrentUserId(),
74
- newData: this.config.includeData ? await this.extractEntityData(event.getEntity()) : undefined,
75
- metadata: {
76
- isNew: event.isNew,
77
- source: 'entity_hook'
78
- }
79
- };
80
-
81
- await this.storeLogEntry(entry);
82
- this.log('info', `Entity created: ${entry.entityId}`, entry);
83
- }
84
-
85
- /**
86
- * Log entity update events
87
- */
88
- @EntityHook("entity.updated")
89
- async handleEntityUpdated(event: EntityUpdatedEvent) {
90
- if (!this.config.enabled) return;
91
-
92
- const entry: AuditLogEntry = {
93
- id: this.generateId(),
94
- timestamp: new Date(),
95
- action: 'update',
96
- entityId: event.getEntity().id,
97
- entityType: this.getEntityType(event.getEntity()),
98
- userId: this.getCurrentUserId(),
99
- metadata: {
100
- changedComponents: event.getChangedComponents(),
101
- source: 'entity_hook'
102
- }
103
- };
104
-
105
- await this.storeLogEntry(entry);
106
- this.log('info', `Entity updated: ${entry.entityId}`, entry);
107
- }
108
-
109
- /**
110
- * Log entity deletion events
111
- */
112
- @EntityHook("entity.deleted")
113
- async handleEntityDeleted(event: EntityDeletedEvent) {
114
- if (!this.config.enabled) return;
115
-
116
- const entry: AuditLogEntry = {
117
- id: this.generateId(),
118
- timestamp: new Date(),
119
- action: 'delete',
120
- entityId: event.getEntity().id,
121
- entityType: this.getEntityType(event.getEntity()),
122
- userId: this.getCurrentUserId(),
123
- oldData: this.config.includeData ? await this.extractEntityData(event.getEntity()) : undefined,
124
- metadata: {
125
- isSoftDelete: event.isSoftDelete,
126
- source: 'entity_hook'
127
- }
128
- };
129
-
130
- await this.storeLogEntry(entry);
131
- this.log('info', `Entity deleted: ${entry.entityId}`, entry);
132
- }
133
-
134
- /**
135
- * Log component addition events
136
- */
137
- @ComponentHook("component.added")
138
- async handleComponentAdded(event: ComponentAddedEvent) {
139
- if (!this.config.enabled) return;
140
-
141
- const entry: AuditLogEntry = {
142
- id: this.generateId(),
143
- timestamp: new Date(),
144
- action: 'add_component',
145
- entityId: event.getEntity().id,
146
- entityType: this.getEntityType(event.getEntity()),
147
- componentType: event.getComponentType(),
148
- userId: this.getCurrentUserId(),
149
- newData: this.config.includeData ? event.getComponent().data() : undefined,
150
- metadata: {
151
- source: 'component_hook'
152
- }
153
- };
154
-
155
- await this.storeLogEntry(entry);
156
- this.log('debug', `Component added: ${entry.componentType} to ${entry.entityId}`, entry);
157
- }
158
-
159
- /**
160
- * Log component update events
161
- */
162
- @ComponentHook("component.updated")
163
- async handleComponentUpdated(event: ComponentUpdatedEvent) {
164
- if (!this.config.enabled) return;
165
-
166
- const entry: AuditLogEntry = {
167
- id: this.generateId(),
168
- timestamp: new Date(),
169
- action: 'update_component',
170
- entityId: event.getEntity().id,
171
- entityType: this.getEntityType(event.getEntity()),
172
- componentType: event.getComponentType(),
173
- userId: this.getCurrentUserId(),
174
- oldData: this.config.includeData ? event.getOldData() : undefined,
175
- newData: this.config.includeData ? event.getNewData() : undefined,
176
- metadata: {
177
- source: 'component_hook'
178
- }
179
- };
180
-
181
- await this.storeLogEntry(entry);
182
- this.log('debug', `Component updated: ${entry.componentType} on ${entry.entityId}`, entry);
183
- }
184
-
185
- /**
186
- * Log component removal events
187
- */
188
- @ComponentHook("component.removed")
189
- async handleComponentRemoved(event: ComponentRemovedEvent) {
190
- if (!this.config.enabled) return;
191
-
192
- const entry: AuditLogEntry = {
193
- id: this.generateId(),
194
- timestamp: new Date(),
195
- action: 'remove_component',
196
- entityId: event.getEntity().id,
197
- entityType: this.getEntityType(event.getEntity()),
198
- componentType: event.getComponentType(),
199
- userId: this.getCurrentUserId(),
200
- oldData: this.config.includeData ? event.getComponent().data() : undefined,
201
- metadata: {
202
- source: 'component_hook'
203
- }
204
- };
205
-
206
- await this.storeLogEntry(entry);
207
- this.log('debug', `Component removed: ${entry.componentType} from ${entry.entityId}`, entry);
208
- }
209
-
210
- /**
211
- * Get all audit logs
212
- */
213
- getLogs(filter?: {
214
- entityId?: string;
215
- action?: string;
216
- userId?: string;
217
- since?: Date;
218
- until?: Date;
219
- }): AuditLogEntry[] {
220
- let filteredLogs = [...this.logs];
221
-
222
- if (filter) {
223
- if (filter.entityId) {
224
- filteredLogs = filteredLogs.filter(log => log.entityId === filter.entityId);
225
- }
226
- if (filter.action) {
227
- filteredLogs = filteredLogs.filter(log => log.action === filter.action);
228
- }
229
- if (filter.userId) {
230
- filteredLogs = filteredLogs.filter(log => log.userId === filter.userId);
231
- }
232
- if (filter.since) {
233
- filteredLogs = filteredLogs.filter(log => log.timestamp >= filter.since!);
234
- }
235
- if (filter.until) {
236
- filteredLogs = filteredLogs.filter(log => log.timestamp <= filter.until!);
237
- }
238
- }
239
-
240
- return filteredLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
241
- }
242
-
243
- /**
244
- * Get audit logs for a specific entity
245
- */
246
- getEntityLogs(entityId: string): AuditLogEntry[] {
247
- return this.getLogs({ entityId });
248
- }
249
-
250
- /**
251
- * Get audit logs for a specific user
252
- */
253
- getUserLogs(userId: string): AuditLogEntry[] {
254
- return this.getLogs({ userId });
255
- }
256
-
257
- /**
258
- * Clear old audit logs based on retention policy
259
- */
260
- async clearOldLogs(): Promise<void> {
261
- const cutoffDate = new Date();
262
- cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);
263
-
264
- const initialCount = this.logs.length;
265
- this.logs = this.logs.filter(log => log.timestamp >= cutoffDate);
266
-
267
- const removedCount = initialCount - this.logs.length;
268
- if (removedCount > 0) {
269
- this.log('info', `Cleared ${removedCount} old audit log entries`);
270
- }
271
- }
272
-
273
- /**
274
- * Export audit logs to different formats
275
- */
276
- exportLogs(format: 'json' | 'csv' = 'json'): string {
277
- switch (format) {
278
- case 'json':
279
- return JSON.stringify(this.logs, null, 2);
280
- case 'csv':
281
- return this.convertToCSV(this.logs);
282
- default:
283
- throw new Error(`Unsupported export format: ${format}`);
284
- }
285
- }
286
-
287
- /**
288
- * Get audit statistics
289
- */
290
- getStatistics(): {
291
- totalEntries: number;
292
- entriesByAction: Record<string, number>;
293
- entriesByEntityType: Record<string, number>;
294
- oldestEntry: Date | null;
295
- newestEntry: Date | null;
296
- averageEntriesPerDay: number;
297
- } {
298
- const stats = {
299
- totalEntries: this.logs.length,
300
- entriesByAction: {} as Record<string, number>,
301
- entriesByEntityType: {} as Record<string, number>,
302
- oldestEntry: null as Date | null,
303
- newestEntry: null as Date | null,
304
- averageEntriesPerDay: 0
305
- };
306
-
307
- if (this.logs.length === 0) return stats;
308
-
309
- // Calculate distributions
310
- for (const log of this.logs) {
311
- stats.entriesByAction[log.action] = (stats.entriesByAction[log.action] || 0) + 1;
312
- if (log.entityType) {
313
- stats.entriesByEntityType[log.entityType] = (stats.entriesByEntityType[log.entityType] || 0) + 1;
314
- }
315
- }
316
-
317
- // Calculate date range
318
- const timestamps = this.logs.map(log => log.timestamp.getTime()).sort();
319
- stats.oldestEntry = new Date(timestamps[0]!);
320
- stats.newestEntry = new Date(timestamps[timestamps.length - 1]!);
321
-
322
- // Calculate average entries per day
323
- const daysDiff = (stats.newestEntry.getTime() - stats.oldestEntry.getTime()) / (1000 * 60 * 60 * 24);
324
- stats.averageEntriesPerDay = daysDiff > 0 ? stats.totalEntries / daysDiff : stats.totalEntries;
325
-
326
- return stats;
327
- }
328
-
329
- /**
330
- * Store log entry based on configured storage backend
331
- */
332
- private async storeLogEntry(entry: AuditLogEntry): Promise<void> {
333
- try {
334
- switch (this.config.storage) {
335
- case 'memory':
336
- this.storeInMemory(entry);
337
- break;
338
- case 'database':
339
- await this.storeInDatabase(entry);
340
- break;
341
- case 'file':
342
- await this.storeInFile(entry);
343
- break;
344
- case 'external':
345
- await this.storeExternally(entry);
346
- break;
347
- default:
348
- throw new Error(`Unsupported storage backend: ${this.config.storage}`);
349
- }
350
- } catch (error) {
351
- this.log('error', `Failed to store audit log entry: ${error}`);
352
- // Fallback to memory storage
353
- this.storeInMemory(entry);
354
- }
355
- }
356
-
357
- private storeInMemory(entry: AuditLogEntry): void {
358
- this.logs.push(entry);
359
-
360
- // Enforce max entries limit
361
- if (this.logs.length > this.config.maxEntries) {
362
- this.logs = this.logs.slice(-this.config.maxEntries);
363
- }
364
- }
365
-
366
- private async storeInDatabase(entry: AuditLogEntry): Promise<void> {
367
- // Implementation for database storage
368
- // This would integrate with your database layer
369
- throw new Error("Database storage not implemented - extend this method");
370
- }
371
-
372
- private async storeInFile(entry: AuditLogEntry): Promise<void> {
373
- // Implementation for file storage
374
- // This would write to log files
375
- throw new Error("File storage not implemented - extend this method");
376
- }
377
-
378
- private async storeExternally(entry: AuditLogEntry): Promise<void> {
379
- // Implementation for external service (e.g., Logstash, Splunk)
380
- // This would send to external logging service
381
- throw new Error("External storage not implemented - extend this method");
382
- }
383
-
384
- private startCleanupInterval(): void {
385
- // Run cleanup daily
386
- this.cleanupInterval = setInterval(() => {
387
- this.clearOldLogs().catch(error => {
388
- this.log('error', `Failed to clear old audit logs: ${error}`);
389
- });
390
- }, 24 * 60 * 60 * 1000); // 24 hours
391
- }
392
-
393
- private stopCleanupInterval(): void {
394
- if (this.cleanupInterval) {
395
- clearInterval(this.cleanupInterval);
396
- this.cleanupInterval = null;
397
- }
398
- }
399
-
400
- private generateId(): string {
401
- return `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
402
- }
403
-
404
- private getEntityType(entity: Entity): string | undefined {
405
- // Try to determine entity type from components
406
- // This is a simple implementation - you might want to enhance this
407
- try {
408
- const components = entity.componentList();
409
- return components.length > 0 ? components[0]?.constructor.name : 'unknown';
410
- } catch {
411
- return 'unknown';
412
- }
413
- }
414
-
415
- private getCurrentUserId(): string | undefined {
416
- // Implementation to get current user from context
417
- // This depends on your authentication system
418
- return 'system'; // Default to system user
419
- }
420
-
421
- private async extractEntityData(entity: Entity): Promise<any> {
422
- try {
423
- const data: any = {};
424
- const components = entity.componentList();
425
-
426
- for (const component of components) {
427
- try {
428
- const componentData = await entity.get(component.constructor as any);
429
- if (componentData) {
430
- data[component.constructor.name] = componentData;
431
- }
432
- } catch (error) {
433
- // Skip components that can't be retrieved
434
- this.log('debug', `Could not extract data for component ${component.constructor.name}`);
435
- }
436
- }
437
-
438
- return data;
439
- } catch (error) {
440
- this.log('warn', `Failed to extract entity data: ${error}`);
441
- return { error: 'Failed to extract data' };
442
- }
443
- }
444
-
445
- private convertToCSV(logs: AuditLogEntry[]): string {
446
- const headers = [
447
- 'id', 'timestamp', 'action', 'entityId', 'entityType',
448
- 'componentType', 'userId', 'oldData', 'newData', 'metadata'
449
- ];
450
-
451
- const rows = logs.map(log => [
452
- log.id,
453
- log.timestamp.toISOString(),
454
- log.action,
455
- log.entityId,
456
- log.entityType || '',
457
- log.componentType || '',
458
- log.userId || '',
459
- log.oldData ? JSON.stringify(log.oldData) : '',
460
- log.newData ? JSON.stringify(log.newData) : '',
461
- log.metadata ? JSON.stringify(log.metadata) : ''
462
- ]);
463
-
464
- return [headers, ...rows]
465
- .map(row => row.map(field => `"${field.replace(/"/g, '""')}"`).join(','))
466
- .join('\n');
467
- }
468
-
469
- private log(level: string, message: string, data?: any): void {
470
- const fullMessage = data ? `${message} ${JSON.stringify(data)}` : message;
471
-
472
- switch (level) {
473
- case 'debug':
474
- logger.debug(fullMessage);
475
- break;
476
- case 'info':
477
- logger.info(fullMessage);
478
- break;
479
- case 'warn':
480
- logger.warn(fullMessage);
481
- break;
482
- case 'error':
483
- logger.error(fullMessage);
484
- break;
485
- }
486
- }
487
-
488
- /**
489
- * Cleanup resources
490
- */
491
- destroy(): void {
492
- this.stopCleanupInterval();
493
- this.logs = [];
494
- }
495
- }
package/validate-docs.sh DELETED
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # BunSane Documentation Validation Script
4
- # This script validates the basic functionality of the documentation site
5
-
6
- echo "🔍 Validating BunSane Documentation Site..."
7
- echo "=========================================="
8
-
9
- # Check if docs directory exists
10
- if [ ! -d "docs" ]; then
11
- echo "❌ docs directory not found!"
12
- exit 1
13
- fi
14
-
15
- echo "✅ docs directory exists"
16
-
17
- # Check for required files
18
- required_files=("index.html" "_sidebar.md" "README.md" "_coverpage.md" "getting-started.md")
19
- for file in "${required_files[@]}"; do
20
- if [ -f "docs/$file" ]; then
21
- echo "✅ $file exists"
22
- else
23
- echo "❌ $file missing"
24
- exit 1
25
- fi
26
- done
27
-
28
- # Check for core concepts directory
29
- if [ -d "docs/core-concepts" ]; then
30
- echo "✅ core-concepts directory exists"
31
- else
32
- echo "❌ core-concepts directory missing"
33
- exit 1
34
- fi
35
-
36
- # Check for entity documentation
37
- if [ -f "docs/core-concepts/entity.md" ]; then
38
- echo "✅ entity.md exists"
39
- else
40
- echo "❌ entity.md missing"
41
- exit 1
42
- fi
43
-
44
- # Validate HTML structure
45
- if grep -q "<!DOCTYPE html>" docs/index.html; then
46
- echo "✅ index.html has valid HTML structure"
47
- else
48
- echo "❌ index.html missing DOCTYPE"
49
- exit 1
50
- fi
51
-
52
- # Check for Docsify configuration
53
- if grep -q "window.\$docsify" docs/index.html; then
54
- echo "✅ Docsify configuration found"
55
- else
56
- echo "❌ Docsify configuration missing"
57
- exit 1
58
- fi
59
-
60
- # Check sidebar structure
61
- if grep -q "\- \*\*Getting Started\*\*" docs/_sidebar.md; then
62
- echo "✅ Sidebar has Getting Started section"
63
- else
64
- echo "❌ Sidebar missing Getting Started section"
65
- exit 1
66
- fi
67
-
68
- # Check for navigation links
69
- if grep -q "\[Entity System\]" docs/_sidebar.md; then
70
- echo "✅ Sidebar has Entity System link"
71
- else
72
- echo "❌ Sidebar missing Entity System link"
73
- exit 1
74
- fi
75
-
76
- echo ""
77
- echo "🎉 All basic validations passed!"
78
- echo "=================================="
79
- echo "Your BunSane documentation site is ready for deployment."
80
- echo ""
81
- echo "Next steps:"
82
- echo "1. Enable GitHub Pages in your repository settings"
83
- echo "2. Set the source to 'Deploy from a branch'"
84
- echo "3. Select the 'gh-pages' branch (will be created by GitHub Actions)"
85
- echo "4. Your documentation will be available at: https://yaaruu.github.io/bunsane/"
86
- echo ""
87
- echo "For local development:"
88
- echo "1. Install docsify-cli: npm install -g docsify-cli"
89
- echo "2. Run: docsify serve docs"
90
- echo "3. Open http://localhost:3000"