"as" notation with TypeScript, need to export a namespace? - node.js

I have this TypeScript code:
import * as suman from 'suman';
const Test = suman.init(module,{
ioc: {
a: 'foo',
b: 'far'
} as suman.Ioc
});
As you can see I am trying to declare that the ioc object has a type of suman.Ioc (ioc object should "adhere to the Ioc interface"). But my IDE says "cannot find namespace 'suman'".
How can I create a type and reference it in my code in this scenario? I'd like to be able to reference the Ioc object type from the suman import if possible.
In other words, I don't want to do this all in the same file:
declare namespace suman {
interface Ioc {
a: string,
b: string
}
}
import * as suman from 'suman';
const Test = suman.init(module,{
ioc: {
a: 'foo',
b: 'far'
} as suman.Ioc
});
the reason is because I would then have to repeat the namespace declaration for every file like this which shouldn't be necessary (or advised).

suman is typed but the typed version is not release yet.
For now, you can installing it by npm install sumanjs/suman if you are comfortable to use the latest code.
If you want to use the latest release AND use the typings, you can consider using typings to install the typings file: typings install suman=github:sumanjs/suman/lib/index.d.ts and include typings/index.d.ts in your tsconfig.json.
As for as suman.Ioc, it is a way to tell the compiler that "hey, I know that you think this 'thing' is of some other type, but I would like you to treat it as 'suman.Ioc'".
That's why, even if the typings is there, it will not do what you wanted.
Luckily, the typings supplied by suman will be working fine for you.

Related

Cannot find module when using type from another module in class-validator

