From bb049ea9a90550906ad046663aad401af694df26 Mon Sep 17 00:00:00 2001
From: fengmk2 <fengmk2@gmail.com>
Date: Sun, 15 Dec 2024 20:02:35 +0800
Subject: [PATCH] feat: merge Ready and EventEmitter (#5)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
	- Added support for Node.js version 22 in CI workflows.
- Introduced a new `ReadyEventEmitter` class for event-driven readiness
handling.
	- Added `ReadyEventClass` to manage 'ready-event' emissions.
- Enhanced documentation with a new badge for Node.js version and a
section on `ReadyEventEmitter`.

- **Bug Fixes**
- Improved promise handling and method documentation in the `Ready`
class.

- **Chores**
- Updated dependency versions in `package.json` and removed unnecessary
dependencies.
	- Removed explicit test check during the release job in CI workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 .github/workflows/nodejs.yml  |  5 ++--
 .github/workflows/release.yml |  2 --
 README.md                     | 20 +++++++++-----
 package.json                  |  8 +++---
 src/index.ts                  | 29 +++++++++++++++++----
 test/index.test.ts            | 49 ++++++++++++++++++++++++++++++-----
 6 files changed, 86 insertions(+), 27 deletions(-)

diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 0298d9f..619ea9e 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -3,7 +3,6 @@ name: CI
 on:
   push:
     branches: [ master ]
-
   pull_request:
     branches: [ master ]
 
@@ -12,5 +11,7 @@ jobs:
     name: Node.js
     uses: node-modules/github-actions/.github/workflows/node-test.yml@master
     with:
-      version: '16.17.0, 16, 18, 20'
+      version: '16.17.0, 16, 18, 20, 22'
       os: 'ubuntu-latest'
+    secrets:
+      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a4e1158..1c6cbb1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -11,5 +11,3 @@ jobs:
     secrets:
       NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
       GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
-    with:
-      checkTest: false
diff --git a/README.md b/README.md
index 7b47975..e681e00 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
 [![CI](https://github.com/node-modules/get-ready/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/get-ready/actions/workflows/nodejs.yml)
 [![Test coverage][codecov-image]][codecov-url]
 [![npm download][download-image]][download-url]
+[![Node.js Version](https://img.shields.io/node/v/get-ready.svg?style=flat)](https://nodejs.org/en/download/)
 
 [npm-image]: https://img.shields.io/npm/v/get-ready.svg?style=flat-square
 [npm-url]: https://npmjs.org/package/get-ready
@@ -51,6 +52,16 @@ obj.ready(true);
 obj.ready().then(() => console.log('ready'));
 ```
 
+### ReadyEventEmitter
+
+```ts
+import { ReadyEventEmitter } from 'get-ready';
+
+class MyClass extends ReadyEventEmitter {
+  // your handler here
+}
+```
+
 **Warning: the callback is called after nextTick**
 
 ### Emit
@@ -79,13 +90,8 @@ obj.ready(new Error('err'));
 
 [MIT](LICENSE)
 
-<!-- GITCONTRIBUTOR_START -->
-
 ## Contributors
 
-|[<img src="https://avatars.githubusercontent.com/u/221826?v=4" width="100px;"/><br/><sub><b>supershabam</b></sub>](https://github.com/supershabam)<br/>|[<img src="https://avatars.githubusercontent.com/u/156269?v=4" width="100px;"/><br/><sub><b>fengmk2</b></sub>](https://github.com/fengmk2)<br/>|[<img src="https://avatars.githubusercontent.com/u/360661?v=4" width="100px;"/><br/><sub><b>popomore</b></sub>](https://github.com/popomore)<br/>|[<img src="https://avatars.githubusercontent.com/u/985607?v=4" width="100px;"/><br/><sub><b>dead-horse</b></sub>](https://github.com/dead-horse)<br/>|[<img src="https://avatars.githubusercontent.com/u/32174276?v=4" width="100px;"/><br/><sub><b>semantic-release-bot</b></sub>](https://github.com/semantic-release-bot)<br/>|
-| :---: | :---: | :---: | :---: | :---: |
-
-This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Mon Jun 05 2023 14:06:50 GMT+0800`.
+[![Contributors](https://contrib.rocks/image?repo=node-modules/get-ready)](https://github.com/node-modules/get-ready/graphs/contributors)
 
-<!-- GITCONTRIBUTOR_END -->
+Made with [contributors-img](https://contrib.rocks).
diff --git a/package.json b/package.json
index 0619d60..8d656f3 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,7 @@
     "egg-bin": "^6.4.1",
     "eslint": "^8.51.0",
     "eslint-config-egg": "^13.0.0",
-    "git-contributor": "^2.1.5",
-    "tshy": "^1.2.2",
+    "tshy": "3",
     "tshy-after": "^1.0.0",
     "typescript": "^5.2.2"
   },
@@ -23,7 +22,6 @@
     "node": ">= 16.13.0"
   },
   "scripts": {
-    "contributor": "git-contributor",
     "lint": "eslint src test --ext ts",
     "test": "npm run lint && egg-bin test",
     "ci": "egg-bin cov && npm run prepublishOnly && npm pack",
@@ -65,5 +63,7 @@
     }
   },
   "type": "module",
-  "types": "./dist/commonjs/index.d.ts"
+  "types": "./dist/commonjs/index.d.ts",
+  "main": "./dist/commonjs/index.js",
+  "module": "./dist/esm/index.js"
 }
diff --git a/src/index.ts b/src/index.ts
index 7db2637..71a2291 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,7 @@
+import { EventEmitter } from 'node:events';
+
 export type CallbackFunction = (err?: Error) => void;
-export type ReadyFunctionArg = boolean | Error | CallbackFunction | undefined;
+export type ReadyFunctionArg = boolean | Error | CallbackFunction;
 
 export class Ready {
   #isReady: boolean;
@@ -11,6 +13,8 @@ export class Ready {
     this.#readyCallbacks = [];
   }
 
+  ready(): Promise<void>;
+  ready(flagOrFunction: ReadyFunctionArg): void;
   ready(flagOrFunction?: ReadyFunctionArg) {
     // register a callback
     if (flagOrFunction === undefined || typeof flagOrFunction === 'function') {
@@ -25,7 +29,7 @@ export class Ready {
    * It will return promise when no argument passing.
    */
   #register(func?: CallbackFunction) {
-    // support `this.ready().then(onready);` and `await this.ready()`;
+    // support `this.ready().then(onReady);` and `await this.ready()`;
     if (!func) {
       return new Promise<void>((resolve, reject) => {
         function func(err?: Error) {
@@ -51,8 +55,8 @@ export class Ready {
   }
 
   /**
-   * Call the callbacks that has been registerd, and clean the callback stack.
-   * If the flag is not false, it will be marked as ready. Then the callbacks will be called immediatly when register.
+   * Call the callbacks that has been registered, and clean the callback stack.
+   * If the flag is not false, it will be marked as ready. Then the callbacks will be called immediately when register.
    * @param {Boolean|Error} flag - Set a flag whether it had been ready. If the flag is an error, it's also ready, but the callback will be called with argument `error`
    */
   #emit(flag: boolean | Error) {
@@ -76,8 +80,23 @@ export class Ready {
     if (!obj) return;
     const ready = new Ready();
     // delegate method
-    obj.ready = (flagOrFunction: any) => ready.ready(flagOrFunction);
+    obj.ready = (flagOrFunction: any) => {
+      return ready.ready(flagOrFunction);
+    };
   }
 }
 
 export default Ready;
+
+export class ReadyEventEmitter extends EventEmitter {
+  #readyObj = new Ready();
+
+  ready(): Promise<void>;
+  ready(flagOrFunction: ReadyFunctionArg): void;
+  ready(flagOrFunction?: ReadyFunctionArg) {
+    if (flagOrFunction === undefined) {
+      return this.#readyObj.ready();
+    }
+    this.#readyObj.ready(flagOrFunction);
+  }
+}
diff --git a/test/index.test.ts b/test/index.test.ts
index 234793e..c46fe80 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -1,5 +1,5 @@
 import { strict as assert } from 'node:assert';
-import Ready, { ReadyFunctionArg, Ready as ReadyBase } from '../src/index.js';
+import Ready, { ReadyFunctionArg, Ready as ReadyBase, ReadyEventEmitter } from '../src/index.js';
 
 class SomeClass {
   property: string;
@@ -10,8 +10,13 @@ class SomeClass {
     this.#readyObject = new Ready();
   }
 
+  ready(): Promise<void>;
+  ready(arg: ReadyFunctionArg): void;
   ready(arg?: ReadyFunctionArg) {
-    return this.#readyObject.ready(arg);
+    if (arg === undefined) {
+      return this.#readyObject.ready();
+    }
+    this.#readyObject.ready(arg);
   }
 
   method() {
@@ -32,6 +37,22 @@ class ReadySubClass extends ReadyBase {
   }
 }
 
+class ReadyEventClass extends ReadyEventEmitter {
+  property: string;
+
+  constructor() {
+    super();
+    this.property = 'value';
+    this.ready(() => {
+      this.emit('ready-event');
+    });
+  }
+
+  method() {
+    return 'method';
+  }
+}
+
 describe('Ready.mixin', () => {
   it('should exports mixin', () => {
     assert(Ready.mixin);
@@ -88,7 +109,7 @@ describe('new Ready()', () => {
     assert.deepEqual(arr, [ 1, 2 ]);
   });
 
-  it('should immediatly call callback when already ready', done => {
+  it('should immediately call callback when already ready', done => {
     const someClass = new SomeClass();
     someClass.ready(true);
     someClass.ready(done);
@@ -113,11 +134,25 @@ describe('new Ready()', () => {
   });
 });
 
+describe('new ReadyEventClass()', () => {
+  it('should have Ready properties', async () => {
+    const someClass = new ReadyEventClass();
+    assert('ready' in someClass);
+    let gotReadyEvent = false;
+    someClass.on('ready-event', () => {
+      gotReadyEvent = true;
+    });
+    someClass.ready(true);
+    await someClass.ready();
+    assert.equal(gotReadyEvent, true);
+  });
+});
+
 describe('promise', () => {
   it('should resolve after ready', done => {
     const someClass = new SomeClass();
-    someClass.ready()!.then(() => {
-      someClass.ready()!.then(done);
+    someClass.ready().then(() => {
+      someClass.ready().then(done);
     });
     someClass.ready(true);
   });
@@ -144,7 +179,7 @@ describe('error', () => {
 
   it('should get error in promise', done => {
     const someClass = new SomeClass();
-    someClass.ready()!.catch(err => {
+    someClass.ready().catch(err => {
       assert(err);
       assert(err.message === 'error');
       done();
@@ -165,7 +200,7 @@ describe('error', () => {
   it('should get error after ready in promise', done => {
     const someClass = new SomeClass();
     someClass.ready(new Error('error'));
-    someClass.ready()!.catch(err => {
+    someClass.ready().catch(err => {
       assert(err);
       assert(err.message === 'error');
       done();