Skip to content

Commit 6dda681

Browse files
committedNov 11, 2023
Require Node.js 18 and move to ESM
Fixes #40
1 parent 9214f98 commit 6dda681

File tree

9 files changed

+322
-364
lines changed

9 files changed

+322
-364
lines changed
 

‎.github/funding.yml

-4
This file was deleted.

‎.github/workflows/main.yml

+4-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
node-version:
13-
- 14
14-
- 12
15-
- 10
16-
- 8
13+
- 20
14+
- 18
1715
steps:
18-
- uses: actions/checkout@v2
19-
- uses: actions/setup-node@v1
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
2018
with:
2119
node-version: ${{ matrix.node-version }}
2220
- run: npm install

‎index.d.ts

+92-134
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,125 @@
1-
/// <reference types="node"/>
2-
import {LiteralUnion} from 'type-fest';
3-
import {Hash} from 'crypto';
1+
import {type Hash} from 'node:crypto';
2+
import {type LiteralUnion} from 'type-fest';
43

5-
declare namespace hasha {
6-
type ToStringEncoding = 'hex' | 'base64' | 'latin1';
7-
type HashaInput = Buffer | string | Array<Buffer | string>;
8-
type HashaEncoding = ToStringEncoding | 'buffer';
4+
export type HashInput = HashSyncInput | NodeJS.ReadableStream;
5+
export type HashSyncInput = Uint8Array | string | Array<Uint8Array | string>;
6+
export type StringEncoding = 'hex' | 'base64' | 'latin1';
7+
export type HashEncoding = StringEncoding | 'buffer';
98

10-
type AlgorithmName = LiteralUnion<
11-
'md5' | 'sha1' | 'sha256' | 'sha512',
12-
string
13-
>;
9+
export type HashAlgorithm = LiteralUnion<
10+
'md5' | 'sha1' | 'sha256' | 'sha512',
11+
string
12+
>;
1413

15-
interface Options<EncodingType = HashaEncoding> {
16-
/**
17-
Encoding of the returned hash.
18-
19-
@default 'hex'
20-
*/
21-
readonly encoding?: EncodingType;
22-
23-
/**
24-
Values: `md5` `sha1` `sha256` `sha512` _([Platform dependent](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options))_
25-
26-
_The `md5` algorithm is good for [file revving](https://github.com/sindresorhus/rev-hash), but you should never use `md5` or `sha1` for anything sensitive. [They're insecure.](https://security.googleblog.com/2014/09/gradually-sunsetting-sha-1.html)_
14+
export type Options<EncodingType extends HashEncoding = 'hex'> = {
15+
/**
16+
The encoding of the returned hash.
2717
28-
@default 'sha512'
29-
*/
30-
readonly algorithm?: AlgorithmName;
31-
}
32-
}
18+
@default 'hex'
19+
*/
20+
readonly encoding?: EncodingType;
3321

34-
declare const hasha: {
3522
/**
36-
Calculate the hash for a `string`, `Buffer`, or an array thereof.
23+
The available values are [platform dependent](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options).
3724
38-
@param input - Data you want to hash.
25+
_The `md5` algorithm is good for [file revving](https://github.com/sindresorhus/rev-hash), but you should never use `md5` or `sha1` for anything sensitive. [They're insecure.](https://security.googleblog.com/2014/09/gradually-sunsetting-sha-1.html)_
3926
40-
While strings are supported you should prefer buffers as they're faster to hash. Although if you already have a string you should not convert it to a buffer.
27+
@default 'sha512'
28+
*/
29+
readonly algorithm?: HashAlgorithm;
30+
};
4131

42-
Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation.
32+
/**
33+
Calculate the hash of a `string`, `Uint8Array`, or an array thereof.
4334
44-
@returns A hash.
35+
@param input - The value to hash.
36+
@returns A hash.
4537
46-
@example
47-
```
48-
import hasha = require('hasha');
38+
The operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
4939
50-
hasha('unicorn');
51-
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
52-
```
53-
*/
54-
(input: hasha.HashaInput): string;
55-
(
56-
input: hasha.HashaInput,
57-
options: hasha.Options<hasha.ToStringEncoding>
58-
): string;
59-
(input: hasha.HashaInput, options: hasha.Options<'buffer'>): Buffer;
40+
While strings are supported you should prefer buffers as they're faster to hash. Although if you already have a string you should not convert it to a buffer.
6041
61-
/**
62-
Asynchronously calculate the hash for a `string`, `Buffer`, or an array thereof.
42+
Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation.
6343
64-
In Node.js 12 or later, the operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
44+
@example
45+
```
46+
import {hash} from 'hasha';
6547
66-
@param input - Data you want to hash.
48+
await hash('unicorn');
49+
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
50+
```
51+
*/
52+
export function hash(input: HashInput, options?: Options<StringEncoding>): Promise<string>;
53+
export function hash(input: HashInput, options?: Options<'buffer'>): Promise<Buffer>;
6754

