Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ jobs:
uses: node-modules/github-actions/.github/workflows/node-test.yml@master
with:
os: 'ubuntu-latest'
version: '16, 18, 20, 22'
version: '16, 18, 20, 22, 23'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
23 changes: 23 additions & 0 deletions .github/workflows/pkg.pr.new.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Publish Any Commit
on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm install

- name: Build
run: npm run prepublishOnly --if-present

- run: npx pkg-pr-new publish
104 changes: 70 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![CI](https://github.com/node-modules/utility/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/utility/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/utility.svg?style=flat)](https://nodejs.org/en/download/)

[npm-image]: https://img.shields.io/npm/v/utility.svg?style=flat-square
[npm-url]: https://npmjs.org/package/utility
Expand All @@ -29,68 +30,97 @@ const utils = require('utility');
Also you can use it within typescript, like this ↓

```ts
import * as utility from 'utility';
import * as utils from 'utility';
```

### md5

```js
utils.md5('苏千').should.equal('5f733c47c58a077d61257102b2d44481');
utils.md5(Buffer.from('苏千')).should.equal('5f733c47c58a077d61257102b2d44481');
```ts
import { md5 } from 'utility';

md5('苏千');
// '5f733c47c58a077d61257102b2d44481'

md5(Buffer.from('苏千'));
// '5f733c47c58a077d61257102b2d44481'

// md5 base64 format
utils.md5('苏千', 'base64'); // 'X3M8R8WKB31hJXECstREgQ=='
md5('苏千', 'base64');
// 'X3M8R8WKB31hJXECstREgQ=='

// Object md5 hash. Sorted by key, and JSON.stringify. See source code for detail
utils.md5({foo: 'bar', bar: 'foo'}).should.equal(utils.md5({bar: 'foo', foo: 'bar'}));
md5({foo: 'bar', bar: 'foo'}).should.equal(md5({bar: 'foo', foo: 'bar'}));
```

### sha1

```js
utils.sha1('苏千').should.equal('0a4aff6bab634b9c2f99b71f25e976921fcde5a5');
utils.sha1(Buffer.from('苏千')).should.equal('0a4aff6bab634b9c2f99b71f25e976921fcde5a5');
```ts
import { sha1 } from 'utility';

sha1('苏千');
// '0a4aff6bab634b9c2f99b71f25e976921fcde5a5'

sha1(Buffer.from('苏千'));
// '0a4aff6bab634b9c2f99b71f25e976921fcde5a5'

// sha1 base64 format
utils.sha1('苏千', 'base64'); // 'Ckr/a6tjS5wvmbcfJel2kh/N5aU='
sha1('苏千', 'base64');
// 'Ckr/a6tjS5wvmbcfJel2kh/N5aU='

// Object sha1 hash. Sorted by key, and JSON.stringify. See source code for detail
utils.sha1({foo: 'bar', bar: 'foo'}).should.equal(utils.sha1({bar: 'foo', foo: 'bar'}));
sha1({foo: 'bar', bar: 'foo'}).should.equal(sha1({bar: 'foo', foo: 'bar'}));
```

### sha256

```js
utils.sha256(Buffer.from('苏千')).should.equal('75dd03e3fcdbba7d5bec07900bae740cc8e361d77e7df8949de421d3df5d3635');
```ts
import { sha256 } from 'utility';

sha256(Buffer.from('苏千'));
// '75dd03e3fcdbba7d5bec07900bae740cc8e361d77e7df8949de421d3df5d3635'
```

### hmac

```js
```ts
import { hmac } from 'utility';

// hmac-sha1 with base64 output encoding
utils.hmac('sha1', 'I am a key', 'hello world'); // 'pO6J0LKDxRRkvSECSEdxwKx84L0='
hmac('sha1', 'I am a key', 'hello world');
// 'pO6J0LKDxRRkvSECSEdxwKx84L0='
```

### decode and encode

```js
```ts
import { base64encode, base64decode, escape, unescape, encodeURIComponent, decodeURIComponent } from 'utility';

// base64 encode
utils.base64encode('你好¥'); // '5L2g5aW977+l'
utils.base64decode('5L2g5aW977+l') // '你好¥'
base64encode('你好¥');
// '5L2g5aW977+l'
base64decode('5L2g5aW977+l');
// '你好¥'

// urlsafe base64 encode
utils.base64encode('你好¥', true); // '5L2g5aW977-l'
utils.base64decode('5L2g5aW977-l', true); // '你好¥'
base64encode('你好¥', true);
// '5L2g5aW977-l'
base64decode('5L2g5aW977-l', true);
// '你好¥'

// html escape and unescape
utils.escape('<script/>"& &amp;'); // '&lt;script/&gt;&quot;&amp; &amp;amp;'
utils.unescape('&lt;script/&gt;&quot;&amp; &amp;amp;'); // '<script/>"& &amp;'
escape('<script/>"& &amp;');
// '&lt;script/&gt;&quot;&amp; &amp;amp;'
unescape('&lt;script/&gt;&quot;&amp; &amp;amp;');
// '<script/>"& &amp;'

// Safe encodeURIComponent and decodeURIComponent
utils.decodeURIComponent(utils.encodeURIComponent('你好, nodejs')).should.equal('你好, nodejs');
decodeURIComponent(encodeURIComponent('你好, Node.js'));
// '你好, Node.js'
```

### others

___[WARNNING] getIP() remove, PLEASE use `https://github.com/node-modules/address` module instead.___
___[WARNNING] `getIP()` remove, PLEASE use `https://github.com/node-modules/address` module instead.___

```js
// get a function parameter's names
Expand Down Expand Up @@ -164,12 +194,18 @@ utils.random(2, 1000); // [2, 1000)
utils.random(); // 0
```

### Timers
### Timeout

```js
utils.setImmediate(function () {
console.log('hi');
});
#### `runWithTimeout(scope, timeout)`

Executes a scope promise with a specified timeout duration. If the promise doesn't resolve within the timeout period, it will reject with a `TimeoutError`.

```ts
import { runWithTimeout } from 'utility';

await runWithTimeout(async () => {
// long run operation here
}, 1000);
```

### map
Expand Down Expand Up @@ -216,17 +252,17 @@ const res = utils.try(function () {
```Note``` that when you use ```typescript```, you must use the following methods to call ' Try '

```js
import * as utility from 'utility';
import { UNSTABLE_METHOD } from 'utility';

utility.UNSTABLE_METHOD.try(...);
UNSTABLE_METHOD.try(...);
...
```

### argumentsToArray

```js
function foo() {
const arr = utility.argumentsToArray(arguments);
const arr = utils.argumentsToArray(arguments);
console.log(arr.join(', '));
}
```
Expand Down Expand Up @@ -268,10 +304,10 @@ async () => {

```js
// assign object
utility.assign({}, { a: 1 });
utils.assign({}, { a: 1 });

// assign multiple object
utility.assign({}, [ { a: 1 }, { b: 1 } ]);
utils.assign({}, [ { a: 1 }, { b: 1 } ]);
```

## benchmark
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"pretest": "npm run lint -- --fix && npm run prepublishOnly",
"test": "egg-bin test",
"test-local": "egg-bin test",
"preci": "npm run prepublishOnly",
"preci": "npm run lint && npm run prepublishOnly && attw --pack",
"ci": "egg-bin cov",
"prepublishOnly": "tshy && tshy-after"
},
Expand All @@ -16,6 +16,7 @@
"unescape": "^1.0.1"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.1",
"@eggjs/tsconfig": "^1.3.3",
"@types/escape-html": "^1.0.4",
"@types/mocha": "^10.0.6",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './number.js';
export * from './string.js';
export * from './optimize.js';
export * from './object.js';
export * from './timeout.js';
37 changes: 37 additions & 0 deletions src/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export class TimeoutError extends Error {
timeout: number;

constructor(timeout: number) {
super(`Timed out after ${timeout}ms`);
this.name = this.constructor.name;
this.timeout = timeout;
Error.captureStackTrace(this, this.constructor);
}
}

// https://betterstack.com/community/guides/scaling-nodejs/nodejs-timeouts/
export async function promiseTimeout<T>(
promiseArg: Promise<T>,
timeout: number,
): Promise<T> {
let timer: NodeJS.Timeout;
const timeoutPromise = new Promise<never>((_, reject) => {
timer = setTimeout(() => {
reject(new TimeoutError(timeout));
}, timeout);
});

try {
return await Promise.race([ promiseArg, timeoutPromise ]);
} finally {
clearTimeout(timer!);
}
}

export async function runWithTimeout<T>(
scope: () => Promise<T>,
timeout: number,
): Promise<T> {
return await promiseTimeout(scope(), timeout);
}

61 changes: 61 additions & 0 deletions test/timeout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { strict as assert } from 'node:assert';
import * as utility from '../src/index.js';
import { runWithTimeout, TimeoutError, promiseTimeout } from '../src/index.js';

function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}

describe('test/timeout.test.ts', () => {
describe('runWithTimeout()', () => {
it('should timeout', async () => {
await assert.rejects(async () => {
await runWithTimeout(async () => {
await sleep(20);
}, 10);
}, (err: unknown) => {
assert(err instanceof TimeoutError);
assert.equal(err.timeout, 10);
assert.equal(err.message, 'Timed out after 10ms');
// console.error(err);
return true;
});

await assert.rejects(async () => {
await utility.runWithTimeout(async () => {
await sleep(1000);
}, 15);
}, (err: unknown) => {
assert(err instanceof TimeoutError);
assert.equal(err.timeout, 15);
assert.equal(err.message, 'Timed out after 15ms');
// console.error(err);
return true;
});
});

it('should timeout', async () => {
const result = await runWithTimeout(async () => {
await sleep(20);
return 100000;
}, 100);
assert.equal(result, 100000);
});
});

describe('promiseTimeout()', () => {
it('should timeout', async () => {
await assert.rejects(async () => {
await promiseTimeout(sleep(20), 10);
}, (err: unknown) => {
assert(err instanceof TimeoutError);
assert.equal(err.timeout, 10);
assert.equal(err.message, 'Timed out after 10ms');
// console.error(err);
return true;
});
});
});
});
Loading