vega-framework 0.1.29__py3-none-any.whl → 0.1.31__py3-none-any.whl
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.
- vega/cli/commands/generate.py +114 -0
- vega/cli/commands/init.py +6 -0
- vega/cli/main.py +18 -1
- vega/cli/templates/__init__.py +6 -0
- vega/cli/templates/components.py +34 -0
- vega/cli/templates/domain/event.py.j2 +23 -0
- vega/cli/templates/domain/event_handler.py.j2 +22 -0
- vega/cli/templates/domain/repository_interface.py.j2 +1 -1
- vega/cli/templates/project/events_init.py.j2 +32 -0
- vega/discovery/__init__.py +2 -1
- vega/discovery/events.py +86 -0
- vega/events/README.md +564 -0
- vega/events/SYNTAX_GUIDE.md +360 -0
- vega/events/__init__.py +30 -0
- vega/events/bus.py +382 -0
- vega/events/decorators.py +181 -0
- vega/events/event.py +156 -0
- vega/events/middleware.py +259 -0
- vega/patterns/interactor.py +47 -1
- vega_framework-0.1.31.dist-info/METADATA +349 -0
- {vega_framework-0.1.29.dist-info → vega_framework-0.1.31.dist-info}/RECORD +24 -13
- vega_framework-0.1.29.dist-info/METADATA +0 -485
- {vega_framework-0.1.29.dist-info → vega_framework-0.1.31.dist-info}/WHEEL +0 -0
- {vega_framework-0.1.29.dist-info → vega_framework-0.1.31.dist-info}/entry_points.txt +0 -0
- {vega_framework-0.1.29.dist-info → vega_framework-0.1.31.dist-info}/licenses/LICENSE +0 -0
vega/events/README.md
ADDED
@@ -0,0 +1,564 @@
|
|
1
|
+
# Vega Events - Domain Events & Event Bus
|
2
|
+
|
3
|
+
The Vega Events module provides a powerful event-driven architecture for your applications, enabling loose coupling and reactive programming patterns.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- ✅ **Event Bus** - Publish/subscribe pattern for domain events
|
8
|
+
- ✅ **Async Support** - Full async/await support for event handlers
|
9
|
+
- ✅ **Auto-Publish** - Ultra-clean syntax with metaclass-powered instant publishing
|
10
|
+
- ✅ **Priority Ordering** - Control handler execution order
|
11
|
+
- ✅ **Retry Logic** - Automatic retries for failed handlers
|
12
|
+
- ✅ **Middleware** - Cross-cutting concerns (logging, metrics, validation)
|
13
|
+
- ✅ **Event Inheritance** - Handlers for base events receive derived events
|
14
|
+
- ✅ **Type-Safe** - Full type hints support
|
15
|
+
|
16
|
+
## Quick Start
|
17
|
+
|
18
|
+
### 1. Define an Event
|
19
|
+
|
20
|
+
Events are immutable data classes that represent something that happened:
|
21
|
+
|
22
|
+
```python
|
23
|
+
from dataclasses import dataclass
|
24
|
+
from vega.events import Event
|
25
|
+
|
26
|
+
@dataclass(frozen=True)
|
27
|
+
class UserCreated(Event):
|
28
|
+
user_id: str
|
29
|
+
email: str
|
30
|
+
name: str
|
31
|
+
|
32
|
+
def __post_init__(self):
|
33
|
+
super().__init__()
|
34
|
+
```
|
35
|
+
|
36
|
+
### 2. Subscribe to Events
|
37
|
+
|
38
|
+
Use the `@subscribe` decorator to register event handlers:
|
39
|
+
|
40
|
+
```python
|
41
|
+
from vega.events import subscribe
|
42
|
+
|
43
|
+
@subscribe(UserCreated)
|
44
|
+
async def send_welcome_email(event: UserCreated):
|
45
|
+
"""Send welcome email when user is created"""
|
46
|
+
print(f"Sending welcome email to {event.email}")
|
47
|
+
await email_service.send(
|
48
|
+
to=event.email,
|
49
|
+
subject="Welcome!",
|
50
|
+
body=f"Hello {event.name}, welcome to our platform!"
|
51
|
+
)
|
52
|
+
|
53
|
+
@subscribe(UserCreated)
|
54
|
+
async def create_audit_log(event: UserCreated):
|
55
|
+
"""Log user creation for audit trail"""
|
56
|
+
await audit_service.log(f"User {event.user_id} created at {event.timestamp}")
|
57
|
+
```
|
58
|
+
|
59
|
+
### 3. Publish Events
|
60
|
+
|
61
|
+
**Simple Syntax (Recommended)** - Just call `.publish()` on the event:
|
62
|
+
|
63
|
+
```python
|
64
|
+
async def create_user(name: str, email: str):
|
65
|
+
# Create user logic...
|
66
|
+
user = User(id="123", name=name, email=email)
|
67
|
+
|
68
|
+
# Publish event - Simple and clean!
|
69
|
+
event = UserCreated(
|
70
|
+
user_id=user.id,
|
71
|
+
email=user.email,
|
72
|
+
name=user.name
|
73
|
+
)
|
74
|
+
await event.publish() # That's it!
|
75
|
+
|
76
|
+
return user
|
77
|
+
```
|
78
|
+
|
79
|
+
**Alternative Syntax** - Using the event bus directly:
|
80
|
+
|
81
|
+
```python
|
82
|
+
from vega.events import get_event_bus
|
83
|
+
|
84
|
+
async def create_user(name: str, email: str):
|
85
|
+
user = User(id="123", name=name, email=email)
|
86
|
+
|
87
|
+
# Publish via event bus
|
88
|
+
bus = get_event_bus()
|
89
|
+
await bus.publish(UserCreated(
|
90
|
+
user_id=user.id,
|
91
|
+
email=user.email,
|
92
|
+
name=user.name
|
93
|
+
))
|
94
|
+
|
95
|
+
return user
|
96
|
+
```
|
97
|
+
|
98
|
+
## Advanced Usage
|
99
|
+
|
100
|
+
### Priority and Ordering
|
101
|
+
|
102
|
+
Control handler execution order with priorities:
|
103
|
+
|
104
|
+
```python
|
105
|
+
@subscribe(UserCreated, priority=100)
|
106
|
+
async def critical_handler(event: UserCreated):
|
107
|
+
"""Runs first due to higher priority"""
|
108
|
+
pass
|
109
|
+
|
110
|
+
@subscribe(UserCreated, priority=0)
|
111
|
+
async def normal_handler(event: UserCreated):
|
112
|
+
"""Runs after critical handlers"""
|
113
|
+
pass
|
114
|
+
```
|
115
|
+
|
116
|
+
### Retry on Failure
|
117
|
+
|
118
|
+
Automatically retry handlers that fail:
|
119
|
+
|
120
|
+
```python
|
121
|
+
@subscribe(UserCreated, retry_on_error=True, max_retries=5)
|
122
|
+
async def unreliable_handler(event: UserCreated):
|
123
|
+
"""Will retry up to 5 times on failure"""
|
124
|
+
response = await external_api.call()
|
125
|
+
if not response.ok:
|
126
|
+
raise Exception("API call failed")
|
127
|
+
```
|
128
|
+
|
129
|
+
### Event Inheritance
|
130
|
+
|
131
|
+
Handlers for base events automatically receive derived events:
|
132
|
+
|
133
|
+
```python
|
134
|
+
@dataclass(frozen=True)
|
135
|
+
class UserEvent(Event):
|
136
|
+
user_id: str
|
137
|
+
|
138
|
+
@dataclass(frozen=True)
|
139
|
+
class UserCreated(UserEvent):
|
140
|
+
email: str
|
141
|
+
|
142
|
+
@dataclass(frozen=True)
|
143
|
+
class UserUpdated(UserEvent):
|
144
|
+
changes: dict
|
145
|
+
|
146
|
+
# This handler receives ALL UserEvent subtypes
|
147
|
+
@subscribe(UserEvent)
|
148
|
+
async def handle_any_user_event(event: UserEvent):
|
149
|
+
print(f"User event: {event.event_name}")
|
150
|
+
```
|
151
|
+
|
152
|
+
### Error Handling
|
153
|
+
|
154
|
+
Register global error handlers:
|
155
|
+
|
156
|
+
```python
|
157
|
+
from vega.events import get_event_bus
|
158
|
+
|
159
|
+
bus = get_event_bus()
|
160
|
+
|
161
|
+
@bus.on_error
|
162
|
+
async def log_event_errors(event, exception, handler_name):
|
163
|
+
"""Called when any handler fails"""
|
164
|
+
logger.error(
|
165
|
+
f"Handler '{handler_name}' failed for event '{event.event_name}': {exception}"
|
166
|
+
)
|
167
|
+
# Send to error tracking service
|
168
|
+
await sentry.capture_exception(exception)
|
169
|
+
```
|
170
|
+
|
171
|
+
### Middleware
|
172
|
+
|
173
|
+
Add cross-cutting concerns with middleware:
|
174
|
+
|
175
|
+
```python
|
176
|
+
from vega.events import get_event_bus, LoggingEventMiddleware
|
177
|
+
|
178
|
+
bus = get_event_bus()
|
179
|
+
|
180
|
+
# Built-in logging middleware
|
181
|
+
bus.add_middleware(LoggingEventMiddleware())
|
182
|
+
|
183
|
+
# Custom middleware
|
184
|
+
from vega.events import EventMiddleware
|
185
|
+
|
186
|
+
class TenantContextMiddleware(EventMiddleware):
|
187
|
+
async def before_publish(self, event: Event):
|
188
|
+
# Add tenant context to all events
|
189
|
+
tenant_id = get_current_tenant()
|
190
|
+
event.add_metadata('tenant_id', tenant_id)
|
191
|
+
|
192
|
+
async def after_publish(self, event: Event):
|
193
|
+
# Cleanup after event is processed
|
194
|
+
pass
|
195
|
+
|
196
|
+
bus.add_middleware(TenantContextMiddleware())
|
197
|
+
```
|
198
|
+
|
199
|
+
### Metrics Collection
|
200
|
+
|
201
|
+
Track event processing with built-in metrics middleware:
|
202
|
+
|
203
|
+
```python
|
204
|
+
from vega.events import get_event_bus
|
205
|
+
from vega.events.middleware import MetricsEventMiddleware
|
206
|
+
|
207
|
+
bus = get_event_bus()
|
208
|
+
metrics = MetricsEventMiddleware()
|
209
|
+
bus.add_middleware(metrics)
|
210
|
+
|
211
|
+
# Later, get metrics
|
212
|
+
stats = metrics.get_metrics()
|
213
|
+
print(stats)
|
214
|
+
# {
|
215
|
+
# 'UserCreated': {
|
216
|
+
# 'count': 150,
|
217
|
+
# 'avg_duration_ms': 45.2,
|
218
|
+
# 'min_duration_ms': 12.1,
|
219
|
+
# 'max_duration_ms': 234.5,
|
220
|
+
# }
|
221
|
+
# }
|
222
|
+
```
|
223
|
+
|
224
|
+
### Validation
|
225
|
+
|
226
|
+
Validate events before publishing:
|
227
|
+
|
228
|
+
```python
|
229
|
+
from vega.events.middleware import ValidationEventMiddleware
|
230
|
+
|
231
|
+
def validate_user_created(event):
|
232
|
+
if not event.email or '@' not in event.email:
|
233
|
+
raise ValueError("Invalid email address")
|
234
|
+
if not event.user_id:
|
235
|
+
raise ValueError("user_id is required")
|
236
|
+
|
237
|
+
validation = ValidationEventMiddleware()
|
238
|
+
validation.add_validator(UserCreated, validate_user_created)
|
239
|
+
|
240
|
+
bus.add_middleware(validation)
|
241
|
+
```
|
242
|
+
|
243
|
+
### Event Enrichment
|
244
|
+
|
245
|
+
Automatically add metadata to events:
|
246
|
+
|
247
|
+
```python
|
248
|
+
from vega.events.middleware import EnrichmentEventMiddleware
|
249
|
+
|
250
|
+
enrichment = EnrichmentEventMiddleware()
|
251
|
+
|
252
|
+
# Add correlation ID
|
253
|
+
enrichment.add_enricher(
|
254
|
+
lambda event: event.add_metadata('correlation_id', get_correlation_id())
|
255
|
+
)
|
256
|
+
|
257
|
+
# Add user context
|
258
|
+
enrichment.add_enricher(
|
259
|
+
lambda event: event.add_metadata('triggered_by', get_current_user_id())
|
260
|
+
)
|
261
|
+
|
262
|
+
bus.add_middleware(enrichment)
|
263
|
+
```
|
264
|
+
|
265
|
+
## Integration with Vega Patterns
|
266
|
+
|
267
|
+
### In Interactors
|
268
|
+
|
269
|
+
```python
|
270
|
+
from vega.patterns import Interactor
|
271
|
+
from vega.di import bind
|
272
|
+
from vega.events import get_event_bus
|
273
|
+
|
274
|
+
class CreateUser(Interactor[User]):
|
275
|
+
def __init__(self, name: str, email: str):
|
276
|
+
self.name = name
|
277
|
+
self.email = email
|
278
|
+
|
279
|
+
@bind
|
280
|
+
async def call(self, repository: UserRepository) -> User:
|
281
|
+
# Domain logic
|
282
|
+
user = User(name=self.name, email=self.email)
|
283
|
+
user = await repository.save(user)
|
284
|
+
|
285
|
+
# Publish domain event
|
286
|
+
bus = get_event_bus()
|
287
|
+
await bus.publish(UserCreated(
|
288
|
+
user_id=user.id,
|
289
|
+
email=user.email,
|
290
|
+
name=user.name
|
291
|
+
))
|
292
|
+
|
293
|
+
return user
|
294
|
+
```
|
295
|
+
|
296
|
+
### In Mediators
|
297
|
+
|
298
|
+
```python
|
299
|
+
from vega.patterns import Mediator
|
300
|
+
from vega.events import get_event_bus
|
301
|
+
|
302
|
+
class UserRegistrationWorkflow(Mediator[User]):
|
303
|
+
def __init__(self, name: str, email: str, password: str):
|
304
|
+
self.name = name
|
305
|
+
self.email = email
|
306
|
+
self.password = password
|
307
|
+
|
308
|
+
async def call(self) -> User:
|
309
|
+
# Create user
|
310
|
+
user = await CreateUser(self.name, self.email)
|
311
|
+
|
312
|
+
# Set password
|
313
|
+
await SetUserPassword(user.id, self.password)
|
314
|
+
|
315
|
+
# Publish workflow completion event
|
316
|
+
bus = get_event_bus()
|
317
|
+
await bus.publish(UserRegistrationCompleted(
|
318
|
+
user_id=user.id,
|
319
|
+
email=user.email
|
320
|
+
))
|
321
|
+
|
322
|
+
return user
|
323
|
+
```
|
324
|
+
|
325
|
+
## Event Naming Conventions
|
326
|
+
|
327
|
+
Events should be named in **past tense** to indicate something that happened:
|
328
|
+
|
329
|
+
✅ **Good**:
|
330
|
+
- `UserCreated`
|
331
|
+
- `OrderPlaced`
|
332
|
+
- `PaymentProcessed`
|
333
|
+
- `EmailSent`
|
334
|
+
- `InventoryUpdated`
|
335
|
+
|
336
|
+
❌ **Bad**:
|
337
|
+
- `CreateUser` (this is a command/action, not an event)
|
338
|
+
- `PlaceOrder` (command)
|
339
|
+
- `SendEmail` (command)
|
340
|
+
|
341
|
+
## Best Practices
|
342
|
+
|
343
|
+
### 1. Keep Events Immutable
|
344
|
+
|
345
|
+
Use `@dataclass(frozen=True)` to ensure events cannot be modified:
|
346
|
+
|
347
|
+
```python
|
348
|
+
@dataclass(frozen=True) # ✅ Immutable
|
349
|
+
class UserCreated(Event):
|
350
|
+
user_id: str
|
351
|
+
email: str
|
352
|
+
```
|
353
|
+
|
354
|
+
### 2. Include All Relevant Data
|
355
|
+
|
356
|
+
Events should contain all data needed by handlers:
|
357
|
+
|
358
|
+
```python
|
359
|
+
# ✅ Good - includes all relevant data
|
360
|
+
@dataclass(frozen=True)
|
361
|
+
class OrderPlaced(Event):
|
362
|
+
order_id: str
|
363
|
+
customer_id: str
|
364
|
+
items: List[OrderItem]
|
365
|
+
total_amount: Decimal
|
366
|
+
currency: str
|
367
|
+
|
368
|
+
# ❌ Bad - handlers need to fetch additional data
|
369
|
+
@dataclass(frozen=True)
|
370
|
+
class OrderPlaced(Event):
|
371
|
+
order_id: str # Handlers must fetch order details
|
372
|
+
```
|
373
|
+
|
374
|
+
### 3. One Event Per Domain Action
|
375
|
+
|
376
|
+
Create specific events for specific domain actions:
|
377
|
+
|
378
|
+
```python
|
379
|
+
# ✅ Good - specific events
|
380
|
+
@dataclass(frozen=True)
|
381
|
+
class UserEmailChanged(Event):
|
382
|
+
user_id: str
|
383
|
+
old_email: str
|
384
|
+
new_email: str
|
385
|
+
|
386
|
+
@dataclass(frozen=True)
|
387
|
+
class UserPasswordChanged(Event):
|
388
|
+
user_id: str
|
389
|
+
changed_at: datetime
|
390
|
+
|
391
|
+
# ❌ Bad - generic event
|
392
|
+
@dataclass(frozen=True)
|
393
|
+
class UserUpdated(Event):
|
394
|
+
user_id: str
|
395
|
+
changes: dict # Too generic
|
396
|
+
```
|
397
|
+
|
398
|
+
### 4. Handlers Should Be Idempotent
|
399
|
+
|
400
|
+
Handlers may be called multiple times (retries), so make them idempotent:
|
401
|
+
|
402
|
+
```python
|
403
|
+
@subscribe(UserCreated)
|
404
|
+
async def send_welcome_email(event: UserCreated):
|
405
|
+
# ✅ Check if email already sent
|
406
|
+
if await email_log.has_sent(event.user_id, 'welcome'):
|
407
|
+
return
|
408
|
+
|
409
|
+
await email_service.send(event.email, "Welcome!")
|
410
|
+
await email_log.record(event.user_id, 'welcome')
|
411
|
+
```
|
412
|
+
|
413
|
+
### 5. Don't Publish Events in Handlers
|
414
|
+
|
415
|
+
Avoid publishing events from within event handlers to prevent cascading complexity:
|
416
|
+
|
417
|
+
```python
|
418
|
+
# ❌ Bad - publishing from handler
|
419
|
+
@subscribe(UserCreated)
|
420
|
+
async def on_user_created(event: UserCreated):
|
421
|
+
bus = get_event_bus()
|
422
|
+
await bus.publish(WelcomeEmailSent(...)) # ❌ Cascading events
|
423
|
+
|
424
|
+
# ✅ Good - publish from domain logic
|
425
|
+
class CreateUser(Interactor[User]):
|
426
|
+
async def call(self, ...):
|
427
|
+
user = await repository.save(user)
|
428
|
+
|
429
|
+
# Publish all related events here
|
430
|
+
bus = get_event_bus()
|
431
|
+
await bus.publish(UserCreated(...))
|
432
|
+
await bus.publish(WelcomeEmailScheduled(...))
|
433
|
+
```
|
434
|
+
|
435
|
+
## Testing
|
436
|
+
|
437
|
+
### Testing Event Handlers
|
438
|
+
|
439
|
+
```python
|
440
|
+
import pytest
|
441
|
+
from vega.events import EventBus, set_event_bus
|
442
|
+
|
443
|
+
@pytest.fixture
|
444
|
+
def event_bus():
|
445
|
+
"""Create isolated event bus for tests"""
|
446
|
+
bus = EventBus()
|
447
|
+
set_event_bus(bus)
|
448
|
+
yield bus
|
449
|
+
# Cleanup
|
450
|
+
bus.clear_subscribers()
|
451
|
+
|
452
|
+
async def test_user_created_handler(event_bus):
|
453
|
+
"""Test that welcome email is sent"""
|
454
|
+
sent_emails = []
|
455
|
+
|
456
|
+
@event_bus.subscribe(UserCreated)
|
457
|
+
async def track_emails(event: UserCreated):
|
458
|
+
sent_emails.append(event.email)
|
459
|
+
|
460
|
+
# Publish event
|
461
|
+
await event_bus.publish(UserCreated(
|
462
|
+
user_id="123",
|
463
|
+
email="test@test.com",
|
464
|
+
name="Test User"
|
465
|
+
))
|
466
|
+
|
467
|
+
# Assert
|
468
|
+
assert "test@test.com" in sent_emails
|
469
|
+
```
|
470
|
+
|
471
|
+
### Mocking Event Bus
|
472
|
+
|
473
|
+
```python
|
474
|
+
from unittest.mock import AsyncMock
|
475
|
+
|
476
|
+
async def test_interactor_publishes_event():
|
477
|
+
"""Test that interactor publishes event"""
|
478
|
+
mock_bus = AsyncMock()
|
479
|
+
|
480
|
+
# Inject mock bus
|
481
|
+
set_event_bus(mock_bus)
|
482
|
+
|
483
|
+
# Execute interactor
|
484
|
+
user = await CreateUser(name="Test", email="test@test.com")
|
485
|
+
|
486
|
+
# Assert event was published
|
487
|
+
mock_bus.publish.assert_called_once()
|
488
|
+
event = mock_bus.publish.call_args[0][0]
|
489
|
+
assert isinstance(event, UserCreated)
|
490
|
+
assert event.email == "test@test.com"
|
491
|
+
```
|
492
|
+
|
493
|
+
## API Reference
|
494
|
+
|
495
|
+
### Event
|
496
|
+
|
497
|
+
Base class for all events.
|
498
|
+
|
499
|
+
**Properties**:
|
500
|
+
- `event_id: str` - Unique identifier (auto-generated UUID)
|
501
|
+
- `timestamp: datetime` - When event was created (auto-generated)
|
502
|
+
- `event_name: str` - Event class name
|
503
|
+
- `metadata: Dict[str, Any]` - Additional metadata
|
504
|
+
|
505
|
+
**Methods**:
|
506
|
+
- `add_metadata(key: str, value: Any)` - Add metadata to event
|
507
|
+
|
508
|
+
### EventBus
|
509
|
+
|
510
|
+
Central event bus for pub/sub.
|
511
|
+
|
512
|
+
**Methods**:
|
513
|
+
- `subscribe(event_type, handler, priority=0, retry_on_error=False, max_retries=3)` - Subscribe handler
|
514
|
+
- `unsubscribe(event_type, handler)` - Unsubscribe handler
|
515
|
+
- `publish(event)` - Publish event to all subscribers
|
516
|
+
- `publish_many(events)` - Publish multiple events
|
517
|
+
- `add_middleware(middleware)` - Add middleware
|
518
|
+
- `on_error(handler)` - Register error handler
|
519
|
+
- `clear_subscribers(event_type=None)` - Clear subscribers
|
520
|
+
- `get_subscriber_count(event_type)` - Get subscriber count
|
521
|
+
|
522
|
+
### Decorators
|
523
|
+
|
524
|
+
**`@subscribe(event_type, priority=0, retry_on_error=False, max_retries=3)`**
|
525
|
+
|
526
|
+
Subscribe function to event on global bus.
|
527
|
+
|
528
|
+
**`@event_handler(event_type, bus=None, priority=0, retry_on_error=False, max_retries=3)`**
|
529
|
+
|
530
|
+
Mark method as event handler with optional custom bus.
|
531
|
+
|
532
|
+
### Middleware
|
533
|
+
|
534
|
+
**Built-in Middleware**:
|
535
|
+
- `LoggingEventMiddleware` - Log all events
|
536
|
+
- `MetricsEventMiddleware` - Collect event metrics
|
537
|
+
- `ValidationEventMiddleware` - Validate events before publishing
|
538
|
+
- `EnrichmentEventMiddleware` - Auto-add metadata to events
|
539
|
+
|
540
|
+
**Custom Middleware**:
|
541
|
+
|
542
|
+
Extend `EventMiddleware` and implement:
|
543
|
+
- `async def before_publish(event: Event)` - Called before event is published
|
544
|
+
- `async def after_publish(event: Event)` - Called after handlers complete
|
545
|
+
|
546
|
+
## Performance Considerations
|
547
|
+
|
548
|
+
- **Async Handlers**: All handlers run concurrently (not sequentially)
|
549
|
+
- **Error Isolation**: Failed handlers don't block other handlers
|
550
|
+
- **Retry Overhead**: Use retry sparingly - exponential backoff adds delay
|
551
|
+
- **Middleware Order**: Middleware runs in order added - keep it lightweight
|
552
|
+
|
553
|
+
## Examples
|
554
|
+
|
555
|
+
See the [examples directory](../../examples/events/) for complete examples:
|
556
|
+
- Basic event publishing
|
557
|
+
- Event handlers with DI
|
558
|
+
- Middleware usage
|
559
|
+
- Testing strategies
|
560
|
+
- Integration with Interactors/Mediators
|
561
|
+
|
562
|
+
## License
|
563
|
+
|
564
|
+
Part of the Vega Framework - See LICENSE for details.
|