68-
While strings are supported you should prefer buffers as they're faster to hash. Although if you already have a string you should not convert it to a buffer.
55+
/**
56+
Synchronously calculate the hash of a `string`, `Uint8Array`, or an array thereof.
6957
70-
Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation.
58+
@param input - The value to hash.
59+
@returns A hash.
7160
72-
@returns A hash.
61+
While strings are supported you should prefer buffers as they're faster to hash. Although if you already have a string you should not convert it to a buffer.
7362
74-
@example
75-
```
76-
import hasha = require('hasha');
63+
Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation.
7764
78-
(async () => {
79-
console.log(await hasha.async('unicorn'));
80-
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
81-
})();
82-
```
83-
*/
84-
async(input: hasha.HashaInput): Promise<string>;
85-
async(
86-
input: hasha.HashaInput,
87-
options: hasha.Options<hasha.ToStringEncoding>
88-
): Promise<string>;
89-
async(input: hasha.HashaInput, options: hasha.Options<'buffer'>): Promise<Buffer>;
65+
@example
66+
```
67+
import {hashSync} from 'hasha';
9068
91-
/**
92-
Create a [hash transform stream](https://nodejs.org/api/crypto.html#crypto_class_hash).
69+
hashSync('unicorn');
70+
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
71+
```
72+
*/
73+
export function hashSync(input: HashSyncInput, options?: Options<StringEncoding>): string;
74+
export function hashSync(input: HashSyncInput, options?: Options<'buffer'>): Buffer;
9375

94-
@returns The created hash transform stream.
76+
/**
77+
Calculate the hash of a file.
9578
96-
@example
97-
```
98-
import hasha = require('hasha');
79+
@param filePath - Path to a file you want to hash.
80+
@returns The calculated file hash.
9981
100-
// Hash the process input and output the hash sum
101-
process.stdin.pipe(hasha.stream()).pipe(process.stdout);
102-
```
103-
*/
104-
stream(options?: hasha.Options<hasha.HashaEncoding>): Hash;
105-
106-
/**
107-
Calculate the hash for a stream.
108-
109-
@param stream - A stream you want to hash.
110-
@returns The calculated hash.
111-
*/
112-
fromStream(stream: NodeJS.ReadableStream): Promise<string>;
113-
fromStream(
114-
stream: NodeJS.ReadableStream,
115-
options?: hasha.Options<hasha.ToStringEncoding>
116-
): Promise<string>;
117-
fromStream(
118-
stream: NodeJS.ReadableStream,
119-
options?: hasha.Options<'buffer'>
120-
): Promise<Buffer>;
82+
The operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
12183
122-
/**
123-
Calculate the hash for a file.
84+
@example
85+
```
86+
import {hashFile} from 'hasha';
12487
125-
In Node.js 12 or later, the operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
88+
// Get the MD5 hash of an image
89+
await hashFile('unicorn.png', {algorithm: 'md5'});
90+
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
91+
```
92+
*/
93+
export function hashFile(filePath: string, options?: Options<StringEncoding>): Promise<string>;
94+
export function hashFile(filePath: string, options?: Options<'buffer'>): Promise<Buffer>;
12695

