In typescript, promisify converts fs.stat to ts error 2349 - node.js

I am a typescript beginner, I have encountered some typescript warnings when using promisify to convert fs.stas.
const stat: (
pathname: string
) => Promise<fs.Stats | NodeJS.ErrnoException> = util.promisify(fs.stat);
Cannot invoke an expression whose type lacks a call signature. Type 'Stats' has no compatible call signatures.ts(2349)

What does your tsconfig.json and package.json look like?
The following works for me:
import fs from "fs";
import util from "util";
const stat: (pathname: string) => Promise<fs.Stats> = util.promisify(fs.stat);
tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true
}
}
package.json:
{
"dependencies": {
"#types/node": "^11.11.8",
"typescript": "^3.3.4000"
}
}
Also, you shouldn't specify the type of error stat throws in the type of the promise. So instead of Promise<fs.Stats | NodeJS.ErrnoException>, you should just do Promise<fs.Stats>

Related

Sinon stub failed when upgrade ts-node to v10

Everything is ok when my ts-node version is 8.x ~ 9.x , but sinon stub failed when I upgraded ts-node to 10.x
The code likes below:
// file: ./lambda.ts
export const getAWSLambda = () => new AWS.Lambda({ region: AWS_DEFAULT_REGION });
export class AWSLambda {
private readonly lambda: AWS.Lambda;
constructor() {
this.lambda = getAWSLambda();
}
async invoke(params: ServerlessParams): Promise<ServerlessResponse> {
const response = await this.lambda.invoke({
// ...
}).promise();
return {};
}
}
// file: unit.test.ts
import sinon from 'sinon';
import * as lambda from './lambda';
describe('Lambda Unit Test', () => {
let lambdaService: lambda.AWSLambda;
let sandbox: sinon.SinonSandbox;
let getAWSLambdaStub: sinon.SinonStub;
let invokeStub: sinon.SinonStub;
before(() => {
sandbox = sinon.createSandbox();
});
beforeEach(() => {
invokeStub = sandbox.stub();
getAWSLambdaStub = sandbox.stub(lambda, 'getAWSLambda');
getAWSLambdaStub.returns({
invoke: () => ({
promise: invokeStub,
}),
});
lambdaService = new lambda.AWSLambda();
});
it('shound succeed to invoke', async () => {
invokeStub.resolves({
StatusCode: 200,
});
// -----------
// called real function, stub failed.
const res = await lambdaService.invoke();
expect(getAWSLambdaStub).to.be.calledOnce;
expect(invokeStub).to.be.calledOnce;
});
});
The stub failed, the real function was called.
sinon: 11.1.2
ts-node: 10.2.1
typescript: 3.9.7
node: 14.17.3
tsconfig.json:
{
"compilerOptions": {
"preserveConstEnums": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strict": true,
"moduleResolution": "node",
"sourceMap": false,
"outDir": "dist/src",
"allowJs": true,
"checkJs": true,
"noUnusedLocals": true,
"skipLibCheck": true
},
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc-experimental"
}
}
Everything is ok when my ts-node version is 8.x ~ 9.x , but sinon stub failed when ts-node is v10. Is there any change in the import of module for ts-node's swc ?
Add swc integration and new --transpiler option to use third-party transpilers for a massive speed boost on large codebases
I guess here is the cause of the problem, and I want to use ts-node v10, but I don't known how to fix the problems.

Migrate Node.js project to TypeScript from plain ES6

