From c04c8955dd5268f81f4397e01f88b182eb6c7372 Mon Sep 17 00:00:00 2001 From: hanquliu Date: Sun, 8 Oct 2023 19:42:16 +0800 Subject: [PATCH 1/2] feat: refactor with typescript --- .eslintrc | 11 +++- .github/workflows/ci.yml | 18 ----- .github/workflows/nodejs.yml | 16 +++++ .github/workflows/release.yml | 6 +- .gitignore | 2 + package.json | 61 ++++++++++++----- lib/ready.js => src/index.ts | 94 ++++++++++++++------------- test/{index.test.js => index.test.ts} | 12 ++-- test/{ready.test.js => ready.test.ts} | 64 +++++++++--------- tsconfig.json | 15 +++++ 10 files changed, 176 insertions(+), 123 deletions(-) delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/nodejs.yml rename lib/ready.js => src/index.ts (60%) rename test/{index.test.js => index.test.ts} (75%) rename test/{ready.test.js => ready.test.ts} (88%) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index c799fe5..a65f844 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,10 @@ { - "extends": "eslint-config-egg" -} + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ], + "rules": { + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-unused-vars": "off" + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 22a8662..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: CI - -on: - push: - branches: [ master ] - - pull_request: - branches: [ master ] - - workflow_dispatch: {} - -jobs: - Job: - name: Node.js - uses: artusjs/github-actions/.github/workflows/node-test.yml@v1 - with: - os: 'ubuntu-latest' - version: '14, 16, 18' diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..0a5fafa --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,16 @@ +name: CI + +on: + push: + branches: [ master ] + + pull_request: + branches: [ master ] + +jobs: + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + os: 'ubuntu-latest, macos-latest' + version: '16, 18, 20' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1612587..d359c56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,14 +4,12 @@ on: push: branches: [ master ] - workflow_dispatch: {} - jobs: release: name: Node.js - uses: artusjs/github-actions/.github/workflows/node-release.yml@v1 + uses: node-modules/github-actions/.github/workflows/node-release.yml@master secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} with: - checkTest: false + checkTest: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 25fbf5a..ac7e53f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ coverage/ +.tshy* +dist/ \ No newline at end of file diff --git a/package.json b/package.json index a6a9bbd..e52e474 100644 --- a/package.json +++ b/package.json @@ -7,22 +7,30 @@ "ready", "async" ], - "main": "lib/ready.js", "files": [ - "index.js", - "lib" + "dist", + "src" ], "dependencies": { - "debug": "^4.3.4", - "get-ready": "^2.0.1", + "get-ready": "^3.0.0", "once": "^1.4.0" }, "devDependencies": { - "egg-bin": "^5.9.0", - "eslint": "^8.31.0", - "eslint-config-egg": "^12.1.0", - "koa": "^1.2.4", - "spy": "^1.0.0" + "@eggjs/koa": "^2.15.1", + "@eggjs/tsconfig": "^1.3.3", + "@types/mocha": "^10.0.2", + "@types/node": "^20.8.3", + "@types/once": "^1.4.1", + "egg-bin": "^6.5.2", + "eslint": "^8.51.0", + "eslint-config-egg": "^12.3.1", + "git-contributor": "^2.1.5", + "mocha": "^10.2.0", + "tinyspy": "^2.2.0", + "ts-node": "^10.9.1", + "tshy": "^1.2.2", + "tshy-after": "^1.0.0", + "typescript": "^5.2.2" }, "repository": { "type": "git", @@ -32,12 +40,35 @@ "author": "popomore ", "license": "MIT", "scripts": { - "lint": "eslint .", "test": "npm run lint -- --fix && egg-bin test", - "cov": "egg-bin cov", - "ci": "npm run lint && egg-bin cov" + "lint": "eslint src test --ext ts", + "ci": "npm run lint && egg-bin cov && npm run prepublishOnly", + "contributor": "git-contributor", + "prepublishOnly": "tshy && tshy-after" }, "engines": { - "node": ">=14.0.0" - } + "node": ">=16.0.0" + }, + "ci": { + "version": "16, 18, 20" + }, + "tshy": { + "exports": { + ".": "./src/index.ts" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "type": "module", + "types": "./dist/commonjs/index.d.ts" } diff --git a/lib/ready.js b/src/index.ts similarity index 60% rename from lib/ready.js rename to src/index.ts index 7e2394c..c3d47e6 100644 --- a/lib/ready.js +++ b/src/index.ts @@ -1,10 +1,29 @@ -const debug = require('util').debuglog('ready-callback'); -const EventEmitter = require('events'); -const once = require('once'); -const ready = require('get-ready'); -const { randomUUID } = require('crypto'); +import EventEmitter from 'node:events'; +import { debuglog } from 'node:util'; +import { randomUUID } from 'node:crypto'; +import once from 'once'; +import ReadyObject = require('get-ready'); +import { type ReadyFunctionArg } from 'get-ready'; + +const debug = debuglog('ready-callback'); + +interface ReadyOption { + timeout?: number; + isWeakDep?: boolean; + lazyStart?: boolean; +} +interface ReadyCallbackOption { + name?: string; + timeout?: number; + isWeakDep?: boolean; +} +interface ReadyCallbackFn { + (err?: any): void; + id: string; +} +type ReadyCallbackCache = Map; -const defaults = { +const defaults: ReadyCallbackOption = { timeout: 10000, isWeakDep: false, }; @@ -13,21 +32,19 @@ const defaults = { * @class Ready */ class Ready extends EventEmitter { + isError = false; + cache: ReadyCallbackCache = new Map(); - /** - * @class - * @param {Object} opt - * - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout - * - {Boolean} [isWeakDep=false] - whether it's a weak dependency - * - {Boolean} [lazyStart=false] - will not check cache size automatically, if lazyStart is true - */ - constructor(opt) { + opt: ReadyOption; + obj: any; + + ready: (flagOrFunction?: ReadyFunctionArg) => void; + + constructor(opt: ReadyOption = {}) { super(); - ready.mixin(this); + ReadyObject.default.mixin(this); - this.opt = opt || {}; - this.isError = false; - this.cache = new Map(); + this.opt = opt; if (!this.opt.lazyStart) { this.start(); @@ -50,7 +67,7 @@ class Ready extends EventEmitter { * @param {Object} obj - The mixed object * @return {Ready} this */ - mixin(obj) { + mixin(obj?: any) { // only mixin once if (!obj || this.obj) return null; @@ -72,19 +89,12 @@ class Ready extends EventEmitter { return this; } - /** - * Create a callback, ready won't be fired until all the callbacks are triggered. - * @function Ready#readyCallback - * @param {String} name - - * @param {Object} opt - the options that will override global - * @return {Function} - a callback - */ - readyCallback(name, opt) { + readyCallback(name: string, opt: ReadyCallbackOption = {}) { opt = Object.assign({}, defaults, this.opt, opt); const cacheKey = randomUUID(); opt.name = name || cacheKey; const timer = setTimeout(() => this.emit('ready_timeout', opt.name), opt.timeout); - const cb = once(err => { + const cb = once((err?: any) => { if (err != null && !(err instanceof Error)) { err = new Error(err); } @@ -93,23 +103,14 @@ class Ready extends EventEmitter { if (this.isError === true) return; // fire callback after all register setImmediate(() => this.readyDone(cacheKey, opt, err)); - }); + }) as unknown as ReadyCallbackFn; debug('[%s] Register task id `%s` with %j', cacheKey, opt.name, opt); cb.id = opt.name; this.cache.set(cacheKey, cb); return cb; } - /** - * resolve ths callback when readyCallback be called - * @function Ready#readyDone - * @private - * @param {String} id - unique id generated by readyCallback - * @param {Object} opt - the options that will override global - * @param {Error} err - err passed by ready callback - * @return {Ready} this - */ - readyDone(id, opt, err) { + readyDone(id: string, opt: ReadyCallbackOption, err?: Error) { if (err != null && !opt.isWeakDep) { this.isError = true; debug('[%s] Throw error task id `%s`, error %s', id, opt.name, err); @@ -130,17 +131,18 @@ class Ready extends EventEmitter { } return this; } - } -// Use ready-callback with options -module.exports = opt => new Ready(opt); -module.exports.Ready = Ready; - -function getRemain(map) { - const names = []; +function getRemain(map: ReadyCallbackCache) { + const names: string[] = []; for (const cb of map.values()) { names.push(cb.id); } return names; } + +export { Ready }; + +export default function(opt: ReadyOption = {}) { + return new Ready(opt); +} diff --git a/test/index.test.js b/test/index.test.ts similarity index 75% rename from test/index.test.js rename to test/index.test.ts index 1d9bc6d..a226435 100644 --- a/test/index.test.js +++ b/test/index.test.ts @@ -1,13 +1,13 @@ -const assert = require('assert'); -const koa = require('koa'); -const spy = require('spy'); -const ready = require('..'); +import { strict as assert } from 'node:assert'; +import { spy } from 'tinyspy'; +import Koa = require('@eggjs/koa'); +import ready from '../src/index.js'; describe('koa', function() { - let app; + let app: any; beforeEach(function() { - app = koa(); + app = new Koa.default(); ready().mixin(app); }); diff --git a/test/ready.test.js b/test/ready.test.ts similarity index 88% rename from test/ready.test.js rename to test/ready.test.ts index fdbd004..11b2590 100644 --- a/test/ready.test.js +++ b/test/ready.test.ts @@ -1,7 +1,7 @@ -const assert = require('assert'); -const EventEmitter = require('events'); -const spy = require('spy'); -const Ready = require('..').Ready; +import { strict as assert } from 'node:assert'; +import EventEmitter from 'node:events'; +import { spy } from 'tinyspy'; +import { Ready } from '../src/index.js'; function sleep(ms) { return new Promise(resolve => { @@ -106,8 +106,8 @@ describe('Ready', function() { setTimeout(function() { assert(spyError.callCount === 2); assert(spyReady.callCount === 2); - assert(spyReady.calls[0].arguments[0].message === 'aaa'); - assert(spyReady.calls[1].arguments[0].message === 'aaa'); + assert(spyReady.calls[0][0].message === 'aaa'); + assert(spyReady.calls[1][0].message === 'aaa'); done(); }, 20); }); @@ -129,7 +129,7 @@ describe('Ready', function() { setTimeout(function() { assert(spyError.callCount === 1); assert(spyReady.callCount === 1); - assert(spyReady.calledWith(err)); + assert.deepEqual(spyReady.calls[0][0], err); done(); }, 10); }); @@ -151,7 +151,7 @@ describe('Ready', function() { setTimeout(function() { assert(spyError.callCount === 1); assert(spyReady.callCount === 1); - assert(spyReady.calls[0].arguments[0].message === 'error'); + assert(spyReady.calls[0][0].message === 'error'); done(); }, 20); }); @@ -177,12 +177,12 @@ describe('Ready', function() { describe('ready stat', function() { it('should emit ready_stat when every task end', function(done) { - const obj = new EventEmitter(); + const obj:any = new EventEmitter(); const ready = new Ready(); ready.mixin(obj); - const data = []; - const timeout = []; + const data: {id: string; remain: string[]}[] = []; + const timeout: string[] = []; obj.on('ready_stat', function(e) { data.push(e); }); @@ -253,8 +253,8 @@ describe('Ready', function() { setTimeout(function() { assert(spyReady.callCount === 1); assert(spyTimeout.callCount === 2); - assert(spyTimeout.calledWith('a')); - assert(spyTimeout.calledWith('d')); + assert.deepEqual(spyTimeout.calls[0], [ 'a' ]); + assert.deepEqual(spyTimeout.calls[1], [ 'd' ]); done(); }, 150); }); @@ -282,8 +282,8 @@ describe('Ready', function() { setTimeout(function() { assert(spyReady.callCount === 1); assert(spyTimeout.callCount === 2); - assert(spyTimeout.calledWith('a')); - assert(spyTimeout.calledWith('c')); + assert.deepEqual(spyTimeout.calls[0], [ 'c' ]); + assert.deepEqual(spyTimeout.calls[1], [ 'a' ]); done(); }, 150); }); @@ -361,7 +361,7 @@ describe('Ready', function() { setTimeout(function() { assert(spyError.callCount === 1); assert(spyReady.callCount === 1); - assert(spyReady.calledWith(err)); + assert.deepEqual(spyReady.calls[0][0], err); done(); }, 20); }); @@ -369,7 +369,7 @@ describe('Ready', function() { describe('error', () => { it('should get error in ready', done => { - const obj = {}; + const obj:any = {}; const ready = new Ready(); ready.mixin(obj); @@ -382,7 +382,7 @@ describe('Ready', function() { }); it('should ready with error after callback error', done => { - const obj = {}; + const obj:any = {}; const ready = new Ready(); ready.mixin(obj); @@ -394,27 +394,27 @@ describe('Ready', function() { }); }); - it('should get error object when pass other type in callback', function* () { - let err = yield assertErrorType('error'); + it('should get error object when pass other type in callback', async function() { + let err = await assertErrorType('error'); assert(err.message === 'error'); - err = yield assertErrorType(0); + err = await assertErrorType(0); assert(err.message === '0'); - err = yield assertErrorType([ '1', '2' ]); + err = await assertErrorType([ '1', '2' ]); assert(err.message === '1,2'); - err = yield assertErrorType({}); + err = await assertErrorType({}); assert(err.message === '[object Object]'); - err = yield assertErrorType(true); + err = await assertErrorType(true); assert(err.message === 'true'); - err = yield assertErrorType(null); + err = await assertErrorType(null); assert(err === undefined); function assertErrorType(value) { - const obj = {}; + const obj:any = {}; const ready = new Ready(); ready.mixin(obj); @@ -427,7 +427,7 @@ describe('Ready', function() { }); it('should not throw when mixin an object that do not support events', function(done) { - const obj = {}; + const obj:any = {}; const ready = new Ready(); ready.mixin(obj); @@ -444,7 +444,7 @@ describe('Ready', function() { setTimeout(function() { assert(spyReady.called === true); - assert(spyReady.calledWith(err)); + assert.deepEqual(spyReady.calls[0][0], err); assert(spyError.callCount === 1); done(); }, 10); @@ -468,17 +468,17 @@ describe('Ready', function() { }); describe('lazy start', function() { - it('should ready after start manually', function* () { + it('should ready after start manually', async function() { const obj = new EventEmitter(); const ready = new Ready({ lazyStart: true }); const readySpy = spy(); ready.ready(readySpy); ready.mixin(obj); - yield sleep(1); + await sleep(1); assert(readySpy.called === false); ready.start(); - yield sleep(1); - assert(readySpy.called === true); + await sleep(1); + assert(readySpy.called); }); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..17c84c2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "target": "es2022", + "module": "nodenext", + "moduleResolution": "nodenext" + }, + "include": [ + "src", + "test" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file From cd2f99e04d0eeb55541a894bbe49a2d8fac2d9eb Mon Sep 17 00:00:00 2001 From: hanquliu Date: Wed, 11 Oct 2023 09:48:35 +0800 Subject: [PATCH 2/2] chore: bump get-ready from 3.0.0 to 3.1.0 --- package.json | 2 +- src/index.ts | 5 ++--- tsconfig.json | 9 +-------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e52e474..977efbe 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "src" ], "dependencies": { - "get-ready": "^3.0.0", + "get-ready": "^3.1.0", "once": "^1.4.0" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index c3d47e6..900e344 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,7 @@ import EventEmitter from 'node:events'; import { debuglog } from 'node:util'; import { randomUUID } from 'node:crypto'; import once from 'once'; -import ReadyObject = require('get-ready'); -import { type ReadyFunctionArg } from 'get-ready'; +import { Ready as ReadyObject, type ReadyFunctionArg } from 'get-ready'; const debug = debuglog('ready-callback'); @@ -42,7 +41,7 @@ class Ready extends EventEmitter { constructor(opt: ReadyOption = {}) { super(); - ReadyObject.default.mixin(this); + ReadyObject.mixin(this); this.opt = opt; diff --git a/tsconfig.json b/tsconfig.json index 17c84c2..a00905a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,12 +4,5 @@ "target": "es2022", "module": "nodenext", "moduleResolution": "nodenext" - }, - "include": [ - "src", - "test" - ], - "exclude": [ - "node_modules" - ] + } } \ No newline at end of file