127-
@param filePath - Path to a file you want to hash.
128-
@returns The calculated file hash.
96+
/**
97+
Synchronously calculate the hash of a file.
12998
130-
@example
131-
```
132-
import hasha = require('hasha');
99+
@param filePath - Path to a file you want to hash.
100+
@returns The calculated file hash.
133101
134-
(async () => {
135-
// Get the MD5 hash of an image
136-
const hash = await hasha.fromFile('unicorn.png', {algorithm: 'md5'});
102+
@example
103+
```
104+
import {hashFileSync} from 'hasha';
137105
138-
console.log(hash);
139-
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
140-
})();
141-
```
142-
*/
143-
fromFile(filePath: string): Promise<string>;
144-
fromFile(
145-
filePath: string,
146-
options: hasha.Options<hasha.ToStringEncoding>
147-
): Promise<string>;
148-
fromFile(
149-
filePath: string,
150-
options: hasha.Options<'buffer'>
151-
): Promise<Buffer>;
106+
// Get the MD5 hash of an image
107+
hashFileSync('unicorn.png', {algorithm: 'md5'});
108+
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
109+
```
110+
*/
111+
export function hashFileSync(filePath: string, options?: Options<StringEncoding>): string;
112+
export function hashFileSync(filePath: string, options?: Options<'buffer'>): Buffer;
152113

153-
/**
154-
Synchronously calculate the hash for a file.
114+
/**
115+
Create a [hash transform stream](https://nodejs.org/api/crypto.html#crypto_class_hash).
155116
156-
@param filePath - Path to a file you want to hash.
157-
@returns The calculated file hash.
158-
*/
159-
fromFileSync(filePath: string): string;
160-
fromFileSync(
161-
filePath: string,
162-
options: hasha.Options<hasha.ToStringEncoding>
163-
): string;
164-
fromFileSync(filePath: string, options: hasha.Options<'buffer'>): Buffer;
165-
};
117+
@example
118+
```
119+
import {hashingStream} from 'hasha';
166120
167-
export = hasha;
121+
// Hash the process input and output the hash sum
122+
process.stdin.pipe(hashingStream()).pipe(process.stdout);
123+
```
124+
*/
125+
export function hashingStream(options?: Options): Hash;

‎index.js

+74-73
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
'use strict';
2-
const fs = require('fs');
3-
const path = require('path');
4-
const crypto = require('crypto');
5-
const isStream = require('is-stream');
1+
import fs from 'node:fs';
2+
import crypto from 'node:crypto';
3+
import {isStream} from 'is-stream';
64

7-
const {Worker} = (() => {
5+
const {Worker} = await (async () => {
86
try {
9-
return require('worker_threads');
10-
} catch (_) {
7+
return await import('node:worker_threads');
8+
} catch {
119
return {};
1210
}
1311
})();
@@ -29,7 +27,7 @@ const recreateWorkerError = sourceError => {
2927
};
3028

3129
const createWorker = () => {
32-
worker = new Worker(path.join(__dirname, 'thread.js'));
30+
worker = new Worker(new URL('thread.js', import.meta.url));
3331

3432
worker.on('message', message => {
3533
const task = tasks.get(message.id);
@@ -52,7 +50,7 @@ const createWorker = () => {
5250
});
5351
};
5452

55-
const taskWorker = (method, args, transferList) => new Promise((resolve, reject) => {
53+
const taskWorker = (method, arguments_, transferList) => new Promise((resolve, reject) => {
5654
const id = taskIdCounter++;
5755
tasks.set(id, {resolve, reject});
5856

@@ -61,90 +59,93 @@ const taskWorker = (method, args, transferList) => new Promise((resolve, reject)
6159
}
6260

6361
worker.ref();
64-
worker.postMessage({id, method, args}, transferList);
62+
worker.postMessage({id, method, arguments_}, transferList);
6563
});
6664

67-
const hasha = (input, options = {}) => {
68-
let outputEncoding = options.encoding || 'hex';
69-
70-
if (outputEncoding === 'buffer') {
71-
outputEncoding = undefined;
65+
export async function hash(input, options = {}) {
66+
if (isStream(input)) {
67+
return new Promise((resolve, reject) => {
68+
// TODO: Use `stream.compose` and `.toArray()`.
69+
input
70+
.on('error', reject)
71+
.pipe(hashingStream(options))
72+
.on('error', reject)
73+
.on('finish', function () {
74+
resolve(this.read());
75+
});
76+
});
7277
}
7378

74-
const hash = crypto.createHash(options.algorithm || 'sha512');
79+
if (Worker === undefined) {
80+
return hashSync(input, options);
81+
}
7582

76-
const update = buffer => {
77-
const inputEncoding = typeof buffer === 'string' ? 'utf8' : undefined;
78-
hash.update(buffer, inputEncoding);
79-
};
83+
let {
84+
encoding = 'hex',
85+
algorithm = 'sha512',
86+
} = options;
8087

81-
if (Array.isArray(input)) {
82-
input.forEach(update);
83-
} else {
84-
update(input);
88+
if (encoding === 'buffer') {
89+
encoding = undefined;
8590
}
8691

87-
return hash.digest(outputEncoding);
88-
};
89-
90-
hasha.stream = (options = {}) => {
91-
let outputEncoding = options.encoding || 'hex';
92+
const hash = await taskWorker('hash', [algorithm, input]);
9293

93-
if (outputEncoding === 'buffer') {
94-
outputEncoding = undefined;
94+
if (encoding === undefined) {
95+
return Buffer.from(hash);
9596
}
9697

97-
const stream = crypto.createHash(options.algorithm || 'sha512');
98-
stream.setEncoding(outputEncoding);
99-
return stream;
100-
};
98+
return Buffer.from(hash).toString(encoding);
99+
}
101100

102-
hasha.fromStream = async (stream, options = {}) => {
103-
if (!isStream(stream)) {
104-
throw new TypeError('Expected a stream');
101+
export function hashSync(input, {encoding = 'hex', algorithm = 'sha512'} = {}) {
102+
if (encoding === 'buffer') {
103+
encoding = undefined;
105104
}
106105

107-
return new Promise((resolve, reject) => {
108-
// TODO: Use `stream.pipeline` and `stream.finished` when targeting Node.js 10
109-
stream
110-
.on('error', reject)
111-
.pipe(hasha.stream(options))
112-
.on('error', reject)
113-
.on('finish', function () {
114-
resolve(this.read());
115-
});
116-
});
117-
};
106+
const hash = crypto.createHash(algorithm);
107+
108+
const update = buffer => {
109+
const inputEncoding = typeof buffer === 'string' ? 'utf8' : undefined;
110+
hash.update(buffer, inputEncoding);
111+
};
118112

119-
if (Worker === undefined) {
120-
hasha.fromFile = async (filePath, options) => hasha.fromStream(fs.createReadStream(filePath), options);
121-
hasha.async = async (input, options) => hasha(input, options);
122-
} else {
123-
hasha.fromFile = async (filePath, {algorithm = 'sha512', encoding = 'hex'} = {}) => {
124-
const hash = await taskWorker('hashFile', [algorithm, filePath]);
113+
for (const element of [input].flat()) {
114+
update(element);
115+
}
125116

126-
if (encoding === 'buffer') {
127-
return Buffer.from(hash);
128-
}
117+
return hash.digest(encoding);
118+
}
129119

130-
return Buffer.from(hash).toString(encoding);
131-
};
120+
export async function hashFile(filePath, options = {}) {
121+
if (Worker === undefined) {
122+
return hash(fs.createReadStream(filePath), options);
123+
}
132124

133-
hasha.async = async (input, {algorithm = 'sha512', encoding = 'hex'} = {}) => {
134-
if (encoding === 'buffer') {
135-
encoding = undefined;
136-
}
125+
const {
126+
encoding = 'hex',
127+
algorithm = 'sha512',
128+
} = options;
137129

138-
const hash = await taskWorker('hash', [algorithm, input]);
130+
const hash = await taskWorker('hashFile', [algorithm, filePath]);
139131

140-
if (encoding === undefined) {
141-
return Buffer.from(hash);
142-
}
132+
if (encoding === 'buffer') {
133+
return Buffer.from(hash);
134+
}
143135

144-
return Buffer.from(hash).toString(encoding);
145-
};
136+
return Buffer.from(hash).toString(encoding);
137+
}
138+
139+
export function hashFileSync(filePath, options) {
140+
return hashSync(fs.readFileSync(filePath), options);
146141
}
147142

148-
hasha.fromFileSync = (filePath, options) => hasha(fs.readFileSync(filePath), options);
143+
export function hashingStream({encoding = 'hex', algorithm = 'sha512'} = {}) {
144+
if (encoding === 'buffer') {
145+
encoding = undefined;
146+
}
149147

150-
module.exports = hasha;
148+
const stream = crypto.createHash(algorithm);
149+
stream.setEncoding(encoding);
150+
return stream;
151+
}

‎index.test-d.ts

+44-35
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,51 @@
1+
import process from 'node:process';
12
import {expectType} from 'tsd';
2-
import hasha = require('.');
3-
4-
expectType<string>(hasha('unicorn'));
5-
expectType<string>(hasha('unicorn', {algorithm: 'md5'}));
6-
expectType<string>(hasha('unicorn', {encoding: 'latin1'}));
7-
expectType<Buffer>(hasha('unicorn', {encoding: 'buffer'}));
8-
9-
expectType<string>(hasha(['unicorn']));
10-
expectType<string>(hasha([Buffer.from('unicorn', 'utf8')]));
11-
expectType<string>(hasha(['unicorn', Buffer.from('unicorn', 'utf8')]));
12-
13-
expectType<Promise<string>>(hasha.async('unicorn'));
14-
expectType<Promise<string>>(hasha.async('unicorn', {algorithm: 'md5'}));
15-
expectType<Promise<string>>(hasha.async('unicorn', {encoding: 'latin1'}));
16-
expectType<Promise<Buffer>>(hasha.async('unicorn', {encoding: 'buffer'}));
17-
18-
expectType<Promise<string>>(hasha.async(['unicorn']));
19-
expectType<Promise<string>>(hasha.async([Buffer.from('unicorn', 'utf8')]));
20-
expectType<Promise<string>>(hasha.async(['unicorn', Buffer.from('unicorn', 'utf8')]));
21-
22-
process.stdin.pipe(hasha.stream()).pipe(process.stdout);
23-
24-
expectType<Promise<string | null>>(hasha.fromStream(process.stdin));
25-
expectType<Promise<string | null>>(
26-
hasha.fromStream(process.stdin, {encoding: 'hex'})
3+
import {
4+
hash,
5+
hashSync,
6+
hashFile,
7+
hashFileSync,
8+
hashingStream,
9+
} from './index.js';
10+
11+
expectType<string>(hashSync('unicorn'));
12+
expectType<string>(hashSync('unicorn', {algorithm: 'md5'}));
13+
expectType<string>(hashSync('unicorn', {encoding: 'latin1'}));
14+
expectType<Buffer>(hashSync('unicorn', {encoding: 'buffer'}));
15+
16+
expectType<string>(hashSync(['unicorn']));
17+
expectType<string>(hashSync([Buffer.from('unicorn', 'utf8')]));
18+
expectType<string>(hashSync(['unicorn', Buffer.from('unicorn', 'utf8')]));
19+
20+
expectType<Promise<string>>(hash('unicorn'));
21+
expectType<Promise<string>>(hash('unicorn', {algorithm: 'md5'}));
22+
expectType<Promise<string>>(hash('unicorn', {encoding: 'latin1'}));
23+
expectType<Promise<Buffer>>(hash('unicorn', {encoding: 'buffer'}));
24+
25+
expectType<Promise<string>>(hash(['unicorn']));
26+
expectType<Promise<string>>(hash([Buffer.from('unicorn', 'utf8')]));
27+
expectType<Promise<string>>(hash(['unicorn', Buffer.from('unicorn', 'utf8')]));
28+
29+
process.stdin.pipe(hashingStream()).pipe(process.stdout);
30+
31+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
32+
expectType<Promise<string>>(hash(process.stdin));
33+
expectType<Promise<string>>(
34+
hash(process.stdin, {encoding: 'hex'}),
2735
);
28-
expectType<Promise<Buffer | null>>(
29-
hasha.fromStream(process.stdin, {encoding: 'buffer'})
36+
expectType<Promise<Buffer>>(
37+
hash(process.stdin, {encoding: 'buffer'}),
3038
);
39+
/* eslint-enable @typescript-eslint/no-unsafe-argument */
3140

32-
expectType<Promise<string | null>>(hasha.fromFile('unicorn.png'));
33-
expectType<Promise<string | null>>(
34-
hasha.fromFile('unicorn.png', {encoding: 'base64'})
41+
expectType<Promise<string>>(hashFile('unicorn.png'));
42+
expectType<Promise<string>>(
43+
hashFile('unicorn.png', {encoding: 'base64'}),
3544
);
36-
expectType<Promise<Buffer | null>>(
37-
hasha.fromFile('unicorn.png', {encoding: 'buffer'})
45+
expectType<Promise<Buffer>>(
46+
hashFile('unicorn.png', {encoding: 'buffer'}),
3847
);
3948

40-
expectType<string>(hasha.fromFileSync('unicorn.png'));
41-
expectType<string>(hasha.fromFileSync('unicorn.png', {encoding: 'base64'}));
42-
expectType<Buffer>(hasha.fromFileSync('unicorn.png', {encoding: 'buffer'}));
49+
expectType<string>(hashFileSync('unicorn.png'));
50+
expectType<string>(hashFileSync('unicorn.png', {encoding: 'base64'}));
51+
expectType<Buffer>(hashFileSync('unicorn.png', {encoding: 'buffer'}));

‎package.json

+16-9
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@
1010
"email": "sindresorhus@gmail.com",
1111
"url": "https://sindresorhus.com"
1212
},
13+
"type": "module",
14+
"exports": {
15+
"types": "./index.d.ts",
16+
"default": "./index.js"
17+
},
18+
"sideEffects": false,
1319
"engines": {
14-
"node": ">=8"
20+
"node": ">=18"
1521
},
1622
"scripts": {
1723
"test": "xo && ava && tsd"
@@ -44,19 +50,20 @@
4450
"easy"
4551
],
4652
"dependencies": {
47-
"is-stream": "^2.0.0",
48-
"type-fest": "^0.8.0"
53+
"is-stream": "^3.0.0",
54+
"type-fest": "^4.7.1"
4955
},
5056
"devDependencies": {
51-
"@types/node": "^12.7.5",
52-
"ava": "^2.4.0",
53-
"proxyquire": "^2.1.0",
54-
"tsd": "^0.8.0",
55-
"xo": "^0.24.0"
57+
"@types/node": "^20.9.0",
58+
"ava": "^5.3.1",
59+
"esmock": "^2.6.0",
60+
"tsd": "^0.29.0",
61+
"xo": "^0.56.0"
5662
},
5763
"xo": {
5864
"rules": {
59-
"import/no-unresolved": "off"
65+
"unicorn/no-await-expression-member": "off",
66+
"n/prefer-global/buffer": "off"
6067
}
6168
}
6269
}

‎readme.md

+39-48
Original file line numberDiff line numberDiff line change
@@ -16,60 +16,38 @@ Convenience wrapper around the core [`crypto` Hash class](https://nodejs.org/api
1616

1717
## Install
1818

19-
```
20-
$ npm install hasha
19+
```sh
20+
npm install hasha
2121
```
2222