Is started migrating a Node.js project from plain ES6 to TypeScript.
What I did:
npm install -g typescript
npm install #types/node --save-dev
Setup tsconfig.json:
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "es6",
"sourceMap": true,
"outDir": "dist",
"allowJs": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules",
"dist",
"docs"
]
}
Change all file extensions from .js to .ts (except in node_modules):
find . -not \( -path node_modules -prune \) -iname "*.js" -exec bash -c 'mv "$1" "${1%.js}".ts' - '{}' \;
Running tsc now leads to a ton of errors like these:
server.ts:13:7 - error TS2451: Cannot redeclare block-scoped variable 'async'.
13 const async = require('async');
~~~~~
Or these:
bootstrap/index.ts:8:7
8 const async = require('async');
~~~~~
'async' was also declared here.
Update:
The same happens for retry and other npm packages:
const retry = require('retry');
Changing require statements to ES6 import statements mostly fixed these but having to migrate a few thousands files at once is not feasable so I need a way to stick with require for a while. Is this possible?
It's possible, but you'll still have to edit those files.
Either of those methods will be enough.
Replace const ... = require() with import ... = require():
import async = require('async');
...
Add export {} to the top of the file:
export {};
const async = require('async');
...
The reason of initial issue is that in TS different files are not modules unless they explicitly declared as modules, thus they are compiled/executed in the same global scope, and that's why tsc is reporting you that async variable can't be redeclared.
From documentation:
In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module. Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well).
This is the same problem as this one.
In order to be treated as ES module, a file should contain either import or export statement, otherwise a variable is considered to be declared in global scope by TypeScript compiler (even if this is not so at runtime).
The solution is same as in linked question, to add dummy export {}. This could be done in batch with regex replacement but in case CommonJS , module.exports = ... exports are already in use, there may be a conflict between them.
The use of CommonJS require() imports results in untyped code. All major libraries already have according #types/... or built-in typings. Existing NPM packages can be matched with a regex from code base in order to install relevant #types/... packages in batch, imports like const async = require('async') can be replaced in batch with import async from 'async'. This requires esModuleInterop and allowSyntheticDefaultImports options to be set.
async is a protected keyword. When you use async/await you might skip the 'async' package. If you made ES6+ properly with ECMAScript modules (ESM) you also renamed all your files *.mjs, for example index.mjs. If you have the filename index.js it is most often assumed NOT to be ESM. You have to add types / interfaces to all your ES6 code, so depending on your case it might not be feasible to make all at once, that's why I give the example in ES2015+ ESM notation.
For TypeScript you should be able to use ESM because I guess you want more up to date notation. In order to use async at top level, the async function exist for doing that. Example code for index.mjs that include ES2015+ import from ES5/CommonJS *.js with module.exports and ESM import/export and finally dynamic import:
import { createRequireFromPath } from 'module'; // ESM import
import { fileURLToPath } from 'url';
const require = createRequireFromPath(fileURLToPath(import.meta.url));
// const untypedAsync = require('async');
class Index {
constructor() {
this._server = null;
this.host = `localhost`;
this.port = 8080;
}
set server(value) { this._server = value; }
get server() { return this._server; }
async start() {
const http = await import(`http`); // dynamic import
this.server = http.createServer(this.handleRequest);
this.server.on(`error`, (err) => {
console.error(`start error:`, err);
});
this.server.on(`clientError`, (err, socket) => {
console.error(`start clientError:`, err);
if (socket.writable) {
return socket.end(`HTTP/1.1 400 Bad Request\r\n\r\n`);
}
socket.destroy();
});
this.server.on(`connection`, (socket) => {
const arrival = new Date().toJSON();
const ip = socket.remoteAddress;
const port = socket.localPort;
console.log(`Request from IP-Address ${ip} and source port ${port} at ${arrival}`);
});
this.server.listen(this.port, this.host, () => {
console.log(`http server listening at ${this.host}:${this.port}`);
});
}
handleRequest(req, res) {
console.log(`url:`, req.url);
res.setHeader(`Content-Type`, `application/json`);
res.writeHead(200);
res.end(JSON.stringify({ url: req.url }));
}
}
export default Index; // ESM export
export const randomName = new Index(); // Usage: import { randomName } from './index.mjs';
async function main() {
const index = new Index();
const cjs = require(`./otherfile.js`); // ES5/CommonJS import
console.log(`otherfile:`, cjs);
// 'async' can be used by using: cjs.untypedAsync
await index.start();
}
main();
// in otherfile.js
const untypedAsync = require('async');
const test = {
url: "url test",
title: "title test",
};
module.exports = { test, untypedAsync }; // ES5/CommonJS export.
However, to use .mjs with typescript currently have some issues. Please look at the related typescript issues that are still open: .mjs input files and .mjs output files. You should at least transpile your .ts to .mjs to solve your problems. The scripts might look like (es6 to ts source):
// in package.json
"files": [ "dist" ],
"main": "dist/index",
"types": "dist/index.d.ts",
"scripts": {
"mjs": "tsc -d && mv dist/index.js dist/index.mjs",
"cjs": "tsc -m commonjs",
"start": "node --no-warnings --experimental-modules ./dist/index.mjs"
"build": "npm run mjs && npm run cjs"
},
"devDependencies": {
"typescript": "^3.2.2"
}
// in tsconfig.json
"compilerOptions": {
"module": "es2015",
"target": "ES2017",
"rootDir": "src",
"outDir": "dist",
"sourceMap": false,
"strict": true
}
Since you are migrating a large project to TypeScript, I would suggest using some tool like this package (https://www.npmjs.com/package/javascript-to-typescript) which could automate some of the work.
You can write a script to open each file in the project and add export {} at the top as suggested by #Styx in his answer.

TypeError: Reflect.hasOwnMetadata is not a function

I am trying to use inversify with typescript and node.js. I am currently using node.js version 6.9.1, typescript version 2.6.2, and ECMAScript 6. When I try to run the node.js application, I keep receiving the following error, "TypeError: Reflect.hasOwnMetadata is not a function".
Following documentation I found online, by adding import "reflect-matadata" at the top of the inversify.config.js file, it seemed to work for awhile, but now the problem is back again.
In my inversify.config.ts file, I have the following code that gets called from my app.ts file:
export let initialize = async (): Promise<Container> => {
var container = new Container();
try {
if (!Reflect.hasOwnMetadata("inversify:paramtypes", ClassToAddInjectableTo)) {
decorate(injectable(), ClassToAddInjectableTo);
}
….
container.bind<interfaces.Controller>(TYPE.Controller).to(MyController1).whenTargetNamed('MyController1');
container.bind<interfaces.Controller>(TYPE.Controller).to(MyController2).whenTargetNamed('MyController2');
} catch (ex) {
throw ex;
}
}
This code exists in app.ts and it calls the code in the inversify.config.ts file:
setup = async () => {
try {
let container: Container = await initialize();
…..
} catch (err){
throw err;
}
}
The problem seems to be on the following line in node_modules/inversify/lib/annotation/decorator_utils.js:22:
if (Reflect.hasOwnMetadata(metadataKey, annotationTarget) === true) {
In the generated inversify.config.js file, the following code seems to be above the import "reflect-metadata":
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
I noticed that the help I found online only indicated that I needed to add import "reflect-metadata".
Also, here is my tsconfig.json file:
{
"compilerOptions": {
"module": "commonjs",
"target": "ES6",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"types": ["reflect-metadata"],
"lib": ["ES6"],
"sourceMap": true,
"inlineSources": true,
"pretty": true,
"outDir": "dist",
"rootDir": "src",
"noLib": false,
"declaration": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
What could I be missing?
Update
For the issue above, I added the import "reflect-metadata" to the file inversify.config.ts file. However, I commented this import statement and added the import "reflect-metadata" to the app.ts file and now I am getting a different error:
throw new Error(ERRORS_MSGS.DUPLICATED_INJECTABLE_DECORATOR);
^
Error: Cannot apply #injectable decorator multiple times.
Now I found a post on the internet that seemed to describe indicate the adding import "reflect-metadata" adds a Reflect global variable. Also, I don't know if this helps but I removed the #injectable decorator from the controllers.
Try:
npm install reflect-metadata --save
Then import it only once in your entire application:
import "reflect-metadata"
If you are using inversify-express-utils make sure that your controllers are annotated with #controller not with #injectable. Also make sure that your coontrollers are imported once.
import "./controllers/some_controller";
import "./controllers/another_controller";
In case of failure during running jest test, add to the top of the spec file:
import "reflect-metadata";

typescript error running navalia example

I am trying to run this example from https://github.com/joelgriffith/navalia but for the light of me, I couldn't get it to work without error:
navaliatest.ts
/// <reference path="typings.d.ts" />
import { Chrome } from 'navalia';
const chrome = new Chrome();
async function buyItOnAmazon() {
const url = await chrome.goto('https://amazon.com');
const typed = await chrome.type('input', 'Kindle');
const clicked = await chrome.click('.nav-search-submit input');
chrome.done();
console.log(url, typed, clicked); // 'https://www.amazon.com/', true, true
}
buyItOnAmazon();
tsconfig.json
{
"files": [
"navaliatest.ts"
],
"compilerOptions": {
"noImplicitAny": false,
"target": "es6",
"moduleResolution": "node",
"paths": {
"*" : ["/usr/local/lib/node_modules/*"]
}
}
}
typings.d.ts
/// <reference path="/usr/local/lib/node_modules/navalia/build/Chrome.d.ts" />
declare module 'navalia' {
var Chrome: any;
export = Chrome;
}
Below are the versions:
MacBook-Pro:testcasperjs myusername$ node --version
v6.11.2MacBook-Pro:testcasperjs myusername$ npm --version
3.10.10
MacBook-Pro:testcasperjs myusername$ tsc --version
Version 2.4.2
This is the error I got although I do get .js file output:
MacBook-Pro:testcasperjs myusername$ tsc navaliatest.ts
../../../usr/local/lib/node_modules/navalia/node_modules/chrome-launcher/chrome-finder.ts(203,16): error TS2339: Property 'from' does not exist on type 'ArrayConstructor'.
../../../usr/local/lib/node_modules/navalia/node_modules/chrome-launcher/chrome-launcher.ts(99,15): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.
navaliatest.ts(3,10): error TS2305: Module ''navalia'' has no exported member 'Chrome'.
I am sure there is a stupid mistake somewhere but please could someone help me and take a look? Thanks.
You don't need to redeclare navalia. It has already been done for you at node_modules/navalia/build/index.d.ts given that moduleResolution is set to Node
You'll need to set module to commonjs so that you can run it in node
tsconfig.json
{
"files": [
"navaliatest.ts"
],
"compilerOptions": {
"noImplicitAny": false,
"target": "es6",
"module": "commonjs",
"moduleResolution": "Node"
}
}
navaliatest.ts (No change)
import { Chrome } from 'navalia';
const chrome = new Chrome();
async function buyItOnAmazon() {
const url = await chrome.goto('https://amazon.com');
const typed = await chrome.type('input', 'Kindle');
const clicked = await chrome.click('.nav-search-submit input');
chrome.done();
console.log(url, typed, clicked); // 'https://www.amazon.com/', true, true
}
buyItOnAmazon();
It'll create navaliatest.js with no errors, which can be run in node.

Cannot invoke an expression whose type lacks a call signature when using node-fetch

I'm trying to get node-fetch to work in my typescript project:
import * as fetch from 'node-fetch';
import * as assert from 'assert';
export class DatabaseConfigurator {
private url: string;
getNode (): Promise<string> {
return fetch(`${this.url}/_membership`).then((response: fetch.Response) => {
return response.json();
}).then((res: any) => {
assert.equal(res.all_nodes.length, 1);
return res.all_nodes[0];
});
}
}
And I get:
Cannot invoke an expression whose type lacks a call signature. Type 'typeof "/home/vinz243/compactd/node_modules/#types/node-fetch/index"' has no compatible call signatures.
When the definition i installed seems ok (node_modules/#types/node-fetch/index.d.ts):
...
export default function fetch(url: string | Request, init?: RequestInit): Promise<Response>;
My tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist/",
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"allowJs": true
},
"include": [
"./src/**/*"
]
}
You've imported the entire module rather than the default export, the fetch function. You're trying to call the entire module as a function which doesn't work.
Instead of
import * as fetch from 'node-fetch';
try
import fetch from 'node-fetch';

Resources