I'm using typescript on both frontend and backend, so I wanted to create a "shared types" package for them. For the backend I'm using nest.js and I recently ran into an issue with the class-validator package.
In my shared types package I created the following enum-like type (since enums itself don't seem to be working if they are being used from a node module):
export const MealTypes = {
BREAKFAST: 'Breakfast',
LUNCH: 'Lunch',
DINNER: 'Dinner',
SNACK: 'Snack'
} as const;
export type ObjectValues<T> = T[keyof T];
export type MealType = ObjectValues<typeof MealTypes>;
I've installed the module locally using npm i and I'm able to import the type in my backend like this:
import { MealType, MealTypes } from '#r3xc1/shared-types';
Since I am not able to use this constant for the IsEnum class validator, I wrote my own:
#ValidatorConstraint({ name: 'CheckEnum', async: false })
export class CheckEnumValidator implements ValidatorConstraintInterface {
validate(value: string | number, validationArguments: ValidationArguments) {
return Object.values(validationArguments.constraints[0]).includes(value);
}
defaultMessage(args: ValidationArguments) {
return `Must be of type XYZ`;
}
}
and then I'm using it in a DTO class like this:
export class CreateMealDTO {
#Validate(CheckEnumValidator, [MealTypes])
#IsNotEmpty()
meal_type: MealType;
}
But as soon as I add the #Validate(...) I get the following error on start:
Error: Cannot find module '#r3xc1/shared-types'
It only does this, if I am passing a type that has been imported from a node module into a validator. It also happens with other validators like IsEnum.
I'm not really sure why this error is happening and I appreciate any hints or help!

#mailchimp/mailchimp_marketing/types.d.ts' is not a module in nodeJs

I imported import #mailchimp/mailchim_marketing in my NodeJS app:
import mailchimp from "#mailchimp/mailchimp_marketing";
However, it gives following error:
type.d.ts is not a module
I have searched to see if there is a #types/#mailchimp/mailchimp_marketing but I couldn't see it.
The type.d.ts file provided by #mailchimp/mailchimp_marketing doesn't have the types of the library and the package doesn't have a #types package too. So it's necessary to create your own to override the provided by him.
To do this, creates the folders #types/#mailchimp/mailchimp_marketing (one inside another) and creates the file index.d.ts inside mailchimp_marketing.
This file has to contain the declaration of the module, and inside than, the functions and types what you gonna use from library. In my case:
declare module '#mailchimp/mailchimp_marketing' {
type Config = {
apiKey?: string,
accessToken?: string,
server?: string
}
type SetListMemberOptions = {
skipMergeValidation: boolean
}
export type SetListMemberBody = {
email_address: string,
status_if_new: 'subscribed' | 'unsubscribed' | 'cleaned' | 'pending' | 'transactional'
merge_fields?: {[key: string]: any}
}
export default {
setConfig: (config: Config) => {},
lists: {
setListMember: (listId: string, subscriberHash: string, body: SetListMemberBody, opts?: SetListMemberOptions): Promise<void> => {}
}
}
}
SetListMemberBody has much more fields and setListMember is not void, but i added just what I gonna use. To discover this fields and functions I looked in the source code (https://github.com/mailchimp/mailchimp-marketing-node) and api documentation (https://mailchimp.com/developer/api/marketing/list-members/add-or-update-list-member/).
In my case (Typescript 3.7.3) was not necessary to change tsconfig.json, but if you use older version maybe is necessary to add "typeRoots" for your compilerOptions in tsconfig.json:
"compilerOptions": {
"typeRoots": ["#types", "node_modules/#types"]`
// other options
}
After all this, I used the library normally:
import mailchimp, { SetListMemberBody } from '#mailchimp/mailchimp_marketing'
import crypto from 'crypto'
mailchimp.setConfig({
apiKey: process.env.MAILCHIMP_KEY,
server: 'serverHere',
});
const listId = 'listIdHere'
export const addSubscriber = async (member: SetListMemberBody): Promise<void> => {
const hash = crypto.createHash('md5').update(member.email_address).digest('hex')
await mailchimp.lists.setListMember(listId, hash, member)
}
replace your code to this:
const mailchimp = require("#mailchimp/mailchimp_marketing");
Right, you wont have type safe but at least your code will work.
Types for #mailchimp/mailchimp_marketing are available meanwhile.
Use
yarn add -D #types/mailchimp__mailchimp_marketing
or
npm install --save-dev #types/mailchimp__mailchimp_marketing
to install the types package.
EDIT: the types do not seem to be complete.
With a similar issue with #mailchimp/mailchimp_transactional I had to create my own mailchimp__mailchimp_transactional.d.ts with declare module (this package also has just types.d.ts and it is almost empty unlike the types.d.ts in mailchimp_marketing package).
So you can type to create your own type description file using their types.d.ts, place it in #types folder of your project and add #types to tsconfig.json like this:
"compilerOptions": {
// other options here
"typeRoots": [
"#types",
"node_modules/#types"
]
mailchimp__mailchimp_transactional.d.ts
/* eslint-disable camelcase */
declare module '#mailchimp/mailchimp_transactional' {
...
}
A quick & dirty solution is to delete the types.d.ts file, which prevents the error, though you will no longer get any type information for the API.

How does Node.js module system handle modifying and accessing variable of the same module of different versions?

I have a custom Node.js module fooModule that has a private variable foo and public getter and setter to modify this variable.
I have another two modules: zooModule depends on the fooModule#^1.0.1 and cannot use pre-release so far and barModule that depends on the fooModule#^1.0.2-0 (pre-release patch version that contains some fix) and zooModule at the same time.
The barModule firstly sets the foo variable value and then zooModule reads the value of foo.
I have noticed that when the version of the fooModule dependency is the same, then it works as expected, in other words the foo variable is shared between two modules. However, using different versions results in undefined when accessing foo from zooModule.
Here is a small pseudo example to demonstrate the logic. Each of the modules is a standalone npm package.
// fooModule.js
let foo;
export const getFoo = () => foo;
export const setFoo = (newFoo) => foo = newFoo;
// zooModule.js uses v.1.0.1 of the fooModule
import { getFoo } from './fooModule.js'
export const zooFunc = () => {
const zoo = getFoo();
if(!zoo) return;
...
return zoo; //result depends on zoo
};
// barModule.js uses v.1.0.2-0 of the fooModule
import { setFoo } from './fooModule.js'
import { zooFunc } from './zooModule.js'
setFoo('foo');
zooFunc(); // What is the output?
As far as I am concerned, in case of different versions of the fooModule, we become two different instances of the module and accordingly of the variable foo?
I tried to explain the question best I could, but it was hard to explain what I mean, sorry if it is still unclear.
Could give me some hints where to read more about this or give some explanation on how this is supposed to work. Thanks for your time.
EDIT:I forgot to mention that I have this use case in a frontend project bundled by a webpack.

Typescript class can only be found if there isn't a reference to its properties?

I am using TypeScript 2.0 in VSCode, however, the errors highlighted are all confirmed by the TypeScript compiler. So I am importing a module:
import * as els from 'elasticsearch';
where elasticsearch has definitions installed, e.g. npm i #types/elasticsearch -S
Now if in my class I have a property with an els type like this:
private _client: els.Client;
There isn't an issue, however, if I have a property with a type like this:
search(term: string): Promise<els.Client.search> {}
then I get the error:
Module 'Elasticsearch' has no exported member 'Client'
How can the class not be found if I'm looking for one of its properties, but not if I just look for it?
You are right, the error message is confusing. It originates from your attempt to use els.Client.search as a type. You get similar messages if you try this:
import * as els from 'elasticsearch';
class Foo {
private _client: els.Client;
y: els.Client.search;
bar() {}
x: Foo.bar;
}
error TS2305: Module 'Elasticsearch' has no exported member 'Client'.
error TS2503: Cannot find namespace 'Foo'.
Note how in the second message it complains that it can't find Foo right within the Foo class. You might consider posting an issue for typescript about this.
How can the class not be found if I'm looking for one of its
properties, but not if I just look for it?
The real problem is that you probably want the return type of your search to be the same as the return type of els.Client.search. I don't think there is a better way to do that other than essentially copy els.Client.search declaration:
search<T>(term: string): Promise<els.SearchResponse<T>> {}

TypeScript module import in nodejs

What is best practice for importing modules in nodejs with typescript? I come from c# background so I want to do something like this
MyClass.ts
module MyNamespace {
export class MyClass {
}
}
app.ts
// something like using MyNamespace
new MyNamespace.MyClass();
or
MyClass.ts
export class MyClass {
}
app.ts
import MyClass = module("MyClass")
new MyClass();
I know I can do this and it will work, but then I have to think up for two names for each class
import MyClass2 = module("MyClass")
new MyClass2.MyClass();
Point is separating classes to multiple .ts files (preferably one file per class). So question is, how is this done?
You have two choices here:
If you insist on using CommonJS or AMD modules, then you will have to use external modules just the way you described it in your question. Whether or not you use a module to declare your own namespace is mostly a matter of taste. The only way to circumvent the issue of specifying two names is to create a variable that aliases the type:
mymodule.ts
export module MyNamespace {
export class MyClass {
}
}
app.ts
import ns = require('mymodule');
var myclass = new ns.MyNamespace.MyClass();
var myclassalias = ns.MyNamespace.MyClass;
var myclass2 = new myclassalias();
Your other option is to use internal modules which are mostly used to structure your code internally. Internal modules are brought into scope at compile time using reference paths.
mymodule.ts
module MyNamespace {
export class MyClass {
}
}
app.ts
///<reference path='mymodule.ts'/>
var myclass = new MyNamespace.MyClass();
I think you'll have to decide for yourself which of those two approaches is more appropriate.
You can import TypeScript modules into a node.js file using the typescript-require module, which was created for this specific purpose.
I would recommend against using the explicit module (or namespace) keyword, it's a vestigial remnant of an earlier time.* You generally don't need them because any typescript file with a top-level import or export is automatically a module. Your second myModule.ts example was good.
export class MyClass {
...
}
But when you import it to another typescript module, you'll want to use something like this:
import { MyClass } from './myModule';
var myInstance = new MyClass();
Personally, I don't like repetitiveness of line 1, but it is what the language calls for, so I've learned to accept it. I think the utility of this syntax isn't apparent unless you abandon the file-per-class pattern. You pick and choose what names to import from the module, so that no unintended namespace pollution occurs.
An alternative import syntax pulls in all names from the module, but you must qualify the names with the module when you use them. Therefore it is also name collision resistant.
import * as myModule from './myModule';
var myInstance = new myModule.MyClass();
There are exceptions to the general rule about not needing module / namespace keywords, but don't start by focusing on them. Think file == module.

Resources