├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── legacy.js ├── package.json └── test ├── index.js └── legacy.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = lf 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | prettier: 13 | name: Format code 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event_name == 'push' }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Prettier 22 | uses: gulpjs/prettier_action@v3.0 23 | with: 24 | commit_message: 'chore: Run prettier' 25 | prettier_options: '--write .' 26 | 27 | test: 28 | name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [10, 12, 14, 16] 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | 37 | steps: 38 | - name: Clone repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Set Node.js version 42 | uses: actions/setup-node@v2 43 | with: 44 | node-version: ${{ matrix.node }} 45 | 46 | - run: node --version 47 | - run: npm --version 48 | 49 | - name: Install npm dependencies 50 | run: npm install 51 | 52 | - name: Run lint 53 | run: npm run lint 54 | 55 | - name: Run tests 56 | run: npm test 57 | 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@v1.1.2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | flag-name: ${{matrix.os}}-node-${{ matrix.node }} 63 | parallel: true 64 | 65 | coveralls: 66 | needs: test 67 | name: Finish up 68 | 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Coveralls Finished 72 | uses: coverallsapp/github-action@v1.1.2 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | parallel-finished: true 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v2 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: release-please-action 17 | bump-minor-pre-major: true 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Garbage files 64 | .DS_Store 65 | 66 | # Test results 67 | test.xunit 68 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0](https://www.github.com/gulpjs/sparkles/compare/v2.0.0...v2.1.0) (2024-03-23) 4 | 5 | 6 | ### Features 7 | 8 | * Add module for accessing legacy store ([#10](https://www.github.com/gulpjs/sparkles/issues/10)) ([7f8b667](https://www.github.com/gulpjs/sparkles/commit/7f8b6678fe3ec8449f47eb156b91ce5150ed9348)) 9 | 10 | ## [2.0.0](https://www.github.com/gulpjs/sparkles/compare/v1.0.1...v2.0.0) (2022-02-01) 11 | 12 | 13 | ### ⚠ BREAKING CHANGES 14 | 15 | * Use a Symbol for attaching the default namespace to the store 16 | * Use a Symbol for attaching the store to the global (#9) 17 | * Normalize repository, dropping node <10.13 support (#6) 18 | 19 | ### Features 20 | 21 | * Use a Symbol for attaching the default namespace to the store ([2196fb1](https://www.github.com/gulpjs/sparkles/commit/2196fb1503f14f02c9422566d1e3635aa93e6ac0)) 22 | * Use a Symbol for attaching the store to the global ([#9](https://www.github.com/gulpjs/sparkles/issues/9)) ([2196fb1](https://www.github.com/gulpjs/sparkles/commit/2196fb1503f14f02c9422566d1e3635aa93e6ac0)) 23 | * Use Symbol.for so other applications can create the same Symbol ([2196fb1](https://www.github.com/gulpjs/sparkles/commit/2196fb1503f14f02c9422566d1e3635aa93e6ac0)) 24 | 25 | 26 | ### Miscellaneous Chores 27 | 28 | * Normalize repository, dropping node <10.13 support ([#6](https://www.github.com/gulpjs/sparkles/issues/6)) ([6d6f7a8](https://www.github.com/gulpjs/sparkles/commit/6d6f7a8633a437c474efb8a1768b3617f0f8e87d)) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015, 2018-2019, 2021 Blaine Bublitz and Eric Schoffstall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # sparkles 8 | 9 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] 10 | 11 | Namespaced global event emitter 12 | 13 | ## Usage 14 | 15 | Sparkles exports a function that returns a singleton `EventEmitter`. 16 | This EE can be shared across your application, whether or not node loads 17 | multiple copies. 18 | 19 | Note: If you put an event handler in a file in your application, that file must be loaded in via an import somewhere in your application, even if it's not directly being used. Otherwise, it will not be loaded into memory. 20 | 21 | ```js 22 | var sparkles = require('sparkles')(); // make sure to call the function 23 | 24 | sparkles.on('my-event', function (evt) { 25 | console.log('my-event handled', evt); 26 | }); 27 | 28 | sparkles.emit('my-event', { my: 'event' }); 29 | ``` 30 | 31 | ## API 32 | 33 | ### sparkles(namespace) 34 | 35 | Returns an EventEmitter that is shared amongst the provided namespace. If no namespace 36 | is provided, returns a default EventEmitter. 37 | 38 | ### sparkles.exists(namespace); 39 | 40 | Checks whether a namespace exists and returns true or false. 41 | 42 | ## Why the name? 43 | 44 | This is a "global emitter"; shortened: "glitter" but it was already taken; so we got sparkles instead :smile: 45 | 46 | ## License 47 | 48 | MIT 49 | 50 | 51 | [downloads-image]: https://img.shields.io/npm/dm/sparkles.svg?style=flat-square 52 | [npm-url]: https://www.npmjs.com/package/sparkles 53 | [npm-image]: https://img.shields.io/npm/v/sparkles.svg?style=flat-square 54 | 55 | [ci-url]: https://github.com/gulpjs/sparkles/actions?query=workflow:dev 56 | [ci-image]: https://img.shields.io/github/actions/workflow/status/gulpjs/sparkles/dev.yml?branch=master&style=flat-square 57 | 58 | [coveralls-url]: https://coveralls.io/r/gulpjs/sparkles 59 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/sparkles/master.svg?style=flat-square 60 | 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | var sparklesNamespace = Symbol.for('sparkles:store'); 6 | var defaultNamespace = Symbol.for('sparkles:namespace'); 7 | 8 | function getStore() { 9 | var store = global[sparklesNamespace]; 10 | 11 | if (!store) { 12 | store = global[sparklesNamespace] = {}; 13 | } 14 | 15 | return store; 16 | } 17 | 18 | function getEmitter(namespace) { 19 | var store = getStore(); 20 | 21 | namespace = namespace || defaultNamespace; 22 | 23 | var ee = store[namespace]; 24 | 25 | if (!ee) { 26 | ee = store[namespace] = new EventEmitter(); 27 | ee.setMaxListeners(0); 28 | ee.remove = function remove() { 29 | ee.removeAllListeners(); 30 | delete store[namespace]; 31 | }; 32 | } 33 | 34 | return ee; 35 | } 36 | 37 | function exists(namespace) { 38 | var store = getStore(); 39 | 40 | return !!store[namespace]; 41 | } 42 | 43 | module.exports = getEmitter; 44 | module.exports.exists = exists; 45 | -------------------------------------------------------------------------------- /legacy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | var sparklesNamespace = 'store@sparkles'; 6 | var defaultNamespace = 'default' 7 | 8 | function getStore() { 9 | var store = global[sparklesNamespace]; 10 | 11 | if (!store) { 12 | Object.defineProperty(global, sparklesNamespace, { 13 | value: {}, 14 | enumerable: false, 15 | }); 16 | store = global[sparklesNamespace]; 17 | } 18 | 19 | return store; 20 | } 21 | 22 | function getEmitter(namespace) { 23 | var store = getStore(); 24 | 25 | namespace = namespace || defaultNamespace; 26 | 27 | var ee = store[namespace]; 28 | 29 | if (!ee) { 30 | ee = store[namespace] = new EventEmitter(); 31 | ee.setMaxListeners(0); 32 | ee.remove = function remove() { 33 | ee.removeAllListeners(); 34 | delete store[namespace]; 35 | }; 36 | } 37 | 38 | return ee; 39 | } 40 | 41 | function exists(namespace) { 42 | var store = getStore(); 43 | 44 | return !!store[namespace]; 45 | } 46 | 47 | module.exports = getEmitter; 48 | module.exports.exists = exists; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparkles", 3 | "version": "2.1.0", 4 | "description": "Namespaced global event emitter", 5 | "author": "Gulp Team (http://gulpjs.com/)", 6 | "contributors": [ 7 | "Blaine Bublitz " 8 | ], 9 | "repository": "gulpjs/sparkles", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">= 10.13.0" 13 | }, 14 | "main": "index.js", 15 | "files": [ 16 | "LICENSE", 17 | "index.js", 18 | "legacy.js" 19 | ], 20 | "scripts": { 21 | "lint": "eslint .", 22 | "pretest": "npm run lint", 23 | "test": "nyc mocha --async-only" 24 | }, 25 | "devDependencies": { 26 | "eslint": "^7.32.0", 27 | "eslint-config-gulp": "^5.0.1", 28 | "eslint-plugin-node": "^11.1.0", 29 | "expect": "^27.3.1", 30 | "mocha": "^8.4.0", 31 | "nyc": "^15.1.0" 32 | }, 33 | "nyc": { 34 | "reporter": [ 35 | "lcov", 36 | "text-summary" 37 | ] 38 | }, 39 | "prettier": { 40 | "singleQuote": true 41 | }, 42 | "keywords": [ 43 | "ee", 44 | "emitter", 45 | "events", 46 | "global", 47 | "namespaced" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var sparkles = require('../'); 7 | 8 | describe('sparkles()', function () { 9 | describe('behavior on global', function () { 10 | var ee; 11 | var storeSymbol = Symbol.for('sparkles:store'); 12 | var namespaceSymbol = Symbol.for('sparkles:namespace'); 13 | 14 | beforeEach(function (done) { 15 | ee = sparkles(); 16 | done(); 17 | }); 18 | 19 | afterEach(function (done) { 20 | ee.remove(); 21 | done(); 22 | }); 23 | 24 | it('will attach the sparkles store namespace to global', function (done) { 25 | expect(global[storeSymbol]).toBeTruthy(); 26 | done(); 27 | }); 28 | 29 | it('will attach an event emitter to the sparkles store default namespace', function (done) { 30 | expect(global[storeSymbol][namespaceSymbol]).toBeInstanceOf(EventEmitter); 31 | done(); 32 | }); 33 | 34 | it('removes the event emitter from the store when remove is called', function (done) { 35 | ee.on('test', function () {}); 36 | ee.remove(); 37 | expect(global[storeSymbol][namespaceSymbol]).toBeUndefined(); 38 | done(); 39 | }); 40 | 41 | it('does not show up when enumerating the global object', function (done) { 42 | expect(Object.keys(global)).not.toContain(storeSymbol); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should get the default emitter if namespace is not specified', function (done) { 48 | var ee = sparkles(); 49 | expect(ee).toBeInstanceOf(EventEmitter); 50 | 51 | expect(sparkles()).toBe(ee); 52 | done(); 53 | }); 54 | 55 | it('should get an emitter for a specified namespace', function (done) { 56 | var ee = sparkles('ns1'); 57 | expect(ee).toBeInstanceOf(EventEmitter); 58 | 59 | expect(sparkles()).not.toBe(ee); 60 | expect(sparkles('ns1')).toBe(ee); 61 | expect(sparkles('ns2')).not.toBe(ee); 62 | done(); 63 | }); 64 | 65 | it('should remove and re-create emitter in the store', function (done) { 66 | var ee0 = sparkles(); 67 | var ee1 = sparkles('ns1'); 68 | 69 | ee0.remove(); 70 | expect(sparkles()).not.toBe(ee0); 71 | expect(sparkles('ns1')).toBe(ee1); 72 | 73 | ee1.remove(); 74 | expect(sparkles('ns1')).not.toBe(ee1); 75 | done(); 76 | }); 77 | }); 78 | 79 | describe('sparkles.exists()', function () { 80 | it('checks if a namespace has been defined', function (done) { 81 | expect(sparkles.exists('test')).toBe(false); 82 | var ee = sparkles('test'); 83 | expect(sparkles.exists('test')).toBe(true); 84 | ee.remove(); 85 | expect(sparkles.exists('test')).toBe(false); 86 | done(); 87 | }); 88 | }); 89 | 90 | describe('namespace', function () { 91 | var storeSymbol = Symbol.for('sparkles:store'); 92 | var namespaceSymbol = Symbol.for('sparkles:namespace'); 93 | 94 | beforeEach(function (done) { 95 | global[storeSymbol] = {}; 96 | done(); 97 | }); 98 | 99 | afterEach(function (done) { 100 | delete global[storeSymbol]; 101 | done(); 102 | }); 103 | 104 | it('should use an EE from sparkles namespace if it already exists', function (done) { 105 | var ee = (global[storeSymbol][namespaceSymbol] = new EventEmitter()); 106 | ee.custom = 'ee'; 107 | 108 | var sparkles = require('../')(); 109 | 110 | expect(sparkles.custom).toEqual('ee'); 111 | done(); 112 | }); 113 | 114 | it('should allow custom namespaces', function (done) { 115 | var ee = (global[storeSymbol].customNamespace = new EventEmitter()); 116 | ee.custom = true; 117 | 118 | var sparkles = require('../')('customNamespace'); 119 | 120 | expect(sparkles.custom).toEqual(true); 121 | done(); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/legacy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var sparkles = require('../legacy.js'); 7 | 8 | describe('legacy: sparkles()', function () { 9 | describe('behavior on global', function () { 10 | var ee; 11 | var storeNamespace = 'store@sparkles'; 12 | var defaultNamespace = 'default'; 13 | 14 | beforeEach(function (done) { 15 | ee = sparkles(); 16 | done(); 17 | }); 18 | 19 | afterEach(function (done) { 20 | ee.remove(); 21 | done(); 22 | }); 23 | 24 | it('will attach the sparkles store namespace to global', function (done) { 25 | expect(global[storeNamespace]).toBeTruthy(); 26 | done(); 27 | }); 28 | 29 | it('will attach an event emitter to the sparkles store default namespace', function (done) { 30 | expect(global[storeNamespace][defaultNamespace]).toBeInstanceOf(EventEmitter); 31 | done(); 32 | }); 33 | 34 | it('removes the event emitter from the store when remove is called', function (done) { 35 | ee.on('test', function () {}); 36 | ee.remove(); 37 | expect(global[storeNamespace][defaultNamespace]).toBeUndefined(); 38 | done(); 39 | }); 40 | 41 | it('does not show up when enumerating the global object', function (done) { 42 | expect(Object.keys(global)).not.toContain(storeNamespace); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should get the default emitter if namespace is not specified', function (done) { 48 | var ee = sparkles(); 49 | expect(ee).toBeInstanceOf(EventEmitter); 50 | 51 | expect(sparkles()).toBe(ee); 52 | done(); 53 | }); 54 | 55 | it('should get an emitter for a specified namespace', function (done) { 56 | var ee = sparkles('ns1'); 57 | expect(ee).toBeInstanceOf(EventEmitter); 58 | 59 | expect(sparkles()).not.toBe(ee); 60 | expect(sparkles('ns1')).toBe(ee); 61 | expect(sparkles('ns2')).not.toBe(ee); 62 | done(); 63 | }); 64 | 65 | it('should remove and re-create emitter in the store', function (done) { 66 | var ee0 = sparkles(); 67 | var ee1 = sparkles('ns1'); 68 | 69 | ee0.remove(); 70 | expect(sparkles()).not.toBe(ee0); 71 | expect(sparkles('ns1')).toBe(ee1); 72 | 73 | ee1.remove(); 74 | expect(sparkles('ns1')).not.toBe(ee1); 75 | done(); 76 | }); 77 | }); 78 | 79 | describe('legacy: sparkles.exists()', function () { 80 | it('checks if a namespace has been defined', function (done) { 81 | expect(sparkles.exists('test')).toBe(false); 82 | var ee = sparkles('test'); 83 | expect(sparkles.exists('test')).toBe(true); 84 | ee.remove(); 85 | expect(sparkles.exists('test')).toBe(false); 86 | done(); 87 | }); 88 | }); 89 | 90 | describe('legacy: namespace', function () { 91 | var storeNamespace = Symbol.for('sparkles:store'); 92 | var defaultNamespace = Symbol.for('sparkles:namespace'); 93 | 94 | beforeEach(function (done) { 95 | global[storeNamespace] = {}; 96 | done(); 97 | }); 98 | 99 | afterEach(function (done) { 100 | delete global[storeNamespace]; 101 | done(); 102 | }); 103 | 104 | it('should use an EE from sparkles namespace if it already exists', function (done) { 105 | var ee = (global[storeNamespace][defaultNamespace] = new EventEmitter()); 106 | ee.custom = 'ee'; 107 | 108 | var sparkles = require('../')(); 109 | 110 | expect(sparkles.custom).toEqual('ee'); 111 | done(); 112 | }); 113 | 114 | it('should allow custom namespaces', function (done) { 115 | var ee = (global[storeNamespace].customNamespace = new EventEmitter()); 116 | ee.custom = true; 117 | 118 | var sparkles = require('../')('customNamespace'); 119 | 120 | expect(sparkles.custom).toEqual(true); 121 | done(); 122 | }); 123 | }); 124 | --------------------------------------------------------------------------------