I am given this task to perform some stuff with data obtained from an AirTable API with NodeJS. I am using the AirTableJS node library with the DefinitelyTyped airtable definitions (I am using NestJS).
So, naturally, I do the following to import the necessary packages.
yarn add airtable #types/airtable
Then, in my repository that interacts with the AirTable API, I have the following.
import { User } from "../entities/user";
import { ConfigService } from "#nestjs/config";
import { Inject, Injectable } from "#nestjs/common";
import { LogService } from "src/log/services/log/log.service";
import { Base } from "airtable";
#Injectable()
export class UsersRepository {
private readonly apiKey: string;
private readonly baseId: string;
constructor(#Inject(ConfigService) private readonly config: ConfigService, #Inject(LogService) private readonly log: LogService) {
this.apiKey = this.config.get<string>('airtable.apiKey');
this.baseId = this.config.get<string>('airtable.baseId');
}
async getUnmatchedUsers(): Promise<User[]> {
const base: Base = new Airtable({apiKey: this.apiKey}).base(this.baseId);
// other code here
}
}
But, when running it, I get the following error relating to the repository function:
ReferenceError: Airtable is not defined
Am I missing anything here or did I not import the Airtable package correctly?
Thanks.
It's not defined because you haven't imported Airtable.
That's probably going to look like:
import Airtable, { Base } from "airtable";
Currently using Airtable in a Typescript project (tsconfig below) and the following allowed me to make api calls against my Airtable base.
Node v16.5.1
Typescript v4.7.4
Airtable v0.11.4
import Airtable from 'airtable';
Airtable.configure({
endpointUrl: "https://api.airtable.com",
apiKey: `${process.env.AIRTABLE_API_KEY}`,
});
const base = Airtable.base(AIRTABLE_BASE);
base('myTableKey')
.select({
fields: [
'fieldA', 'fieldB'
]
})
{
"compilerOptions": {
// Enable top-level await, and other modern ESM features.
"target": "ESNext",
"module": "ESNext",
// Enable node-style module resolution, for things like npm package imports.
"moduleResolution": "node",
// Enable JSON imports.
"resolveJsonModule": true,
// Enable stricter transpilation for better output.
"isolatedModules": true,
// Add type definitions for our Vite runtime.
"types": ["node","vite/client"],
"outDir": "dist",
"allowSyntheticDefaultImports": true
}
}
You don't have the Airtable class imported from anywhere, so Node and Tyepscript don't have any idea what it is. The closest you have is the Base type imported from the airtable pacakge. As their documentation isn't public, it's hard to say how to fix it.
Related
So I have followed this guide to setup TypeScript in a ASP.NET 5 website project (with Razor Page). I want to have TypeScript typings with Node modules instead of just importing as any.
Now I want to add a Node module, like EthersJS, so I have:
"dependencies": {
"ethers": "^5.4.1"
}
This code would not compile:
import { ethers } from "ethers";
export class EthService {
pro: ethers.providers.BaseProvider;
constructor() {
const pro = this.pro = new ethers.providers.WebSocketProvider("");
}
async getBlockNumberAsync() {
return await this.pro.getBlockNumber();
}
}
unless I add "moduleResolution": "Node" to tsconfig.json. Obviously this wouldn't actually run in a browser.
How should I set it up so the Node module get compiled somehow? I think the problem can be solved if I can do either of these:
Make TypeScript/Gulp/MSBuild change import { ethers } from "ethers"; into proper path (I can copy the compiled lib file manually into wwwroot).
Manually set import path to import { ethers } from "path/to/compiled/ethers.js";, but I need to somehow tell TypeScript that that ethers typing is from ethers Node module.
UPDATE: I think a third possibility is very nice as well if I can just have a declare import (I will just add the ESM file into the global scope) like this:
declare import { ethers } from "ethers";
Is any of the above option possible? Or is there any way? Thanks.
Note: I know there is Triple-Slash Directives and it may solve my problem but I don't really understand what they do yet.
The current gulpfile.js:
/// <binding AfterBuild='default' Clean='clean' />
/*
This file is the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/
var gulp = require("gulp");
var del = require("del");
var paths = {
scripts: ["Scripts/**/*.js", "Scripts/**/*.ts", "Scripts/**/*.map"],
};
gulp.task("clean", function () {
return del(["wwwroot/Scripts/**/*"]);
});
gulp.task("default", function () {
gulp.src(paths.scripts).pipe(gulp.dest("wwwroot/Scripts"));
});
tsconfig.json:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node"
},
"exclude": [
"wwwroot",
"node_modules"
],
"compileOnSave": true
}
I am able to solve the problem by using a standalone ethers.js script file to expose ethers namespace to the global scope:
<script defer src="/libs/ethers.umd.min.js" asp-append-version="true"></script>
Now you need to tell TypeScript that there is an ethers "thing" (namespace/module) in the global scope. I found a solution thanks to this article. Create a .d.ts file (any will work, I name it globals.d.ts):
import { ethers as eth } from "ethers";
declare global {
// #ts-ignore: export typing
export { eth as ethers };
}
Now you can use ethers anywhere without needing any declaration. For example my whole EthService.ts file:
export class EthService {
pro: ethers.providers.WebSocketProvider;
init(server: string) {
this.pro = new ethers.providers.WebSocketProvider(server);
}
async getBlockNoAsync() {
return await this.pro.getBlockNumber();
}
}
I have a npm module in another directory that I'm using in another project using npm link, however when I import the module and try to use the function I get the following error a bunch of errors even though the typescript compiled successfully. Here is my tsconfig for the npm module:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true,
},
"include": ["src"],
"exclude": ["node_modules"]
}
Here is the index.ts of the module:
import * as APIServices from "./API/API"
import APIPage from "./API/APIPage"
export { APIServices, APIPage }
And here is how I'm trying to use the package:
import APIServices from 'common-backend'
console.log(APIServices)
But when I run the file I get the following errors:
TSError: тип Unable to compile TypeScript:
../../common-backend/lib/index.js:3:1 - error TS2323: Cannot redeclare exported variable 'APIPage'.
3 exports.APIPage = exports.APIServices = void 0;
~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:3:19 - error TS2323: Cannot redeclare exported variable 'APIServices'.
3 exports.APIPage = exports.APIServices = void 0;
~~~~~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:5:1 - error TS2323: Cannot redeclare exported variable 'APIServices'.
5 exports.APIServices = APIServices;
~~~~~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:7:1 - error TS2323: Cannot redeclare exported variable 'APIPage'.
7 exports.APIPage = APIPage_1.default;
Finally this is the index.js that the errors are being thrown on:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.APIPage = exports.APIServices = void 0;
var APIServices = require("./API/API");
exports.APIServices = APIServices;
var APIPage_1 = require("./API/APIPage");
exports.APIPage = APIPage_1.default;
Is there something that I forgot to add in the tsconfig? Is this the fault of the compiler transpiling the typescript incorrectly? Or does it have to do something with the way I'm import and exporting the files? I've been scratching my head for a while on this one and nobody else seems to have had the same issue.
Edit:
Forgot to include the API class I'm trying to import:
import { Validators } from "./Validators"
import { APIRoute, Config } from "./helpers/Interface"
import { Router } from "express"
const router = Router()
class API {
private config: Config
private _routes: APIRoute[]
constructor(config: Config) {
this.config = config
this._routes = []
}
get routes() : APIRoute[] {
return this._routes
}
set routes(routes: APIRoute[]) {
this._routes = [...routes]
}
/**
* Add route to routes array
* #param route
*/
addRoute(route: APIRoute) : void {
this.routes.push(route)
}
/**
* Instatiates existing routes in the routes array
*/
loadRoutes() : void {
for(const route of this._routes) {
try {
new route.module(router, route, this.config.authFunction)
}
catch(error) {
console.error(route)
console.error(error)
}
}
}
}
export { API, Validators, router }
I don't think will solve all your problems but your imports seem off. There are two ways of handling imports.
export default APIServices
And then you can import like you have
import APIServices from 'common-backend'
Whereas if you want to export multiple named things like you have
export { APIServices, APIPage }
You need to import them one of two ways
import { APIServices } from 'common-backend'
Or I think you can do something like this but I am less familiar with it because I personally don't like how it looks (I much rather just import what I need)
import * as CommonBackend from 'common-backend'
// then use CommonBackend.APIServices
So it turns out the issue was that I was using ts-node to compile and run the code in the project using the package. I changed node --inspect -r ts-node/register src/app.ts" to simply node ./dist/src/app.js and that solved everything. Although not sure what underlying elements were causing the javascript not to be run properly.
I am using Nestjs with WebStorm & TS 4.2.3^latest.
The problem that I am facing is a bit strange. For example, some modules, like axios can be installed, imported, and used as usual. But some modules, especially Nodejs Core, like fs or path, can't be imported as modules. BUT their methods can be imported and used just fine!
//ERROR: Module undefined on run:dev, but no error in IDE
import path from 'path';
import fs from 'fs';
//Working fine
import { join } from 'path';
import { readFileSync } from 'path';
I am sure, they have correct TS types, even installed manually. For example:
import axios from 'axios';
import path from 'path'; //path is undefined
import { join } from 'path'; // working fine
import { Injectable } from '#nestjs/common';
#Injectable()
export class AppService {
async test(input: string): Promise<void> {
await axios.get() // working fine
await path.join() // Cannot read property 'join' of undefined
//BUT await join() // Works fine!
}
}
I have only one tsconfig.json which is generated by Nest Cli. I am starting my apps via npm start:dev -name and IDE don't show any errors in code, until I ran code.
tsconfig.json module part, just to be sure: "module": "commonjs", package.json doesn't have module part at all.
IDE in this case, misdirect me a bit. Almost forgot, that I am dealing with TS now. Some modules seem to have no default exports, so:
You should import as with them: import * as fs from 'fs';
Or, another option is enabling: "esModuleInterop": true, in your tsconfig.json
I want to create a TypeScript library as private npm package which can be used in Node.js (including 6.x) using ES6 with #types support and TypeScript.
The goal of the library is to extend the Request type from express and provide additional properties.
I created a new Node.js project and add this tsconfig.json:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"strict": true,
"types": ["mocha"]
}
}
These are the relevant parts of the package.json:
{
"name": "#myscope/my-lib",
"main": "dist",
"scripts": {
"build": "rm -rf ./dist && ./node_modules/.bin/tsc",
"test": "./node_modules/.bin/mocha test"
},
"private": true,
"dependencies": {
"joi": "11.4.0"
},
"devDependencies": {
"mocha": "^5.2.0",
"express": "^4.16.4",
"#types/express": "^4.16.1",
"#types/joi": "^14.3.0",
"#types/mocha": "^5.2.5",
"typescript": "^3.2.4"
}
}
My folder structure is this:
- dist
- src
- http
- security
- test
I created a new TypeScript file AuthenticatedRequest.ts in src/http:
import {Request} from "express";
import {UserReference} from "../security/UserReference";
export interface AuthenticatedRequest extends Request {
user: UserReference
}
src/security contains a UserReference.ts:
import {Claim} from "./Claim";
export interface UserReference {
claims: Claim[];
}
and a Claim.ts:
import {IClaim} from "./IClaim";
export class Claim implements IClaim {
type: string;
value: string;
constructor(type: string, value: string) {
this.type = type;
this.value = value;
}
}
IClaim.ts looks like this:
export interface IClaim {
type: string,
value: string
}
In test, I created AuthenticatedRequestTests.js (plain ES6, no TypeScript here to validation code completion and usage from ES6):
'use strict';
const assert = require('assert');
const Claim = require("../dist/security/Claim").Claim;
describe('req', () => {
it('should ', done => {
/** #type {AuthenticatedRequest} */
const req = {};
req.user = { claims: [new Claim('tenantId', '123')] };
assert.equal(req.user.claims[ 0 ].type, 'tenantId');
assert.equal(req.user.claims[ 0 ].value, '123');
return done();
});
});
Now I have sevaral questions:
Is this the expected TypeScript way to solve this?
Is it possible to just use require("../dist/security/Claim"); instead of require("../dist/security/Claim").Claim;?
Instead of using this jsdoc statement /** #type {AuthenticatedRequest} */ I would like to use /** #type {myLib.http.AuthenticatedRequest} */
I also created a local test project for integration and installed my library via npm link.
But instead of using
const Claim = require("#scope/my-lib/security/Claim").Claim; I have to use
const Claim = require("#scope/my-lib/dist/security/Claim").Claim;
How can I get rid of the dist folder name here?
Also, using the jsdoc comment for AuthenticatedRequest in the integration test project, I get the error that the type cannot be found:
package.json
There should be a field called types (or typings) telling your library consumers where are the type definitions for your project. If they are generated by TypeScript and saved to dist/index.d.ts, then that's the path that should be used.
"types": "./dist/index.d.ts"
There should be a field called files containing an array of files/directories that will be delivered to your end users.
Running tests
Is this the expected TypeScript way to solve this?
If you're using TypeScript to develop your library, there is no reason not to use TypeScript for your tests. There are TypeScript-compliant test runners out there (ts-jest used to be popular, and now Jest is capable of understanding TypeScript out of the box).
Is it possible to just use require("../dist/security/Claim"); instead of require("../dist/security/Claim").Claim;?
With TypeScript, a few kinds of syntax are possible. You could export Claim using a default export and do:
import Claim from "../dist/security/Claim";
or:
const Claim = require("../dist/security/Claim");
Instead of using this jsdoc statement /** #type {AuthenticatedRequest} */ I would like to use /** #type {myLib.http.AuthenticatedRequest} */.
You will need an import type. They look like that:
/**
* #type {import('path/to/AuthenticatedRequest')}
*/
const req {}
The path can be relative or absolute. If you'd like to treat the local codebase as if it were installed from npm, you can create another package.json file in your test directory and use a relative path to resolve your library module.
"dependencies": {
"my-library": "../path/to/the/root"
}
Also, using the jsdoc comment for AuthenticatedRequest in the integration test project, I get the error that the type cannot be found:
That's also solved by import types. Unless a type is not in the global scope, you need to import it before you can use it. Use import('path/to/AuthenticatedRequest') instead.
There are a few things going on. If you could create a public repository to demonstrate your problems, I'm sure it would be easier for us to help you address them.
I have an alternate answer for one part of your question. You asked
instead of using
const Claim = require("#scope/my-lib/security/Claim").Claim; I have to use
const Claim = require("#scope/my-lib/dist/security/Claim").Claim;
How can I get rid of the dist folder name here?
As Karol points out, you can use files in package.json so that when your library is published, you send the dist directory as the package root (plus package.json, README, etc). This is a great, established pattern, but has some downsides: installing the package from github: instead of NPM won't work, nor will npm link for local development.
As of very recent Node versions, you can use the exports property instead:
{
"exports": {
"./": "./dist/"
}
}
Now, require("my_lib/security/Claim") resolves to node_modules/my_lib/dist/security/Claim. It would be great if Typescript handled this automatically, but as far as I can tell you also have to specify a baseUrl and paths in your tsconfig.json, at least for now.
Currently, I'm trying to use the opencv4nodejs module within an Electron/Angular application that also uses TypeScript. I've tried several ways to do this. The following codeblock shows what I tried and what error message I get.
// This says cv is undefined:
const cv = window.require('electron').opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
// Same here:
const cv = window.require('electron').remote.opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
// Uncaught Error: The specified module could not be found. (Though the module does exist at that location)
const cv = window.require('electron').remote.require('opencv4nodejs');
const img = cv.imread('../assets/poop.jpg');
// Without window I get "Uncaught TypeError: fs.existsSync is not a function"
const remote = require('electron').remote;
const cv = remote.opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
I had the fs.existSync error before, trying to require something else. I fixed that by using the following tsconfig.app.json:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": ["node"] // Included this line
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
As far as I understand, the remote require is needed to load modules that normally only run on the node.js server. Still I can't seem to figure out how to require the module in my application. The author of the module was very helpful with build problems and other problems, but he never used his module together with TypeScript.
How do I remote require a module in a TypeScript/Angular/Electron based application?
[edit]
I also tried the following:
import { Injectable } from '#angular/core';
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import { ipcRenderer } from 'electron';
import * as childProcess from 'child_process';
#Injectable()
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
childProcess: typeof childProcess;
constructor() {
// Conditional imports
if (this.isElectron()) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.childProcess = window.require('child_process');
}
}
isElectron = () => {
return window && window.process && window.process.type;
}
require = (module: string) => {
return window.require('electron').remote.require(module);
}
}
Injecting this service into my component and calling electronService.require('opencv4nodejs') also did not work.
Since Electron v1.6.10, Electron ships with TypeScript definitions included.
In order to use remote via TypeScript, you can use the following import statement:
import {remote} from 'electron';