@stonyx/events 0.1.1-beta.4 → 0.1.1-beta.40

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/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![CI](https://github.com/abofs/stonyx-events/actions/workflows/ci.yml/badge.svg)](https://github.com/abofs/stonyx-events/actions/workflows/ci.yml)
2
+ [![npm version](https://img.shields.io/npm/v/@stonyx/events.svg)](https://www.npmjs.com/package/@stonyx/events)
3
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+
1
5
  # @stonyx/events
2
6
 
3
7
  A lightweight pub/sub event system for the Stonyx framework. Provides singleton event management with error isolation, async support, and type-safe event registration.
package/dist/main.d.ts ADDED
@@ -0,0 +1,31 @@
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
+ export type EventCallback = (...args: unknown[]) => void | Promise<void>;
11
+ declare class Events {
12
+ static instance: Events | null;
13
+ events: Map<string, Set<EventCallback>>;
14
+ registeredEvents: Set<string>;
15
+ constructor();
16
+ setup(eventNames: string[]): void;
17
+ subscribe(event: string, callback: EventCallback): () => void;
18
+ once(event: string, callback: EventCallback): () => void;
19
+ unsubscribe(event: string, callback: EventCallback): void;
20
+ emit(event: string, ...args: unknown[]): Promise<void>;
21
+ clear(event: string): void;
22
+ reset(): void;
23
+ }
24
+ export default Events;
25
+ export declare const setup: (eventNames: string[]) => void;
26
+ export declare const subscribe: (event: string, callback: EventCallback) => (() => void);
27
+ export declare const once: (event: string, callback: EventCallback) => (() => void);
28
+ export declare const unsubscribe: (event: string, callback: EventCallback) => void;
29
+ export declare const emit: (event: string, ...args: unknown[]) => Promise<void>;
30
+ export declare const clear: (event: string) => void;
31
+ export declare const reset: () => void;
package/dist/main.js ADDED
@@ -0,0 +1,105 @@
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
+ class Events {
11
+ static instance = null;
12
+ events = new Map();
13
+ registeredEvents = new Set();
14
+ constructor() {
15
+ if (Events.instance) {
16
+ return Events.instance;
17
+ }
18
+ Events.instance = this;
19
+ }
20
+ setup(eventNames) {
21
+ if (!Array.isArray(eventNames)) {
22
+ throw new Error('setup() requires an array of event names');
23
+ }
24
+ for (const eventName of eventNames) {
25
+ if (typeof eventName !== 'string') {
26
+ throw new Error('Event names must be strings');
27
+ }
28
+ this.registeredEvents.add(eventName);
29
+ if (!this.events.has(eventName)) {
30
+ this.events.set(eventName, new Set());
31
+ }
32
+ }
33
+ }
34
+ subscribe(event, callback) {
35
+ if (!this.registeredEvents.has(event)) {
36
+ throw new Error(`Event "${event}" is not registered. Call setup() first.`);
37
+ }
38
+ if (typeof callback !== 'function') {
39
+ throw new Error('Callback must be a function');
40
+ }
41
+ const subscribers = this.events.get(event);
42
+ if (!subscribers)
43
+ throw new Error(`Event "${event}" subscribers not found in events Map`);
44
+ subscribers.add(callback);
45
+ // Return unsubscribe function
46
+ return () => this.unsubscribe(event, callback);
47
+ }
48
+ once(event, callback) {
49
+ if (!this.registeredEvents.has(event)) {
50
+ throw new Error(`Event "${event}" is not registered. Call setup() first.`);
51
+ }
52
+ if (typeof callback !== 'function') {
53
+ throw new Error('Callback must be a function');
54
+ }
55
+ const wrapper = async (...args) => {
56
+ this.unsubscribe(event, wrapper);
57
+ await callback(...args);
58
+ };
59
+ return this.subscribe(event, wrapper);
60
+ }
61
+ unsubscribe(event, callback) {
62
+ if (!this.events.has(event)) {
63
+ return;
64
+ }
65
+ const subscribers = this.events.get(event);
66
+ if (subscribers)
67
+ subscribers.delete(callback);
68
+ }
69
+ async emit(event, ...args) {
70
+ if (!this.events.has(event)) {
71
+ return;
72
+ }
73
+ const subscribers = this.events.get(event);
74
+ if (!subscribers)
75
+ return;
76
+ // Execute all handlers with error isolation
77
+ const promises = Array.from(subscribers).map(async (callback) => {
78
+ try {
79
+ await callback(...args);
80
+ }
81
+ catch (error) {
82
+ console.error(`Error in event handler for "${event}":`, error);
83
+ }
84
+ });
85
+ await Promise.all(promises);
86
+ }
87
+ clear(event) {
88
+ this.events.get(event)?.clear();
89
+ }
90
+ reset() {
91
+ this.events.clear();
92
+ this.registeredEvents.clear();
93
+ }
94
+ }
95
+ export default Events;
96
+ // Create singleton instance
97
+ const events = new Events();
98
+ // Export convenience functions that use the singleton
99
+ export const setup = (eventNames) => events.setup(eventNames);
100
+ export const subscribe = (event, callback) => events.subscribe(event, callback);
101
+ export const once = (event, callback) => events.once(event, callback);
102
+ export const unsubscribe = (event, callback) => events.unsubscribe(event, callback);
103
+ export const emit = (event, ...args) => events.emit(event, ...args);
104
+ export const clear = (event) => events.clear(event);
105
+ export const reset = () => events.reset();
package/package.json CHANGED
@@ -3,15 +3,20 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.1.1-beta.4",
6
+ "version": "0.1.1-beta.40",
7
7
  "description": "Lightweight pub/sub event system for the Stonyx framework",
8
- "main": "src/main.js",
8
+ "main": "dist/main.js",
9
+ "types": "dist/main.d.ts",
9
10
  "type": "module",
10
11
  "files": [
11
- "*"
12
+ "dist",
13
+ "README.md"
12
14
  ],
13
15
  "exports": {
14
- ".": "./src/main.js"
16
+ ".": {
17
+ "types": "./dist/main.d.ts",
18
+ "default": "./dist/main.js"
19
+ }
15
20
  },
16
21
  "publishConfig": {
17
22
  "access": "public",
@@ -31,12 +36,17 @@
31
36
  },
32
37
  "homepage": "https://github.com/abofs/stonyx-events#readme",
33
38
  "devDependencies": {
34
- "@stonyx/utils": "0.2.3-beta.3",
39
+ "@stonyx/utils": "0.2.3-beta.20",
40
+ "@types/node": "^25.5.2",
41
+ "@types/qunit": "^2.19.13",
35
42
  "qunit": "^2.24.1",
36
- "sinon": "^21.0.0"
43
+ "sinon": "^21.0.0",
44
+ "typescript": "^5.8.3"
37
45
  },
38
46
  "dependencies": {},
39
47
  "scripts": {
40
- "test": "qunit"
48
+ "build": "tsc",
49
+ "build:test": "tsc -p tsconfig.test.json",
50
+ "test": "pnpm build && pnpm build:test && qunit 'dist-test/test/**/*.js'"
41
51
  }
42
52
  }
@@ -1,21 +0,0 @@
1
- # @stonyx/events - Improvement Opportunities
2
-
3
- ## Orphaned config/environment.js reference
4
-
5
- **Status**: Documentation / .gitignore reference with no actual file
6
-
7
- The `.gitignore` includes `config/environment.js`, and the old `.claude/index.md` documented an `EVENTS_LOG` env var and a `config/environment.js` file. However, neither the file nor the directory exists, and no source code reads `EVENTS_LOG` or any configuration.
8
-
9
- **Options**:
10
- 1. **Remove the reference** -- delete the `config/environment.js` line from `.gitignore` since there is nothing to ignore.
11
- 2. **Create the file** -- if debug logging is planned, implement `config/environment.js` and wire it into `src/main.js`.
12
-
13
- ## Overly broad `"files"` field in package.json
14
-
15
- **Current**: `"files": ["*"]`
16
-
17
- This tells npm to publish every file in the repo (except those in `.npmignore`). The `.npmignore` only excludes `test/` and `.nvmrc`, so the published package will include `.github/`, `.claude/`, `.gitignore`, `pnpm-lock.yaml`, and other development-only files.
18
-
19
- **Options**:
20
- 1. **Use more specific globs** -- e.g., `"files": ["src/", "LICENSE.md", "README.md"]` to ship only what consumers need.
21
- 2. **Remove the field entirely** -- npm's default behavior already includes `package.json`, `README.md`, `LICENSE.md`, and the `main`/`exports` entry points, which is likely sufficient.
@@ -1,262 +0,0 @@
1
- # @stonyx/events - Project Structure & Architecture
2
-
3
- ## Detailed Guides
4
-
5
- - [Testing Guidelines](./testing.md) — Test structure, patterns, and coverage areas
6
- - [Improvements](./improvements.md) — Documented improvement opportunities
7
-
8
- ## Project Overview
9
-
10
- **@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.
11
-
12
- ### Core Purpose
13
- - Provide application-wide event bus for decoupled communication
14
- - Support async event handlers with error isolation
15
- - Ensure type safety through event registration
16
- - Maintain singleton pattern for shared event system
17
-
18
- ### Technology Stack
19
- - **Runtime**: Node.js v24.13.0
20
- - **Module System**: ES Modules
21
- - **Testing**: QUnit 2.24.1 + Sinon 21.0.0
22
- - **License**: Apache 2.0
23
-
24
- ## Architecture Overview
25
-
26
- ### Design Patterns
27
-
28
- **Singleton Pattern**
29
- - Single Events instance shared across the application
30
- - Prevents multiple competing event systems
31
- - Ensures all parts of the application use the same event bus
32
-
33
- **Error Isolation**
34
- - Errors in one handler don't affect other handlers
35
- - All handlers run even if some fail
36
- - Errors are logged to console.error but don't propagate
37
-
38
- **Async Support**
39
- - All event handlers can be async functions
40
- - `emit()` waits for all handlers to complete
41
- - Handlers run in parallel via Promise.all()
42
-
43
- **Type Safety**
44
- - Events must be registered with `setup()` before use
45
- - Prevents typos and invalid event names
46
- - Enforces explicit event declarations
47
-
48
- ## File Structure
49
-
50
- ```
51
- stonyx-events/
52
- ├── .github/
53
- │ └── workflows/
54
- │ ├── ci.yml # GitHub Actions CI pipeline
55
- │ └── publish.yml # NPM publish workflow (alpha + stable)
56
- ├── .claude/
57
- │ ├── project-structure.md # This file
58
- │ ├── testing.md # Testing guidelines
59
- │ └── improvements.md # Improvement opportunities
60
- ├── src/
61
- │ └── main.js # Events class implementation
62
- ├── test/
63
- │ └── unit/
64
- │ └── events-test.js # QUnit tests for Events
65
- ├── .gitignore # Git ignore patterns
66
- ├── .npmignore # NPM ignore patterns
67
- ├── .nvmrc # Node version specification
68
- ├── LICENSE.md # Apache 2.0 license
69
- ├── README.md # User-facing documentation
70
- └── package.json # NPM package configuration
71
- ```
72
-
73
- ## Core Components Deep Dive
74
-
75
- ### Events Class (`src/main.js`)
76
-
77
- The Events class is the heart of the module, providing all pub/sub functionality.
78
-
79
- #### Properties
80
-
81
- **`static instance`** (Events|null)
82
- - Singleton instance reference
83
- - Ensures only one Events instance exists
84
-
85
- **`events`** (Map<string, Set<Function>>)
86
- - Map of event names to their subscriber sets
87
- - Uses Set to prevent duplicate subscriptions
88
- - Automatically created when events are registered
89
-
90
- **`registeredEvents`** (Set<string>)
91
- - Set of all registered event names
92
- - Used to validate subscriptions
93
- - Prevents subscribing to unregistered events
94
-
95
- #### Methods
96
-
97
- **`setup(eventNames: string[])`**
98
- - Register available event names
99
- - Must be called before subscribing to events
100
- - Validates that all names are strings
101
- - Creates empty subscriber sets for each event
102
-
103
- **`subscribe(event: string, callback: Function)`**
104
- - Subscribe to an event
105
- - Throws if event is not registered
106
- - Throws if callback is not a function
107
- - Returns an unsubscribe function
108
- - Callback signature: `(...args: any[]) => void | Promise<void>`
109
-
110
- **`once(event: string, callback: Function)`**
111
- - Subscribe to an event once
112
- - Auto-unsubscribes after first emit
113
- - Wraps callback in a self-removing wrapper
114
- - Returns an unsubscribe function
115
- - Supports async callbacks
116
-
117
- **`emit(event: string, ...args: any[])`**
118
- - Emit an event with arguments
119
- - Async function that waits for all handlers
120
- - Runs all handlers in parallel
121
- - Isolates errors per handler
122
- - Does nothing if event has no subscribers
123
-
124
- **`unsubscribe(event: string, callback: Function)`**
125
- - Remove a specific subscription
126
- - Safe to call multiple times
127
- - Does nothing if event or callback doesn't exist
128
-
129
- **`clear(event: string)`**
130
- - Remove all subscriptions for an event
131
- - Useful for cleanup between tests
132
- - Event remains registered
133
-
134
- **`reset()`**
135
- - Clear all subscriptions and registrations
136
- - Resets to pristine state
137
- - Essential for test isolation
138
-
139
- ## Dependencies & Integration
140
-
141
- ### Production Dependencies
142
- None. This module has zero production dependencies.
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 `pnpm test` (which invokes `qunit`)
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
- - Named convenience exports: `setup`, `subscribe`, `once`, `unsubscribe`, `emit`, `clear`, `reset`
170
-
171
- ### Error Handling
172
- - Validation errors throw immediately
173
- - Runtime errors in handlers are caught and logged
174
- - No silent failures
175
-
176
- ### Naming Conventions
177
- - Event names: camelCase strings (e.g., 'userLogin', 'dataChange')
178
- - Method names: lowercase verbs (setup, subscribe, emit)
179
- - Private instance variables: None (all properties public for testing)
180
-
181
- ## Package Exports
182
-
183
- ### Default Export
184
-
185
- ```javascript
186
- import Events from '@stonyx/events';
187
- ```
188
-
189
- The Events class.
190
-
191
- ### Named Convenience Exports
192
-
193
- ```javascript
194
- import { setup, subscribe, once, unsubscribe, emit, clear, reset } from '@stonyx/events';
195
- ```
196
-
197
- All convenience functions use a singleton Events instance internally, providing a cleaner API without needing to instantiate the class.
198
-
199
- ### Usage Pattern
200
-
201
- ```javascript
202
- // Create/get singleton instance
203
- const events = new Events();
204
-
205
- // Register events
206
- events.setup(['userLogin', 'userLogout']);
207
-
208
- // Subscribe
209
- events.subscribe('userLogin', (user) => {
210
- console.log('User logged in:', user);
211
- });
212
-
213
- // Emit (fire-and-forget, don't await)
214
- events.emit('userLogin', { id: 1, name: 'Alice' });
215
-
216
- // Or await if you need to ensure all handlers complete
217
- await events.emit('userLogin', { id: 1, name: 'Alice' });
218
- ```
219
-
220
- ### Convenience Exports Usage
221
-
222
- ```javascript
223
- import { setup, subscribe, emit } from '@stonyx/events';
224
- setup(['myEvent']);
225
- subscribe('myEvent', handler);
226
- emit('myEvent', data);
227
- ```
228
-
229
- ## Common Pitfalls & Gotchas
230
-
231
- ### Singleton Behavior
232
- - **Issue**: Creating new Events() always returns the same instance
233
- - **Solution**: Don't rely on local variables; singleton ensures shared state
234
- - **Testing**: Always call `reset()` between tests to clear state
235
-
236
- ### Async Handling
237
- - **Issue**: Forgetting to await emit() can lead to race conditions
238
- - **Solution**: Use `await events.emit()` when order matters
239
- - **Note**: For fire-and-forget, omit await (e.g., in synchronous createRecord)
240
-
241
- ### Error Isolation
242
- - **Issue**: One handler error doesn't stop other handlers, but logs to console
243
- - **Solution**: Expect console.error output in tests with failing handlers
244
- - **Testing**: Suppress console.error when testing error isolation
245
-
246
- ### Event Registration
247
- - **Issue**: Subscribing to unregistered events throws an error
248
- - **Solution**: Always call `setup()` with event names before subscribing
249
- - **Why**: Prevents typos and ensures explicit event declarations
250
-
251
- ### Set-based Subscriptions
252
- - **Issue**: Adding the same callback function twice only subscribes once
253
- - **Solution**: Use different function instances for multiple subscriptions
254
- - **Note**: This is by design to prevent accidental duplicate subscriptions
255
-
256
- ## Related Resources
257
-
258
- - [Stonyx Framework](https://github.com/abofs/stonyx)
259
- - [QUnit Documentation](https://qunitjs.com/)
260
- - [Sinon.js Documentation](https://sinonjs.org/)
261
- - [Node.js ES Modules](https://nodejs.org/api/esm.html)
262
- - [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0)
@@ -1,44 +0,0 @@
1
- # @stonyx/events - Testing Guidelines
2
-
3
- ## Test Structure
4
- - All tests in `test/unit/events-test.js`
5
- - QUnit module: `[Unit] Events`
6
- - 17 test cases covering all functionality
7
- - Each test calls `events.reset()` before and after
8
-
9
- ## Test Patterns
10
-
11
- ### Singleton Isolation
12
- ```javascript
13
- const events = new Events();
14
- events.reset(); // Clear any previous state
15
- // ... test code ...
16
- events.reset(); // Clean up after test
17
- ```
18
-
19
- ### Async Testing
20
- ```javascript
21
- test('description', async function (assert) {
22
- // Use async/await for emit()
23
- await events.emit('event');
24
- assert.ok(condition);
25
- });
26
- ```
27
-
28
- ### Error Suppression
29
- ```javascript
30
- const originalConsoleError = console.error;
31
- console.error = () => {}; // Suppress expected errors
32
- // ... test code ...
33
- console.error = originalConsoleError; // Restore
34
- ```
35
-
36
- ## Coverage Areas
37
- 1. Event registration (setup)
38
- 2. Subscription management (subscribe, unsubscribe)
39
- 3. Event emission (emit)
40
- 4. One-time subscriptions (once)
41
- 5. Error isolation
42
- 6. Async support
43
- 7. Singleton behavior
44
- 8. Edge cases (unregistered events, no subscribers)
@@ -1,16 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- pull_request:
5
- branches: [dev, main]
6
-
7
- concurrency:
8
- group: ci-${{ github.head_ref || github.ref }}
9
- cancel-in-progress: true
10
-
11
- permissions:
12
- contents: read
13
-
14
- jobs:
15
- test:
16
- uses: abofs/stonyx-workflows/.github/workflows/ci.yml@main
@@ -1,51 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- repository_dispatch:
5
- types: [cascade-publish]
6
- workflow_dispatch:
7
- inputs:
8
- version-type:
9
- description: 'Version type'
10
- required: true
11
- type: choice
12
- options:
13
- - patch
14
- - minor
15
- - major
16
- custom-version:
17
- description: 'Custom version (optional, overrides version-type)'
18
- required: false
19
- type: string
20
- pull_request:
21
- types: [opened, synchronize, reopened]
22
- branches: [main]
23
- push:
24
- branches: [main]
25
-
26
- concurrency:
27
- group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
28
- cancel-in-progress: false
29
-
30
- permissions:
31
- contents: write
32
- id-token: write
33
- pull-requests: write
34
-
35
- jobs:
36
- publish:
37
- if: "!contains(github.event.head_commit.message, '[skip ci]')"
38
- uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
39
- with:
40
- version-type: ${{ github.event.inputs.version-type }}
41
- custom-version: ${{ github.event.inputs.custom-version }}
42
- cascade-source: ${{ github.event.client_payload.source_package || '' }}
43
- secrets: inherit
44
-
45
- cascade:
46
- needs: publish
47
- uses: abofs/stonyx-workflows/.github/workflows/cascade.yml@main
48
- with:
49
- package-name: ${{ needs.publish.outputs.package-name }}
50
- published-version: ${{ needs.publish.outputs.published-version }}
51
- secrets: inherit
package/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- node_modules/
2
- config/environment.js
3
- *logs/*
4
- .vscode/*
5
- *.vsix
6
- *DS_Store*
7
-
8
- # npm auth (never commit tokens)
9
- .npmrc
package/.npmignore DELETED
@@ -1,2 +0,0 @@
1
- test/
2
- .nvmrc
package/pnpm-lock.yaml DELETED
@@ -1,134 +0,0 @@
1
- lockfileVersion: '9.0'
2
-
3
- settings:
4
- autoInstallPeers: true
5
- excludeLinksFromLockfile: false
6
-
7
- importers:
8
-
9
- .:
10
- devDependencies:
11
- '@stonyx/utils':
12
- specifier: 0.2.3-beta.3
13
- version: 0.2.3-beta.3
14
- qunit:
15
- specifier: ^2.24.1
16
- version: 2.25.0
17
- sinon:
18
- specifier: ^21.0.0
19
- version: 21.0.1
20
-
21
- packages:
22
-
23
- '@sinonjs/commons@3.0.1':
24
- resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
25
-
26
- '@sinonjs/fake-timers@15.1.0':
27
- resolution: {integrity: sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==}
28
-
29
- '@sinonjs/samsam@8.0.3':
30
- resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==}
31
-
32
- '@stonyx/utils@0.2.3-beta.3':
33
- resolution: {integrity: sha512-CLmz/DPgNWU7IXJYQXzmAdph65iapp6mHnvKZQULrd6IdKPGb6KlVMVL29yuyXg0fOapkkoPV4Xf6PmksSE8bw==}
34
-
35
- commander@7.2.0:
36
- resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
37
- engines: {node: '>= 10'}
38
-
39
- diff@8.0.3:
40
- resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
41
- engines: {node: '>=0.3.1'}
42
-
43
- globalyzer@0.1.0:
44
- resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
45
-
46
- globrex@0.1.2:
47
- resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
48
-
49
- has-flag@4.0.0:
50
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
51
- engines: {node: '>=8'}
52
-
53
- node-watch@0.7.3:
54
- resolution: {integrity: sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==}
55
- engines: {node: '>=6'}
56
-
57
- qunit@2.25.0:
58
- resolution: {integrity: sha512-MONPKgjavgTqArCwZOEz8nEMbA19zNXIp5ZOW9rPYj5cbgQp0fiI36c9dPTSzTRRzx+KcfB5eggYB/ENqxi0+w==}
59
- engines: {node: '>=10'}
60
- hasBin: true
61
-
62
- sinon@21.0.1:
63
- resolution: {integrity: sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==}
64
-
65
- supports-color@7.2.0:
66
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
67
- engines: {node: '>=8'}
68
-
69
- tiny-glob@0.2.9:
70
- resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
71
-
72
- type-detect@4.0.8:
73
- resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
74
- engines: {node: '>=4'}
75
-
76
- type-detect@4.1.0:
77
- resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
78
- engines: {node: '>=4'}
79
-
80
- snapshots:
81
-
82
- '@sinonjs/commons@3.0.1':
83
- dependencies:
84
- type-detect: 4.0.8
85
-
86
- '@sinonjs/fake-timers@15.1.0':
87
- dependencies:
88
- '@sinonjs/commons': 3.0.1
89
-
90
- '@sinonjs/samsam@8.0.3':
91
- dependencies:
92
- '@sinonjs/commons': 3.0.1
93
- type-detect: 4.1.0
94
-
95
- '@stonyx/utils@0.2.3-beta.3': {}
96
-
97
- commander@7.2.0: {}
98
-
99
- diff@8.0.3: {}
100
-
101
- globalyzer@0.1.0: {}
102
-
103
- globrex@0.1.2: {}
104
-
105
- has-flag@4.0.0: {}
106
-
107
- node-watch@0.7.3: {}
108
-
109
- qunit@2.25.0:
110
- dependencies:
111
- commander: 7.2.0
112
- node-watch: 0.7.3
113
- tiny-glob: 0.2.9
114
-
115
- sinon@21.0.1:
116
- dependencies:
117
- '@sinonjs/commons': 3.0.1
118
- '@sinonjs/fake-timers': 15.1.0
119
- '@sinonjs/samsam': 8.0.3
120
- diff: 8.0.3
121
- supports-color: 7.2.0
122
-
123
- supports-color@7.2.0:
124
- dependencies:
125
- has-flag: 4.0.0
126
-
127
- tiny-glob@0.2.9:
128
- dependencies:
129
- globalyzer: 0.1.0
130
- globrex: 0.1.2
131
-
132
- type-detect@4.0.8: {}
133
-
134
- type-detect@4.1.0: {}
package/src/main.js DELETED
@@ -1,179 +0,0 @@
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;
168
-
169
- // Create singleton instance
170
- const events = new Events();
171
-
172
- // Export convenience functions that use the singleton
173
- export const setup = (...args) => events.setup(...args);
174
- export const subscribe = (...args) => events.subscribe(...args);
175
- export const once = (...args) => events.once(...args);
176
- export const unsubscribe = (...args) => events.unsubscribe(...args);
177
- export const emit = (...args) => events.emit(...args);
178
- export const clear = (...args) => events.clear(...args);
179
- export const reset = (...args) => events.reset(...args);