2323
## Usage
2424

2525
```js
26-
const hasha = require('hasha');
26+
import {hash} from 'hasha';
2727

28-
hasha('unicorn');
28+
await hash('unicorn');
2929
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
3030
```
3131

32-
```js
33-
const hasha = require('hasha');
34-
35-
(async () => {
36-
console.log(await hasha.async('unicorn'));
37-
//=> 'e233b19aabc7d5e53826fb734d1222f1f0444c3a3fc67ff4af370a66e7cadd2cb24009f1bc86f0bed12ca5fcb226145ad10fc5f650f6ef0959f8aadc5a594b27'
38-
})();
39-
```
40-
41-
```js
42-
const hasha = require('hasha');
43-
44-
// Hash the process input and output the hash sum
45-
process.stdin.pipe(hasha.stream()).pipe(process.stdout);
46-
```
47-
48-
```js
49-
const hasha = require('hasha');
32+
## API
5033

51-
(async () => {
52-
// Get the MD5 hash of an image
53-
const hash = await hasha.fromFile('unicorn.png', {algorithm: 'md5'});
34+
See the Node.js [`crypto` docs](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options) for more about hashing.
5435

55-
console.log(hash);
56-
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
57-
})();
58-
```
36+
### hash(input, options?)
5937

60-
## API
38+
The operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
6139

