Extending class from external 3rd party typescript module - node.js

Hello I have a problem with overwriting types
I want overwrite a type from a libary that adds a property to an other library's typings, the line is: https://github.com/discord-akairo/discord-akairo/blob/e092ce4e0c9e749418601476bcd054a30a262785/src/index.d.ts#L14
and in my code I declare it like this:
declare module 'discord.js' {
export interface Message {
util?: KopekUtil;
}
}
KopekUtil is extending the CommandUtil and the error i get is:
TS2717: Subsequent property declarations must have the same type. Property 'util' must be of type 'CommandUtil', but here has type 'KopekUtil'. index.d.ts(16, 13): 'util' was also declared here.

You mentioned trying to extend Command util class like so
export class KopekUtil extends CommandUtil{
constructor(handler, message: Message | CommandInteraction) {
super(handler, <Message>message);
}
send(options:string | MessageOptions , reply? : boolean){
//your logic
}
}
But I'm afraid it's not possible to overwrite a class that comes from external typescript module.
Although you can introduce a new method in class from external module or extend one of existing methods using object protoype.
The right way to do that
util.js
declare module 'discord-akairo'{
export interface CommandUtil {
mySendMethod(options:string | MessageOptions , reply?:any) : boolean
}
};
CommandUtil.prototype.mySendMethod = function (options:string | MessageOptions , reply?:any) : boolean{
return true;
}
Typescript now merges interfaces and you can use your extension
const message = new Message(new Client(),{},new TextChannel(new Guild(new Client(),{})))
message.util.mySendMethod("hello")

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!

Typescript - Dynamic class Type

I'm trying to build a sort of model Factory in Typescript.
I'm receiving a string parameter from an API call and I would like to istantiate a new object depending on the received value.
Here you can find a simple example of what I would like to accomplish:
/classes/ClassA.ts
export class ClassA {
doSomething() {
console.log("ClassA");
}
}
/classes/ClassB.ts
export class ClassB {
doSomething() {
console.log("ClassB");
}
}
/classes/index.ts
import { ClassA } from './ClassA';
import { ClassB } from './ClassB';
export { ClassA, ClassB }
Now, I would like to import all classes exported from index.ts (this file will be automatically updated when a new Class is being created) and run doSomething() on a class depending on a variable value:
/index.ts
import * as Classes from './classes';
const className: string = "ClassA";
new Classes[className]().doSomething()
In visualStudioCode I don't get any error, but at compile time I get:
error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof import("/testApp/src/tmp/classes/index")'.
Even changing className to "any" gives the same result.
If I remove className type
const className = "ClassA";
it works without any issue but I cannot proceed in this direction because received value is "typed" as string.
I know that prepending istantiation code with
// #ts-ignore
It works but I would like to avoid this kind of "tricks"
So, what would it be the correct way to type className getting it's possible values from the imported ts?
Thanks
Micko

declare global variable in seperate file nodejs+typescript

I am new to typescript and this might be a noob question.
I want to extend global variable provided by nodejs.
As per this blog I wrote this code and it is working
declare global {
namespace NodeJS {
interface Global {
appRoot: string;
}
}
}
import path from "path";
global.appRoot = path.join(__dirname,'../');
console.log(global.appRoot)
but I want to take this global to separate file and if I move this to a new global.d.ts file
I dont know what to export
I am getting this error
Augmentations for the global scope can only be directly nested in
external modules or ambient module declarations.
if do this
declare module NodeJS {
export interface Global {
appRoot: string;
}
}
I get this error
Property 'appRoot' does not exist on type 'Global & typeof globalThis'.
Property 'appRoot' does not exist on type 'Global & typeof globalThis'.
Which version of global declaration works always seems to depend on project setup. In your case, the following global.d.ts should work:
export {}; // make the file a module, to get rid of the warning
declare global {
namespace NodeJS {
interface Global {
appRoot: string;
}
}
}
Also make sure that only either one of the definitions is present.

TypeScript Cannot find namespace despite Variable being in same Class

I'm trying to define Callbackdefinitions to make it easier to work with many callbacks in my Node.js project.
My database.ts file looks like that:
export default class Database {
//Export the enums
public static LoadObjectResponse = LoadObjectResponse;
//Export Callback definitions
public static loadObjectCallback: (resultCode: Database.LoadObjectResponse) => void;//ERROR
...
}
enum LoadObjectResponse {
ERROR_ON_LOADING, //"Error on Loading Object.",
OBJECT_NOT_FOUND //"Object not found."
}
So I want a loadObjectCallback defined, that says that the parameter has to be of the enum Type LoadObjectResponse. But when I try to do it like that, the compiler always gives the error
Cannot find namespace "Database"
I don't understand why it gives me the error, the variable itself is in the definition of Database, why doesn't it work?
It gives me the same error when I try to use it in Classfunction definitions:
public static loadObject(MongoModel, searchObject, callback: Database.LoadObjectResponse) {//ERROR namespace Database not found
Again Error:
Cannot find namespace "Database"
Inside of functions in the Database class calling
Database.LoadObjectResponse
works flawlessly, why doesn't it work in variable definitions?
Cannot find namespace "Database"
This is a common learning curve issue. You need to understand and be comfortable with the intutive concept of declaration spaces : https://basarat.gitbook.io/typescript/project/declarationspaces
Things are distinct in the type declaration space or in the variable declaration space.
In your case public static LoadObjectResponse is a variable hence cannot be used as a type (error on annotation usage : Database.LoadObjectResponse).
Fix
Please don't treat a class as a namespace. The file is a module.
export class Database {
//Export Callback definitions
public static loadObjectCallback: (resultCode: LoadObjectResponse) => void;//ERROR
}
export enum LoadObjectResponse {
ERROR_ON_LOADING, //"Error on Loading Object.",
OBJECT_NOT_FOUND //"Object not found."
}
Also beware of export default : https://basarat.gitbook.io/typescript/main-1/defaultisbad
It's because Database.LoadObjectResponse is a property and not a type. You can't use properties as types.
To make this work, change it to use the type of the property:
static loadObjectCallback: (resultCode: typeof Database.LoadObjectResponse) => void;
Or refer directly to the enum type of LoadObjectResponse:
static loadObjectCallback: (resultCode: LoadObjectResponse) => void

Exception with type definition for random-string module

I am trying to write a .d.ts for random-string.
I have this code:
declare module "random-string" {
export function randomString(opts?: Object): string;
}
I am able to import the module no problem then with:
import randomString = require('random-string');
and invoke:
console.log(randomString); // --> [Function: randomString]
However, this doesn't work with or without an argument:
console.log(randomString({length: 10});
console.log(randomString());
I get this error from tsc:
error TS2088: Cannot invoke an expression whose type lacks a call signature.
I looked in the source for random-string and found this code for the method I am trying to interface with:
module.exports = function randomString(opts) {
// Implementation...
};
I managed to write a .d.ts for the CSON module, no problem, but that was exporting a 'class' rather than a function directly. Is that significant?
Your declaration says there is a module named random-string with a function named randomString within it...
So your usage should be:
console.log(randomString.randomString({ length: 10 }));
console.log(randomString.randomString());
If the module does actually supply the function directly, you should adjust your definition to do the same:
declare module "random-string" {
function randomString(opts?: Object): string;
export = randomString;
}
This would allow you to call it as you do in your question.

Resources