@stonyx/events 0.1.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.
- package/.claude/project-structure.md +421 -0
- package/.claude/settings.local.json +15 -0
- package/.git/config +15 -0
- package/.github/workflows/ci.yml +24 -0
- package/.github/workflows/publish.yml +94 -0
- package/.gitignore +9 -0
- package/.npmignore +2 -0
- package/LICENSE.md +203 -0
- package/README.md +73 -0
- package/config/environment.js +6 -0
- package/package.json +43 -0
- package/src/main.js +167 -0
- package/stonyx-bootstrap.cjs +9 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# @stonyx/events - Project Structure & Architecture
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
**@stonyx/events** is a lightweight pub/sub event system for the Stonyx framework. It provides a singleton-based event management system with error isolation, async support, and type-safe event registration.
|
|
6
|
+
|
|
7
|
+
### Core Purpose
|
|
8
|
+
- Provide application-wide event bus for decoupled communication
|
|
9
|
+
- Support async event handlers with error isolation
|
|
10
|
+
- Ensure type safety through event registration
|
|
11
|
+
- Maintain singleton pattern for shared event system
|
|
12
|
+
|
|
13
|
+
### Technology Stack
|
|
14
|
+
- **Runtime**: Node.js v24.13.0
|
|
15
|
+
- **Module System**: ES Modules
|
|
16
|
+
- **Testing**: QUnit 2.24.1 + Sinon 21.0.0
|
|
17
|
+
- **License**: Apache 2.0
|
|
18
|
+
|
|
19
|
+
## Architecture Overview
|
|
20
|
+
|
|
21
|
+
### Design Patterns
|
|
22
|
+
|
|
23
|
+
**Singleton Pattern**
|
|
24
|
+
- Single Events instance shared across the application
|
|
25
|
+
- Prevents multiple competing event systems
|
|
26
|
+
- Ensures all parts of the application use the same event bus
|
|
27
|
+
|
|
28
|
+
**Error Isolation**
|
|
29
|
+
- Errors in one handler don't affect other handlers
|
|
30
|
+
- All handlers run even if some fail
|
|
31
|
+
- Errors are logged to console.error but don't propagate
|
|
32
|
+
|
|
33
|
+
**Async Support**
|
|
34
|
+
- All event handlers can be async functions
|
|
35
|
+
- `emit()` waits for all handlers to complete
|
|
36
|
+
- Handlers run in parallel via Promise.all()
|
|
37
|
+
|
|
38
|
+
**Type Safety**
|
|
39
|
+
- Events must be registered with `setup()` before use
|
|
40
|
+
- Prevents typos and invalid event names
|
|
41
|
+
- Enforces explicit event declarations
|
|
42
|
+
|
|
43
|
+
## File Structure
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
stonyx-events/
|
|
47
|
+
├── .github/
|
|
48
|
+
│ └── workflows/
|
|
49
|
+
│ └── ci.yml # GitHub Actions CI pipeline
|
|
50
|
+
├── .claude/
|
|
51
|
+
│ ├── settings.local.json # Claude Code permissions
|
|
52
|
+
│ └── project-structure.md # This file
|
|
53
|
+
├── config/
|
|
54
|
+
│ └── environment.js # Environment configuration
|
|
55
|
+
├── src/
|
|
56
|
+
│ └── main.js # Events class implementation
|
|
57
|
+
├── test/
|
|
58
|
+
│ └── unit/
|
|
59
|
+
│ └── events-test.js # QUnit tests for Events
|
|
60
|
+
├── .gitignore # Git ignore patterns
|
|
61
|
+
├── .npmignore # NPM ignore patterns
|
|
62
|
+
├── .nvmrc # Node version specification
|
|
63
|
+
├── LICENSE.md # Apache 2.0 license
|
|
64
|
+
├── README.md # User-facing documentation
|
|
65
|
+
├── package.json # NPM package configuration
|
|
66
|
+
└── stonyx-bootstrap.cjs # CommonJS bootstrap for testing
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Core Components Deep Dive
|
|
70
|
+
|
|
71
|
+
### Events Class (`src/main.js`)
|
|
72
|
+
|
|
73
|
+
The Events class is the heart of the module, providing all pub/sub functionality.
|
|
74
|
+
|
|
75
|
+
#### Properties
|
|
76
|
+
|
|
77
|
+
**`static instance`** (Events|null)
|
|
78
|
+
- Singleton instance reference
|
|
79
|
+
- Ensures only one Events instance exists
|
|
80
|
+
|
|
81
|
+
**`events`** (Map<string, Set<Function>>)
|
|
82
|
+
- Map of event names to their subscriber sets
|
|
83
|
+
- Uses Set to prevent duplicate subscriptions
|
|
84
|
+
- Automatically created when events are registered
|
|
85
|
+
|
|
86
|
+
**`registeredEvents`** (Set<string>)
|
|
87
|
+
- Set of all registered event names
|
|
88
|
+
- Used to validate subscriptions
|
|
89
|
+
- Prevents subscribing to unregistered events
|
|
90
|
+
|
|
91
|
+
#### Methods
|
|
92
|
+
|
|
93
|
+
**`setup(eventNames: string[])`**
|
|
94
|
+
- Register available event names
|
|
95
|
+
- Must be called before subscribing to events
|
|
96
|
+
- Validates that all names are strings
|
|
97
|
+
- Creates empty subscriber sets for each event
|
|
98
|
+
|
|
99
|
+
**`subscribe(event: string, callback: Function)`**
|
|
100
|
+
- Subscribe to an event
|
|
101
|
+
- Throws if event is not registered
|
|
102
|
+
- Throws if callback is not a function
|
|
103
|
+
- Returns an unsubscribe function
|
|
104
|
+
- Callback signature: `(...args: any[]) => void | Promise<void>`
|
|
105
|
+
|
|
106
|
+
**`once(event: string, callback: Function)`**
|
|
107
|
+
- Subscribe to an event once
|
|
108
|
+
- Auto-unsubscribes after first emit
|
|
109
|
+
- Wraps callback in a self-removing wrapper
|
|
110
|
+
- Returns an unsubscribe function
|
|
111
|
+
- Supports async callbacks
|
|
112
|
+
|
|
113
|
+
**`emit(event: string, ...args: any[])`**
|
|
114
|
+
- Emit an event with arguments
|
|
115
|
+
- Async function that waits for all handlers
|
|
116
|
+
- Runs all handlers in parallel
|
|
117
|
+
- Isolates errors per handler
|
|
118
|
+
- Does nothing if event has no subscribers
|
|
119
|
+
|
|
120
|
+
**`unsubscribe(event: string, callback: Function)`**
|
|
121
|
+
- Remove a specific subscription
|
|
122
|
+
- Safe to call multiple times
|
|
123
|
+
- Does nothing if event or callback doesn't exist
|
|
124
|
+
|
|
125
|
+
**`clear(event: string)`**
|
|
126
|
+
- Remove all subscriptions for an event
|
|
127
|
+
- Useful for cleanup between tests
|
|
128
|
+
- Event remains registered
|
|
129
|
+
|
|
130
|
+
**`reset()`**
|
|
131
|
+
- Clear all subscriptions and registrations
|
|
132
|
+
- Resets to pristine state
|
|
133
|
+
- Essential for test isolation
|
|
134
|
+
|
|
135
|
+
## Dependencies & Integration
|
|
136
|
+
|
|
137
|
+
### Direct Dependencies
|
|
138
|
+
|
|
139
|
+
**stonyx** (file:../stonyx)
|
|
140
|
+
- Core Stonyx framework
|
|
141
|
+
- Provides base configuration and bootstrapping
|
|
142
|
+
- Required for module initialization
|
|
143
|
+
|
|
144
|
+
### Dev Dependencies
|
|
145
|
+
|
|
146
|
+
**@stonyx/utils** (file:../stonyx-utils)
|
|
147
|
+
- Utility functions (currently unused but available)
|
|
148
|
+
|
|
149
|
+
**qunit** (^2.24.1)
|
|
150
|
+
- Testing framework
|
|
151
|
+
- Runs via CLI with stonyx-bootstrap.cjs
|
|
152
|
+
|
|
153
|
+
**sinon** (^21.0.0)
|
|
154
|
+
- Mocking/stubbing library
|
|
155
|
+
- Available for advanced testing scenarios
|
|
156
|
+
|
|
157
|
+
### Modules That Depend on @stonyx/events
|
|
158
|
+
|
|
159
|
+
**@stonyx/orm**
|
|
160
|
+
- Uses Events for CRUD lifecycle hooks
|
|
161
|
+
- Fires events: create:before, create:after, update:before, update:after, delete:before, delete:after
|
|
162
|
+
- Provides `ormEvents` singleton instance
|
|
163
|
+
|
|
164
|
+
## Code Patterns & Conventions
|
|
165
|
+
|
|
166
|
+
### Module System
|
|
167
|
+
- ES Modules throughout
|
|
168
|
+
- Default export for Events class
|
|
169
|
+
- No named exports (single-purpose module)
|
|
170
|
+
|
|
171
|
+
### Error Handling
|
|
172
|
+
- Validation errors throw immediately
|
|
173
|
+
- Runtime errors in handlers are caught and logged
|
|
174
|
+
- No silent failures
|
|
175
|
+
|
|
176
|
+
### Logging
|
|
177
|
+
- Environment variable: `EVENTS_LOG` (currently unused in implementation)
|
|
178
|
+
- Configuration prepared in `config/environment.js`
|
|
179
|
+
- Future: Could add debug logging for emit/subscribe events
|
|
180
|
+
|
|
181
|
+
### Naming Conventions
|
|
182
|
+
- Event names: camelCase strings (e.g., 'userLogin', 'dataChange')
|
|
183
|
+
- Method names: lowercase verbs (setup, subscribe, emit)
|
|
184
|
+
- Private instance variables: None (all properties public for testing)
|
|
185
|
+
|
|
186
|
+
## Testing Guidelines
|
|
187
|
+
|
|
188
|
+
### Test Structure
|
|
189
|
+
- All tests in `test/unit/events-test.js`
|
|
190
|
+
- QUnit module: `[Unit] Events`
|
|
191
|
+
- 18 test cases covering all functionality
|
|
192
|
+
- Each test calls `events.reset()` before and after
|
|
193
|
+
|
|
194
|
+
### Test Patterns
|
|
195
|
+
|
|
196
|
+
**Singleton Isolation**
|
|
197
|
+
```javascript
|
|
198
|
+
const events = new Events();
|
|
199
|
+
events.reset(); // Clear any previous state
|
|
200
|
+
// ... test code ...
|
|
201
|
+
events.reset(); // Clean up after test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Async Testing**
|
|
205
|
+
```javascript
|
|
206
|
+
test('description', async function (assert) {
|
|
207
|
+
// Use async/await for emit()
|
|
208
|
+
await events.emit('event');
|
|
209
|
+
assert.ok(condition);
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Error Suppression**
|
|
214
|
+
```javascript
|
|
215
|
+
const originalConsoleError = console.error;
|
|
216
|
+
console.error = () => {}; // Suppress expected errors
|
|
217
|
+
// ... test code ...
|
|
218
|
+
console.error = originalConsoleError; // Restore
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Coverage Areas
|
|
222
|
+
1. Event registration (setup)
|
|
223
|
+
2. Subscription management (subscribe, unsubscribe)
|
|
224
|
+
3. Event emission (emit)
|
|
225
|
+
4. One-time subscriptions (once)
|
|
226
|
+
5. Error isolation
|
|
227
|
+
6. Async support
|
|
228
|
+
7. Singleton behavior
|
|
229
|
+
8. Edge cases (unregistered events, no subscribers)
|
|
230
|
+
|
|
231
|
+
## Extension Points
|
|
232
|
+
|
|
233
|
+
While the Events system is intentionally minimal, potential future enhancements include:
|
|
234
|
+
|
|
235
|
+
### Event Priorities
|
|
236
|
+
- Add priority levels for subscribers
|
|
237
|
+
- Execute high-priority handlers first
|
|
238
|
+
- Use case: Logging before business logic
|
|
239
|
+
|
|
240
|
+
### Wildcard Events
|
|
241
|
+
- Support pattern matching (e.g., 'user:*')
|
|
242
|
+
- Subscribe to multiple events at once
|
|
243
|
+
- Use case: Debugging, logging all events
|
|
244
|
+
|
|
245
|
+
### Event History
|
|
246
|
+
- Optional recording of emitted events
|
|
247
|
+
- Replay functionality for debugging
|
|
248
|
+
- Use case: Time-travel debugging, audit logs
|
|
249
|
+
|
|
250
|
+
### Middleware
|
|
251
|
+
- Pre/post-emit hooks
|
|
252
|
+
- Event transformation pipeline
|
|
253
|
+
- Use case: Validation, logging, metrics
|
|
254
|
+
|
|
255
|
+
## Configuration Reference
|
|
256
|
+
|
|
257
|
+
### Environment Variables
|
|
258
|
+
|
|
259
|
+
**EVENTS_LOG**
|
|
260
|
+
- Type: Boolean (truthy/falsy)
|
|
261
|
+
- Default: `false`
|
|
262
|
+
- Purpose: Enable debug logging (not yet implemented)
|
|
263
|
+
- Usage: `EVENTS_LOG=true npm test`
|
|
264
|
+
|
|
265
|
+
### config/environment.js
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
{
|
|
269
|
+
log: EVENTS_LOG ?? false, // Debug logging flag
|
|
270
|
+
logColor: '#888', // Color for log output
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Currently, these config values are prepared but not used in the Events implementation. They provide a foundation for future debug logging features.
|
|
275
|
+
|
|
276
|
+
## Package Exports
|
|
277
|
+
|
|
278
|
+
### Main Export
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
import Events from '@stonyx/events';
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The package exports a single default export: the Events class.
|
|
285
|
+
|
|
286
|
+
### Usage Pattern
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// Create/get singleton instance
|
|
290
|
+
const events = new Events();
|
|
291
|
+
|
|
292
|
+
// Register events
|
|
293
|
+
events.setup(['userLogin', 'userLogout']);
|
|
294
|
+
|
|
295
|
+
// Subscribe
|
|
296
|
+
events.subscribe('userLogin', (user) => {
|
|
297
|
+
console.log('User logged in:', user);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Emit (fire-and-forget, don't await)
|
|
301
|
+
events.emit('userLogin', { id: 1, name: 'Alice' });
|
|
302
|
+
|
|
303
|
+
// Or await if you need to ensure all handlers complete
|
|
304
|
+
await events.emit('userLogin', { id: 1, name: 'Alice' });
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Development Workflow
|
|
308
|
+
|
|
309
|
+
### Local Development
|
|
310
|
+
|
|
311
|
+
1. **Install dependencies**
|
|
312
|
+
```bash
|
|
313
|
+
cd /Users/mstonepc/Repos/abofs
|
|
314
|
+
./linker.sh local # Link all local Stonyx modules
|
|
315
|
+
cd stonyx-events
|
|
316
|
+
npm install
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
2. **Run tests**
|
|
320
|
+
```bash
|
|
321
|
+
npm test
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
3. **Make changes**
|
|
325
|
+
- Edit `src/main.js` for implementation
|
|
326
|
+
- Edit `test/unit/events-test.js` for tests
|
|
327
|
+
- Follow TDD: Write failing test, implement feature, verify
|
|
328
|
+
|
|
329
|
+
### Test-Driven Development
|
|
330
|
+
|
|
331
|
+
The module follows TDD principles:
|
|
332
|
+
1. Write a failing test for new functionality
|
|
333
|
+
2. Implement the minimum code to pass the test
|
|
334
|
+
3. Refactor while keeping tests green
|
|
335
|
+
4. Ensure all tests pass before committing
|
|
336
|
+
|
|
337
|
+
### CI/CD Pipeline
|
|
338
|
+
|
|
339
|
+
**GitHub Actions** (`.github/workflows/ci.yml`)
|
|
340
|
+
- Triggers on PRs to `dev` and `main` branches
|
|
341
|
+
- Uses pnpm for package management
|
|
342
|
+
- Runs `pnpm test` to verify all tests pass
|
|
343
|
+
- Cancels previous runs on new commits
|
|
344
|
+
|
|
345
|
+
### Publishing Workflow
|
|
346
|
+
|
|
347
|
+
1. **Version bump**
|
|
348
|
+
```bash
|
|
349
|
+
npm version patch|minor|major
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
2. **Publish to NPM**
|
|
353
|
+
```bash
|
|
354
|
+
npm publish
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
3. **Update dependents**
|
|
358
|
+
- Update `@stonyx/orm` to use published version
|
|
359
|
+
- Update other modules as needed
|
|
360
|
+
|
|
361
|
+
## Common Pitfalls & Gotchas
|
|
362
|
+
|
|
363
|
+
### Singleton Behavior
|
|
364
|
+
- **Issue**: Creating new Events() always returns the same instance
|
|
365
|
+
- **Solution**: Don't rely on local variables; singleton ensures shared state
|
|
366
|
+
- **Testing**: Always call `reset()` between tests to clear state
|
|
367
|
+
|
|
368
|
+
### Async Handling
|
|
369
|
+
- **Issue**: Forgetting to await emit() can lead to race conditions
|
|
370
|
+
- **Solution**: Use `await events.emit()` when order matters
|
|
371
|
+
- **Note**: For fire-and-forget, omit await (e.g., in synchronous createRecord)
|
|
372
|
+
|
|
373
|
+
### Error Isolation
|
|
374
|
+
- **Issue**: One handler error doesn't stop other handlers, but logs to console
|
|
375
|
+
- **Solution**: Expect console.error output in tests with failing handlers
|
|
376
|
+
- **Testing**: Suppress console.error when testing error isolation
|
|
377
|
+
|
|
378
|
+
### Event Registration
|
|
379
|
+
- **Issue**: Subscribing to unregistered events throws an error
|
|
380
|
+
- **Solution**: Always call `setup()` with event names before subscribing
|
|
381
|
+
- **Why**: Prevents typos and ensures explicit event declarations
|
|
382
|
+
|
|
383
|
+
### Set-based Subscriptions
|
|
384
|
+
- **Issue**: Adding the same callback function twice only subscribes once
|
|
385
|
+
- **Solution**: Use different function instances for multiple subscriptions
|
|
386
|
+
- **Note**: This is by design to prevent accidental duplicate subscriptions
|
|
387
|
+
|
|
388
|
+
## Future Enhancement Opportunities
|
|
389
|
+
|
|
390
|
+
### Performance Optimizations
|
|
391
|
+
- Lazy initialization of event subscriber sets
|
|
392
|
+
- Debouncing/throttling for high-frequency events
|
|
393
|
+
- Event batching for bulk updates
|
|
394
|
+
|
|
395
|
+
### Developer Experience
|
|
396
|
+
- TypeScript definitions for type-safe event names and payloads
|
|
397
|
+
- Debug mode with detailed logging
|
|
398
|
+
- Event visualization/monitoring tools
|
|
399
|
+
|
|
400
|
+
### Advanced Features
|
|
401
|
+
- Event namespacing (e.g., 'user:login:success')
|
|
402
|
+
- Event bubbling/capturing (DOM-like event propagation)
|
|
403
|
+
- Async middleware pipeline
|
|
404
|
+
- Event replay for debugging
|
|
405
|
+
|
|
406
|
+
### Integration
|
|
407
|
+
- Automatic event logging to @stonyx/logger (when it exists)
|
|
408
|
+
- Metrics/telemetry integration
|
|
409
|
+
- Event persistence for audit trails
|
|
410
|
+
|
|
411
|
+
## Related Resources
|
|
412
|
+
|
|
413
|
+
- [Stonyx Framework](https://github.com/abofs/stonyx)
|
|
414
|
+
- [QUnit Documentation](https://qunitjs.com/)
|
|
415
|
+
- [Sinon.js Documentation](https://sinonjs.org/)
|
|
416
|
+
- [Node.js ES Modules](https://nodejs.org/api/esm.html)
|
|
417
|
+
- [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0)
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
This document is maintained by the Stonyx team and should be updated whenever architectural changes are made to the @stonyx/events module.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(node --version:*)",
|
|
5
|
+
"Bash(source ~/.nvm/nvm.sh)",
|
|
6
|
+
"Bash(nvm use)",
|
|
7
|
+
"Bash(pnpm install:*)",
|
|
8
|
+
"Bash(pnpm store:*)",
|
|
9
|
+
"Bash(npm view:*)",
|
|
10
|
+
"Bash(npm publish:*)",
|
|
11
|
+
"Bash(npm version:*)",
|
|
12
|
+
"Bash(curl:*)"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
package/.git/config
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[core]
|
|
2
|
+
repositoryformatversion = 0
|
|
3
|
+
filemode = true
|
|
4
|
+
bare = false
|
|
5
|
+
logallrefupdates = true
|
|
6
|
+
ignorecase = true
|
|
7
|
+
precomposeunicode = true
|
|
8
|
+
[remote "origin"]
|
|
9
|
+
url = git@github.com:abofs/stonyx-events.git
|
|
10
|
+
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
11
|
+
[branch "stone/initial"]
|
|
12
|
+
remote = origin
|
|
13
|
+
merge = refs/heads/stone/initial
|
|
14
|
+
gk-last-accessed = 2026-02-01T19:49:07.208Z
|
|
15
|
+
gk-last-modified = 2026-02-01T19:49:07.208Z
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- dev
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.head_ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: pnpm/action-setup@v4
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '24.13.0'
|
|
22
|
+
cache: 'pnpm'
|
|
23
|
+
- run: pnpm install --frozen-lockfile
|
|
24
|
+
- run: pnpm test
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version-type:
|
|
7
|
+
description: 'Version type'
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- alpha
|
|
12
|
+
- patch
|
|
13
|
+
- minor
|
|
14
|
+
- major
|
|
15
|
+
custom-version:
|
|
16
|
+
description: 'Custom version (optional, overrides version-type)'
|
|
17
|
+
required: false
|
|
18
|
+
type: string
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
id-token: write # Required for npm provenance
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
publish:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout code
|
|
30
|
+
uses: actions/checkout@v3
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 0
|
|
33
|
+
|
|
34
|
+
- name: Setup pnpm
|
|
35
|
+
uses: pnpm/action-setup@v4
|
|
36
|
+
with:
|
|
37
|
+
version: 9
|
|
38
|
+
|
|
39
|
+
- name: Set up Node.js
|
|
40
|
+
uses: actions/setup-node@v3
|
|
41
|
+
with:
|
|
42
|
+
node-version: 24.13.0
|
|
43
|
+
cache: 'pnpm'
|
|
44
|
+
registry-url: 'https://registry.npmjs.org'
|
|
45
|
+
|
|
46
|
+
- name: Install dependencies
|
|
47
|
+
run: pnpm install --frozen-lockfile
|
|
48
|
+
|
|
49
|
+
- name: Run tests
|
|
50
|
+
run: pnpm test
|
|
51
|
+
|
|
52
|
+
- name: Configure git
|
|
53
|
+
run: |
|
|
54
|
+
git config user.name "github-actions[bot]"
|
|
55
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
56
|
+
|
|
57
|
+
- name: Bump version (custom)
|
|
58
|
+
if: ${{ github.event.inputs.custom-version != '' }}
|
|
59
|
+
run: npm version ${{ github.event.inputs.custom-version }} --no-git-tag-version
|
|
60
|
+
|
|
61
|
+
- name: Bump version (prerelease alpha)
|
|
62
|
+
if: ${{ github.event.inputs.version-type == 'alpha' && github.event.inputs.custom-version == '' }}
|
|
63
|
+
run: npm version prerelease --preid=alpha --no-git-tag-version
|
|
64
|
+
|
|
65
|
+
- name: Bump version (standard)
|
|
66
|
+
if: ${{ github.event.inputs.version-type != 'alpha' && github.event.inputs.custom-version == '' }}
|
|
67
|
+
run: npm version ${{ github.event.inputs.version-type }} --no-git-tag-version
|
|
68
|
+
|
|
69
|
+
- name: Get package version
|
|
70
|
+
id: package-version
|
|
71
|
+
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
|
72
|
+
|
|
73
|
+
- name: Publish to NPM (alpha)
|
|
74
|
+
if: ${{ github.event.inputs.version-type == 'alpha' || contains(steps.package-version.outputs.version, 'alpha') }}
|
|
75
|
+
run: npm publish --tag alpha --access public
|
|
76
|
+
- name: Publish to NPM (stable)
|
|
77
|
+
if: ${{ github.event.inputs.version-type != 'alpha' && !contains(steps.package-version.outputs.version, 'alpha') }}
|
|
78
|
+
run: npm publish --access public
|
|
79
|
+
- name: Commit version bump
|
|
80
|
+
run: |
|
|
81
|
+
git add package.json
|
|
82
|
+
git commit -m "chore: release v${{ steps.package-version.outputs.version }}"
|
|
83
|
+
git tag v${{ steps.package-version.outputs.version }}
|
|
84
|
+
git push origin main --tags
|
|
85
|
+
|
|
86
|
+
- name: Create GitHub Release
|
|
87
|
+
uses: actions/create-release@v1
|
|
88
|
+
env:
|
|
89
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
90
|
+
with:
|
|
91
|
+
tag_name: v${{ steps.package-version.outputs.version }}
|
|
92
|
+
release_name: v${{ steps.package-version.outputs.version }}
|
|
93
|
+
draft: false
|
|
94
|
+
prerelease: ${{ contains(steps.package-version.outputs.version, 'alpha') }}
|
package/.gitignore
ADDED
package/.npmignore
ADDED
package/LICENSE.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [2025] [Stone Costa]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
203
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @stonyx/events
|
|
2
|
+
|
|
3
|
+
A lightweight pub/sub event system for the Stonyx framework. Provides singleton event management with error isolation, async support, and type-safe event registration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Event Registration**: Events must be registered with `setup()` before use, ensuring type safety
|
|
8
|
+
- **Singleton Pattern**: Single Events instance shared across your application
|
|
9
|
+
- **Async Support**: Event handlers can be async functions
|
|
10
|
+
- **Error Isolation**: Errors in one handler don't affect others or prevent other handlers from running
|
|
11
|
+
- **One-time Subscriptions**: Use `once()` for handlers that should only fire once
|
|
12
|
+
- **Unsubscribe**: All subscriptions return an unsubscribe function for cleanup
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @stonyx/events
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import Events from '@stonyx/events';
|
|
24
|
+
|
|
25
|
+
const events = new Events();
|
|
26
|
+
|
|
27
|
+
// Register available events
|
|
28
|
+
events.setup(['userLogin', 'userLogout', 'dataChange']);
|
|
29
|
+
|
|
30
|
+
// Subscribe to events
|
|
31
|
+
events.subscribe('userLogin', (user) => {
|
|
32
|
+
console.log(`${user.name} logged in`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Subscribe once (auto-unsubscribe after first fire)
|
|
36
|
+
events.once('dataChange', () => {
|
|
37
|
+
console.log('Data changed for the first time');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Emit events
|
|
41
|
+
events.emit('userLogin', { name: 'Alice' });
|
|
42
|
+
|
|
43
|
+
// Unsubscribe
|
|
44
|
+
const unsub = events.subscribe('userLogout', handler);
|
|
45
|
+
unsub(); // Remove subscription
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API Reference
|
|
49
|
+
|
|
50
|
+
| Method | Parameters | Description |
|
|
51
|
+
|--------|-----------|-------------|
|
|
52
|
+
| `setup()` | `eventNames: string[]` | Register available event names. Events must be registered before subscribing. |
|
|
53
|
+
| `subscribe()` | `event: string, callback: Function` | Subscribe to an event. Returns an unsubscribe function. |
|
|
54
|
+
| `once()` | `event: string, callback: Function` | Subscribe to an event once. Auto-unsubscribes after first emit. |
|
|
55
|
+
| `emit()` | `event: string, ...args` | Trigger an event with arguments. Async function that calls all subscribers. |
|
|
56
|
+
| `unsubscribe()` | `event: string, callback: Function` | Remove a specific subscription. |
|
|
57
|
+
| `clear()` | `event: string` | Remove all subscriptions for an event. |
|
|
58
|
+
| `reset()` | none | Clear all subscriptions and events. Useful for testing. |
|
|
59
|
+
|
|
60
|
+
## How It Works
|
|
61
|
+
|
|
62
|
+
The Events class provides a lightweight pub/sub system with the following features:
|
|
63
|
+
|
|
64
|
+
- **Event Registration**: Events must be registered with `setup()` before use, ensuring type safety
|
|
65
|
+
- **Singleton Pattern**: Single Events instance shared across your application
|
|
66
|
+
- **Async Support**: Event handlers can be async functions
|
|
67
|
+
- **Error Isolation**: Errors in one handler don't affect others or prevent other handlers from running
|
|
68
|
+
- **One-time Subscriptions**: Use `once()` for handlers that should only fire once
|
|
69
|
+
- **Unsubscribe**: All subscriptions return an unsubscribe function for cleanup
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
Apache — do what you want, just keep attribution.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stonyx/events",
|
|
3
|
+
"keywords": [
|
|
4
|
+
"stonyx-module"
|
|
5
|
+
],
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"description": "Lightweight pub/sub event system for the Stonyx framework",
|
|
8
|
+
"main": "src/main.js",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"*"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/main.js"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/abofs/stonyx-events.git"
|
|
22
|
+
},
|
|
23
|
+
"author": "Stone Costa",
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"contributors": [
|
|
26
|
+
"Stone Costa <stone.costa@synamicd.com>"
|
|
27
|
+
],
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/abofs/stonyx-events/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/abofs/stonyx-events#readme",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@stonyx/utils": "file:../stonyx-utils",
|
|
34
|
+
"qunit": "^2.24.1",
|
|
35
|
+
"sinon": "^21.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"stonyx": "file:../stonyx"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "qunit --require ./stonyx-bootstrap.cjs"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/main.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events - A lightweight pub/sub event system
|
|
3
|
+
*
|
|
4
|
+
* Provides event subscription and emission with support for:
|
|
5
|
+
* - One-time subscriptions (once)
|
|
6
|
+
* - Async event handlers
|
|
7
|
+
* - Error isolation per handler
|
|
8
|
+
* - Singleton pattern
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class Events {
|
|
12
|
+
/**
|
|
13
|
+
* Singleton instance
|
|
14
|
+
* @type {Events}
|
|
15
|
+
*/
|
|
16
|
+
static instance = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a new Events instance
|
|
20
|
+
*/
|
|
21
|
+
constructor() {
|
|
22
|
+
if (Events.instance) {
|
|
23
|
+
return Events.instance;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Map of event names to their subscribers
|
|
28
|
+
* @type {Map<string, Set<Function>>}
|
|
29
|
+
*/
|
|
30
|
+
this.events = new Map();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set of registered event names
|
|
34
|
+
* @type {Set<string>}
|
|
35
|
+
*/
|
|
36
|
+
this.registeredEvents = new Set();
|
|
37
|
+
|
|
38
|
+
Events.instance = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register available event names
|
|
43
|
+
* @param {string[]} eventNames - Array of event names to register
|
|
44
|
+
*/
|
|
45
|
+
setup(eventNames) {
|
|
46
|
+
if (!Array.isArray(eventNames)) {
|
|
47
|
+
throw new Error('setup() requires an array of event names');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const eventName of eventNames) {
|
|
51
|
+
if (typeof eventName !== 'string') {
|
|
52
|
+
throw new Error('Event names must be strings');
|
|
53
|
+
}
|
|
54
|
+
this.registeredEvents.add(eventName);
|
|
55
|
+
if (!this.events.has(eventName)) {
|
|
56
|
+
this.events.set(eventName, new Set());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to an event
|
|
63
|
+
* @param {string} event - Event name to subscribe to
|
|
64
|
+
* @param {Function} callback - Handler function to call when event fires
|
|
65
|
+
* @returns {Function} Unsubscribe function
|
|
66
|
+
* @throws {Error} If event is not registered
|
|
67
|
+
*/
|
|
68
|
+
subscribe(event, callback) {
|
|
69
|
+
if (!this.registeredEvents.has(event)) {
|
|
70
|
+
throw new Error(`Event "${event}" is not registered. Call setup() first.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof callback !== 'function') {
|
|
74
|
+
throw new Error('Callback must be a function');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const subscribers = this.events.get(event);
|
|
78
|
+
subscribers.add(callback);
|
|
79
|
+
|
|
80
|
+
// Return unsubscribe function
|
|
81
|
+
return () => this.unsubscribe(event, callback);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Subscribe to an event once (auto-unsubscribe after first emit)
|
|
86
|
+
* @param {string} event - Event name to subscribe to
|
|
87
|
+
* @param {Function} callback - Handler function to call when event fires
|
|
88
|
+
* @returns {Function} Unsubscribe function
|
|
89
|
+
* @throws {Error} If event is not registered
|
|
90
|
+
*/
|
|
91
|
+
once(event, callback) {
|
|
92
|
+
if (!this.registeredEvents.has(event)) {
|
|
93
|
+
throw new Error(`Event "${event}" is not registered. Call setup() first.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof callback !== 'function') {
|
|
97
|
+
throw new Error('Callback must be a function');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const wrapper = async (...args) => {
|
|
101
|
+
this.unsubscribe(event, wrapper);
|
|
102
|
+
await callback(...args);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return this.subscribe(event, wrapper);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Unsubscribe from an event
|
|
110
|
+
* @param {string} event - Event name to unsubscribe from
|
|
111
|
+
* @param {Function} callback - Handler function to remove
|
|
112
|
+
*/
|
|
113
|
+
unsubscribe(event, callback) {
|
|
114
|
+
if (!this.events.has(event)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const subscribers = this.events.get(event);
|
|
119
|
+
subscribers.delete(callback);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Emit an event with arguments
|
|
124
|
+
* Executes all subscribed handlers asynchronously with error isolation
|
|
125
|
+
* @param {string} event - Event name to emit
|
|
126
|
+
* @param {...any} args - Arguments to pass to handlers
|
|
127
|
+
*/
|
|
128
|
+
async emit(event, ...args) {
|
|
129
|
+
if (!this.events.has(event)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const subscribers = this.events.get(event);
|
|
134
|
+
|
|
135
|
+
// Execute all handlers with error isolation
|
|
136
|
+
const promises = Array.from(subscribers).map(async (callback) => {
|
|
137
|
+
try {
|
|
138
|
+
await callback(...args);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`Error in event handler for "${event}":`, error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await Promise.all(promises);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear all subscriptions for an event
|
|
149
|
+
* @param {string} event - Event name to clear
|
|
150
|
+
*/
|
|
151
|
+
clear(event) {
|
|
152
|
+
if (this.events.has(event)) {
|
|
153
|
+
this.events.get(event).clear();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Reset the entire event system (clear all subscriptions)
|
|
159
|
+
* Useful for testing
|
|
160
|
+
*/
|
|
161
|
+
reset() {
|
|
162
|
+
this.events.clear();
|
|
163
|
+
this.registeredEvents.clear();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default Events;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commonJS Bootstrap loading - Stonyx must be loaded first, prior to the rest of the application
|
|
3
|
+
*/
|
|
4
|
+
const { default:Stonyx } = require('stonyx');
|
|
5
|
+
const { default:config } = require('./config/environment.js');
|
|
6
|
+
|
|
7
|
+
new Stonyx(config, __dirname);
|
|
8
|
+
|
|
9
|
+
module.exports = Stonyx;
|