62-
See the Node.js [`crypto` docs](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options) for more about hashing.
40+
Returns a hash asynchronously.
6341

64-
### hasha(input, options?)
42+
### hashSync(input, options?)
6543

6644
Returns a hash.
6745

6846
#### input
6947

70-
Type: `Buffer | string | Array<Buffer | string>`
48+
Type: `Uint8Array | string | Array<Uint8Array | string> | NodeJS.ReadableStream` *(`NodeJS.ReadableStream` is not available in `hashSync`)*
7149

72-
Buffer you want to hash.
50+
The value to hash.
7351

7452
While strings are supported you should prefer buffers as they're faster to hash. Although if you already have a string you should not convert it to a buffer.
7553

@@ -85,7 +63,7 @@ Type: `string`\
8563
Default: `'hex'`\
8664
Values: `'hex' | 'base64' | 'buffer' | 'latin1'`
8765

88-
Encoding of the returned hash.
66+
The encoding of the returned hash.
8967

9068
##### algorithm
9169

@@ -95,33 +73,46 @@ Values: `'md5' | 'sha1' | 'sha256' | 'sha512'` *([Platform dependent](https://no
9573

9674
*The `md5` algorithm is good for [file revving](https://github.com/sindresorhus/rev-hash), but you should never use `md5` or `sha1` for anything sensitive. [They're insecure.](https://security.googleblog.com/2014/09/gradually-sunsetting-sha-1.html)*
9775

98-
### hasha.async(input, options?)
76+
### hashFile(filePath, options?)
9977

100-
In Node.js 12 or later, the operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
78+
The operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
10179

102-
Returns a hash asynchronously.
80+
Returns a `Promise` for the calculated file hash.
81+
82+
```js
83+
import {hashFile} from 'hasha';
10384

104-
### hasha.stream(options?)
85+
// Get the MD5 hash of an image
86+
await hashFile('unicorn.png', {algorithm: 'md5'});
87+
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
88+
```
10589

106-
Returns a [hash transform stream](https://nodejs.org/api/crypto.html#crypto_class_hash).
90+
### hashFileSync(filePath, options?)
10791

108-
### hasha.fromStream(stream, options?)
92+
Returns the calculated file hash.
10993

110-
Returns a `Promise` for the calculated hash.
94+
```js
95+
import {hashFileSync} from 'hasha';
11196

112-
### hasha.fromFile(filepath, options?)
97+
// Get the MD5 hash of an image
98+
hashFileSync('unicorn.png', {algorithm: 'md5'});
99+
//=> '1abcb33beeb811dca15f0ac3e47b88d9'
100+
```
113101

114-
In Node.js 12 or later, the operation is executed using `worker_threads`. A thread is lazily spawned on the first operation and lives until the end of the program execution. It's unrefed, so it won't keep the process alive.
102+
### hashingStream(options?)
115103

116-
Returns a `Promise` for the calculated file hash.
104+
Returns a [hash transform stream](https://nodejs.org/api/crypto.html#crypto_class_hash).
117105

118-
### hasha.fromFileSync(filepath, options?)
106+
```js
107+
import {hashingStream} from 'hasha';
119108

120-
Returns the calculated file hash.
109+
// Hash the process input and output the hash sum
110+
process.stdin.pipe(hashingStream()).pipe(process.stdout);
111+
```
121112

122113
## Related
123114

124115
- [hasha-cli](https://github.com/sindresorhus/hasha-cli) - CLI for this module
125116
- [crypto-hash](https://github.com/sindresorhus/crypto-hash) - Tiny hashing module that uses the native crypto API in Node.js and the browser
126-
- [hash-obj](https://github.com/sindresorhus/hash-obj) - Get the hash of an object
117+
- [hash-object](https://github.com/sindresorhus/hash-object) - Get the hash of an object
127118
- [md5-hex](https://github.com/sindresorhus/md5-hex) - Create a MD5 hash with hex encoding

‎test.js

+43-40
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,66 @@
1-
import fs from 'fs';
2-
import {Writable as WritableStream} from 'stream';
1+
import fs from 'node:fs';
2+
import {Writable as WritableStream} from 'node:stream';
33
import test from 'ava';
4-
import isStream from 'is-stream';
5-
import proxyquire from 'proxyquire';
6-
import hasha from '.';
4+
import {isStream} from 'is-stream';
5+
import esmock from 'esmock';
6+
import {
7+
hash,
8+
hashSync,
9+
hashFile,
10+
hashFileSync,
11+
hashingStream,
12+
} from './index.js';
713

8-
test('hasha()', t => {
14+
test('hash()', async t => {
15+
t.is((await hash(Buffer.from('unicorn'))).length, 128);
16+
t.is((await hash('unicorn')).length, 128);
17+
t.is((await hash(['foo', 'bar'])).length, 128);
18+
t.is(await hash(['foo', Buffer.from('bar')]), hashSync('foobar'));
19+
t.true(Buffer.isBuffer(await hash(Buffer.from('unicorn'), {encoding: 'buffer'})));
20+
t.is((await hash(Buffer.from('unicorn'), {algorithm: 'md5'})).length, 32);
21+
t.is((await hash(fs.createReadStream('test.js'))).length, 128);
22+
});
23+
24+
test('hashSync()', t => {
925
const fixture = Buffer.from('unicorn');
10-
t.is(hasha(fixture).length, 128);
11-
t.is(hasha('unicorn').length, 128);
12-
t.is(hasha(['foo', 'bar']).length, 128);
13-
t.is(hasha(['foo', Buffer.from('bar')]), hasha('foobar'));
14-
t.true(Buffer.isBuffer(hasha(fixture, {encoding: 'buffer'})));
15-
t.is(hasha(fixture, {algorithm: 'md5'}).length, 32);
26+
t.is(hashSync(fixture).length, 128);
27+
t.is(hashSync('unicorn').length, 128);
28+
t.is(hashSync(['foo', 'bar']).length, 128);
29+
t.is(hashSync(['foo', Buffer.from('bar')]), hashSync('foobar'));
30+
t.true(Buffer.isBuffer(hashSync(fixture, {encoding: 'buffer'})));
31+
t.is(hashSync(fixture, {algorithm: 'md5'}).length, 32);
1632
});
1733

18-
test('hasha.async()', async t => {
19-
t.is((await hasha.async(Buffer.from('unicorn'))).length, 128);
20-
t.is((await hasha.async('unicorn')).length, 128);
21-
t.is((await hasha.async(['foo', 'bar'])).length, 128);
22-
t.is(await hasha.async(['foo', Buffer.from('bar')]), hasha('foobar'));
23-
t.true(Buffer.isBuffer(await hasha.async(Buffer.from('unicorn'), {encoding: 'buffer'})));
24-
t.is((await hasha.async(Buffer.from('unicorn'), {algorithm: 'md5'})).length, 32);
34+
test('hashFile()', async t => {
35+
t.is((await hashFile('test.js')).length, 128);
2536
});
2637

27-
test('hasha.stream()', t => {
28-
t.true(isStream(hasha.stream()));
38+
test('hashFile() - non-existent', async t => {
39+
await t.throwsAsync(hashFile('non-existent-file.txt'), {code: 'ENOENT'});
2940
});
3041

31-
test('hasha.fromStream()', async t => {
32-
t.is((await hasha.fromStream(fs.createReadStream('test.js'))).length, 128);
42+
test('hashFileSync()', t => {
43+
t.is(hashFileSync('test.js').length, 128);
44+
});
45+
46+
test('hashingStream()', t => {
47+
t.true(isStream(hashingStream()));
3348
});
3449

3550
test('crypto error', async t => {
36-
const proxied = proxyquire('.', {
51+
const proxied = await esmock('./index.js', {
3752
crypto: {
38-
createHash: () => {
53+
createHash() {
3954
const stream = new WritableStream();
4055
stream._write = function () {
4156
this.emit('error', new Error('some crypto error'));
4257
};
4358

4459
stream.setEncoding = () => {};
4560
return stream;
46-
}
47-
}
61+
},
62+
},
4863
});
4964

50-
await t.throwsAsync(proxied.fromStream(fs.createReadStream('test.js')), 'some crypto error');
51-
});
52-
53-
test('hasha.fromFile()', async t => {
54-
t.is((await hasha.fromFile('test.js')).length, 128);
55-
});
56-
57-
test('hasha.fromFile(non-existent)', async t => {
58-
await t.throwsAsync(hasha.fromFile('non-existent-file.txt'), {code: 'ENOENT'});
59-
});
60-
61-
test('hasha.fromFileSync()', t => {
62-
t.is(hasha.fromFileSync('test.js').length, 128);
65+
await t.throwsAsync(proxied.hash(fs.createReadStream('test.js')), {message: 'some crypto error'});
6366
});

‎thread.js

+10-15
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
'use strict';
2-
const fs = require('fs');
3-
const crypto = require('crypto');
4-
const {parentPort} = require('worker_threads');
1+
import fs from 'node:fs';
2+
import crypto from 'node:crypto';
3+
import {parentPort} from 'node:worker_threads';
54

65
const handlers = {
76
hashFile: (algorithm, filePath) => new Promise((resolve, reject) => {
87
const hasher = crypto.createHash(algorithm);
98
fs.createReadStream(filePath)
10-
// TODO: Use `Stream.pipeline` when targeting Node.js 12.
9+
// TODO: Use `Stream.pipeline`.
1110
.on('error', reject)
1211
.pipe(hasher)
1312
.on('error', reject)
@@ -16,32 +15,28 @@ const handlers = {
1615
resolve({value: buffer, transferList: [buffer]});
1716
});
1817
}),
19-
hash: async (algorithm, input) => {
18+
async hash(algorithm, input) {
2019
const hasher = crypto.createHash(algorithm);
2120

22-
if (Array.isArray(input)) {
23-
for (const part of input) {
24-
hasher.update(part);
25-
}
26-
} else {
27-
hasher.update(input);
21+
for (const part of [input].flat()) {
22+
hasher.update(part);
2823
}
2924

3025
const {buffer} = new Uint8Array(hasher.digest());
3126
return {value: buffer, transferList: [buffer]};
32-
}
27+
},
3328
};
3429

3530
parentPort.on('message', async message => {
3631
try {
37-
const {method, args} = message;
32+
const {method, arguments_} = message;
3833
const handler = handlers[method];
3934

4035
if (handler === undefined) {
4136
throw new Error(`Unknown method '${method}'`);
4237
}
4338

44-
const {value, transferList} = await handler(...args);
39+
const {value, transferList} = await handler(...arguments_);
4540
parentPort.postMessage({id: message.id, value}, transferList);
4641
} catch (error) {
4742
const newError = {message: error.message, stack: error.stack};

0 commit comments

Comments
 (0)
Please sign in to comment.