Cannot find module error when importing a module with exported enums - node.js

I'm trying to import a custom module that exports enum types. However, when I try to use the exported enums I get this error Error: Cannot find module 'Runescape'
The code works if I either do not use the enums, so the import statement does work, or if I move the enums into my main ts file. I've tried with the compiler option "preserveConstEnums"
tsconfig.json
{
"compilerOptions": {
"resolveJsonModule": true,
"moduleResolution": "node",
"esModuleInterop": true,
"sourceMap": true,
"outDir": "out",
"target": "es6"
}
}
Runescape.ts
declare module 'Runescape' {
...
/**
* #description Enum of all Runescape skills
* #enum
* #default
*/
export enum SkillsEnum {
ATTACK = 'attack',
STRENGTH = 'strength',
DEFENSE = 'defense',
RANGED = 'ranged',
PRAYER = 'prayer',
MAGIC = 'magic',
RUNECRAFT = 'runecraft',
CONSTRUCTION = 'construction',
HITPOINTS = 'hitpoints',
AGILITY = 'agility',
HERBLORE = 'herblore',
THIEVING = 'thieving',
CRAFTING = 'crafting',
FLETCHING = 'fletching',
SLAYER = 'slayer',
HUNTER = 'hunter',
MINING = 'mining',
SMITHING = 'smithing',
FISHING = 'fishing',
COOKING = 'cooking',
FIREMAKING = 'firemaking',
WOODCUTTING = 'woodcutting',
FARMING = 'farming'
}
...
I expect to be able to run code like this
const OSRS_SKILLS_VALUES: string[] = Object.keys(Runescape.SkillsEnum).map(
(key: string): string => Runescape.SkillsEnum[key]
)
and not have the module not found error thrown

This is because Runescape.ts (at least the part you specified in your question) is not a module, it's a file that declares a module (and probably should have been named Runescape.d.ts).
Is Runescape.ts your code or a 3rd party?
If it is your code and you have additional code that makes the "Runescape" module, you should put and export that enum in that module, typescript will take care of generating the deceleration file for you. If you just want to create an enum, export it directly from "Runescape.ts" (without the module declaration).
If it's not your code and you can't modify it, you can still use it by telling typescript compiler where to find the declaration of the module, you do it by adding a reference directive (adjust the path to the actual file, if needed):
/// <reference path="Runescape.ts" />
Then you can use it by importing the module:
import * as Runescape from "Runescape";
const OSRS_SKILLS_VALUES: string[] = Object.keys(Runescape.SkillsEnum).map(
(key: string): string => Runescape.SkillsEnum[key]
)
Or just directly the enum:
import { SkillsEnum } from "Runescape";
const OSRS_SKILLS_VALUES: string[] = Object.keys(SkillsEnum).map(
(key: string): string => SkillsEnum[key]
)
Note that in this case, just because typescript can compile this code doesn't mean it can be executed. Because the enum is just a declaration, the actual javascript code will assume there is already a module named "Runescape" that contains the real code of that enum (actually, it's javascript equivalent, there are no enums in javascript). It is your responsibility that this module is accessible in the runtime environment (node, browser, etc).
To understand better the concepts of modules and declarations files, you should read the typescript docs on modules, namespaces and modules and other related sections in the docs.

Related

Adding dynamic properties to typescript namespace in a declarations file

I am trying to create a #types package for a JavaScript library by submitting a PR to the DefinetlyTyped repository. The shape of the JavaScript library for which i am providing said types is as follows.
const COMMANDS = [
"about", "authorize", "backend", "cat", "check", "checksum", "cleanup",
"config", "config create", "config delete", "config disconnect", "config dump",
"config edit", "config file", "config password", "config providers",
/* A lot more commands which have been ommitted here */
];
const api = function(...args) {
// Omitting irrelevant implementation
}
const promises = api.promises = function(...args) {
// Omitting irrelevant implementation
}
COMAMNDS.forEach(command => {
Object.defineProperty(api, commandName, {
value: function(...args) {
return api(commandName, ...args);
}
});
Object.defineProperty(api.promises, command, {
value: function(...args) {
return promises(commandName, ...args);
}
});
});
module.exports = api;
Another constraint to bear in mind is that because of the use of module.exports above the checks for DefinitelyTyped insist that i make use of export = api syntax as opposed to the export default api syntax, (kindly please give this consideration inside the answers that you provide). The following has been my attempt thus far.
/// <reference types="node" />
import { ChildProcess } from "child_process";
declare const COMMANDS: [
"about", "authorize", "backend", "cat", "check", "checksum", "cleanup",
"config", "config create", "config delete", "config disconnect", "config dump",
"config edit", "config file", "config password", "config providers",
/* A lot more commands which have been ommitted here */
];
type CommandsUnion = typeof COMMANDS[number];
declare function api(
...args: (string | Object)[]
): ChildProcess;
declare namespace api {
type PromiseApiFn = (
...args: (string | Object)[]
) => Promise<Buffer>;
const promises: PromiseApiFn & {
[Command in CommandsUnion]: PromiseApiFn
};
}
export = api;
As you can gather from the above declaration file. I have failed to add the properties on api that are present in the COMMANDS array. This is the principal blocker that I am experiencing. Can you please suggest a work around for this or perhaps a different way to go about providing declarations for this library such that this issue does not arise.
Note, this edit introduces some annoyances I wrote about avoiding in explanations from the first revision of this answer. My original answer used ES module syntax in the declaration file, which is apparently unsupported by DefinitelyTyped (see the quoted documentation at the end of this answer).
It seems like you have a lot of it figured out. I'm going to address the outstanding concerns separately — however — here's a link to the TS Playground with the full example: TS Playground
For reference, here's a link to the source JS file that you shared with me in a comment, but the link is pinned to the version at the latest commit at the time of writing this answer: https://github.com/sntran/rclone.js/blob/8bd4e08afed96f44bd287e34068dfbf071095c9f/rclone.js
import type { ChildProcess } from 'node:child_process';
// ^^^^
I'm not 100% positive that it matters in a declaration file (since it deals with only types, not data), but it's always a good idea to use type modifiers on type imports.
type Command = (
| 'about' // Get quota information from the remote.
| 'authorize' // Remote authorization.
| 'backend' // Run a backend specific command.
| 'cat' // Concatenates any files and sends them to stdout.
| 'check' // Checks the files in the source and destination match.
// --- snip ---
| 'sync' // Make source and dest identical, modifying destination only.
| 'test' // Run a test command.
| 'touch' // Create new file or change file modification time.
| 'tree' // List the contents of the remote in a tree like fashion.
| 'version' // Show the version number.
);
This is the string literal union in your question, but without the data structure (which is unused). Note that I kept the inline comments from the module. Consumers might reference your types (but never the source code), so including those comments in your declaration file would be very helpful in that case. A declaration file is a documentation file that's in a format that is readable by the TS compiler.
type FnStringOrObjectArgs<R> = (...args: (string | object)[]) => R;
type ApiFn = FnStringOrObjectArgs<ChildProcess>;
// Expands to: (...args: (string | object)[]) => ChildProcess
type PromisesFn = FnStringOrObjectArgs<Promise<Buffer>>;
// Expands to: (...args: (string | object)[]) => Promise<Buffer>
The FnStringOrObjectArgs<R> utility produces the base function signature used by this module. The other function types are derived from it generically. You'll see type utilities like this in other libraries: they can help cut down on repetition when you have modular/derived types. This module doesn't have many types, so it's not really necessary, but I wanted to mention the pattern.
Note: I chose arbitrary names for those types — the names you choose are obviously up to you.
Now, for the type of api:
/**
* Spawns a rclone process to execute with the supplied arguments.
*
* The last argument can also be an object with all the flags.
*
* Options for the child process can also be passed into this last argument,
* and they will be picked out.
*
* #param args arguments for the API call.
* #returns the rclone subprocess.
*/
declare const api: ApiFn & Record<Command, ApiFn> & {
/** Promise-based API. */
promises: PromisesFn & Record<Command, PromisesFn>;
};
TS consumers won't get the JSDoc from the source JS file, so including it is essential.
Below I'll discuss mapped types using the type utility Record<Keys, Type>:
First, let's look at the type of the promises property:
PromisesFn & Record<Command, PromisesFn>
The PromisesFn type was defined above, so this type is the intersection (&) of that function and an object whose keys are of type Command and whose values are of type PromisesFn. This results in the following behavior:
api.promises.about; // PromisesFn
api.promises.authorize; // PromisesFn
api.promises.backend; // PromisesFn
// ...etc.
and we can try to use them without error:
api.promises('authorize', 'something'); // Promise<Buffer>
api.promises.authorize('something'); // Promise<Buffer>
api.promises('authorize', {value: 'something'}); // Promise<Buffer>
api.promises.authorize({value: 'something'}); // Promise<Buffer>
Trying these invocations cause diagnostic errors:
api.promises('authorize', 100); /*
~~~
Argument of type 'number' is not assignable to parameter of type 'string | object'.(2345) */
api.promises.authorize(100); /*
~~~
Argument of type 'number' is not assignable to parameter of type 'string | object'.(2345) */
which is great: exactly as intended!
Now, with that done, understanding the type of api is trivial:
ApiFn & Record<Command, ApiFn> & {
/** Promise-based API. */
promises: PromisesFn & Record<Command, PromisesFn>;
}
It's the intersection of
ApiFn, and
an object with Command keys which have ApiFn values, and
an object with the property promises which has a value of the type PromisesFn & Record<Command, PromisesFn>
Let's check a few properties:
api.about; // ApiFn
api.authorize; // ApiFn
and invocations:
api('authorize', 'something'); // ChildProcess
api.authorize('something'); // ChildProcess
api('authorize', {value: 'something'}); // ChildProcess
api.authorize({value: 'something'}); // ChildProcess
looks good.
Now, for the exports. This is where it might seem a bit complicated (it is).
The JS module is in CommonJS format and uses this syntax for its exports:
module.exports = api;
This pattern cannot be represented statically because it creates a module which is in the shape of api itself: it has all the dynamically-assigned and typed properties of api, and is also a function.
The syntax to export this is archaic, but simple:
export = api;
Note that it also prevents writing other export statements in your file, even exporting of types — and my current understanding is that complex object types like api in this example, which is created from utility expressions, can't participate in declaration merging, so I don't know of a way to include the type alises in the exports. (Maybe someone else can comment with a solution to that.)
Here's more detail from the TS handbook on module declaration files and default exports:
One style of exporting in CommonJS is to export a function. Because a function is also an object, then extra fields can be added and are included in the export.
function getArrayLength(arr) {
return arr.length;
}
getArrayLength.maxInterval = 12;
module.exports = getArrayLength;
Which can be described with:
export default function getArrayLength(arr: any[]): number;
export const maxInterval: 12;
Note that using export default in your .d.ts files requires esModuleInterop: true to work. If you can’t have esModuleInterop: true in your project, such as when you’re submitting a PR to Definitely Typed, you’ll have to use the export= syntax instead. This older syntax is harder to use but works everywhere. Here’s how the above example would have to be written using export=:
declare function getArrayLength(arr: any[]): number;
declare namespace getArrayLength {
declare const maxInterval: 12;
}
export = getArrayLength;
See Module: Functions for details of how that works, and the Modules reference page.

#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 to export a TypeScript module so that can be imported both from TypeScript and Node?

Let's say a have the following TypeScript module:
function foo () {
return 123;
}
//TODO: Export code here
I want to export it in such a way that can be imported in these ways from TypeScript:
import foo from 'foo';
import * as foo from 'foo';
and in this way from Node:
const foo = require ( 'foo' );
Requirements:
I don't what the users of my module to have to set the allowSyntheticDefaultImports option
I want the code for exporting the module to be as clean as possible
I want to preserve type definitions
So far I've come up with the following "solutions", but they either don't preserve type definitions well enough or are too verbose:
export = foo['default'] = foo as typeof foo & { default: typeof foo };
export = foo['default'] = foo;
Is there a better way?
This is the best that I could come up with:
export = Object.assign ( foo, { default: foo } );
It's pretty terse, type definitions are properly generated, and it can be imported using all the aforementioned methods.
Since you are publishing as a commonjs package, you don't need rollup/webpack.
All you need is to transpile your code to es5 in commonjs using the TypeScript compiler.
Your tsconfig.json should look like this:
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"outDir": "dist" // customize this yourself.
...
}
}
And in your package.json:
// package.json
{
"main": "dist/index.js",
"typings": "dist/inde.d.ts",
"files": [
"dist" // customize this yourself.
],
...
}
Here is an example repository you can take a look into:
https://github.com/unional/path-equal
As far as I know export and imports is not supported in NodeJS yet.
Check this article: node-js-syntaxerror-unexpected-token-import
And as far as I know the only way to use TypeScript types syntax is only in a .ts file so an option is to compile your needed .ts files to .js using Babel.

Extending TypeScript Global object in node.js

I have a node.js app that attaches some config information to the global object:
global.myConfig = {
a: 1,
b: 2
}
The TypeScript compiler doesn't like this because the Global type has no object named myConfig:
TS2339: Property 'myConfig' does not exist on type 'Global'.
I don't want to do this:
global['myConfig'] = { ... }
How do I either extend the Global type to contain myConfig or just tell TypeScript to shut up and trust me? I'd prefer the first one.
I don't want to change the declarations inside node.d.ts. I saw this SO post and tried this:
declare module NodeJS {
interface Global {
myConfig: any
}
}
as a way to extend the existing Global interface, but it doesn't seem to have any effect.
As of node#16 the NodeJS.Global interface has been removed in favor of globalThis.
You can declare new global variable in a module file as:
declare global {
var NEW_GLOBAL: string;
}
And in a non-module file (no top-level import/export) as:
declare var NEW_GLOBAL: string;
Important note: variable must be declared as var. let and const variables don't show up on globalThis.
I saw this SO post and tried this:
You probably have something like vendor.d.ts:
// some import
// AND/OR some export
declare module NodeJS {
interface Global {
spotConfig: any
}
}
Your file needs to be clean of any root level import or exports. That would turn the file into a module and disconnect it from the global type declaration namespace.
More : https://basarat.gitbooks.io/typescript/content/docs/project/modules.html
To avoid Typescript claim something like this:
TS2339: Property 'myConfig' does not exist on type 'Global'.
I suggest to define custom types. I do it under src/types/custom.d.ts file in my project:
declare global {
namespace NodeJS {
interface Global {
myConfig: {
a: number;
b: number;
}
}
}
}
Then I ensure these are considered by Typescript in tsconfig.json file:
{
...
"files": [
...
"src/types/custom.d.ts"
]
}
Now you're safe to use your custom property:
console.log(global.myConfig.a);
Putting the following file into our project's root directory worked.
global.d.ts
declare namespace NodeJS {
export interface Global {
myConfig: any
}
}
We're using "#types/node": "^7.0.18" and TypeScript Version 2.3.4. Our tsconfig.json file looks like this:
{
"compilerOptions": {
"module": "commonjs",
"target": "es6"
},
"exclude": [
"node_modules"
]
}
Use 'namespace' instead of 'module' to declare custom TypeScript
If you use any of the above answers and are using a newer version of Typescript you'll get a nag about using "module". You should consider namespace instead.
In order to satisfy the requirement here you'll actually need more than extending the Global interface. You'll also need to create a constant with the type as well if you want it to be accessible directly from the "globalThis" context.
NOTE: while the OP asked about an object literal the process is the same as you see here below. Rather than the "Debug" type being a function you would simply define the interface as need, then change "debug:" to myConfig or whatever you wish.
// typically I'll store the below in something like "typings.d.ts"
// this is because, at least typically, these overrides tend to
// be minimal in nature. You could break them up and Typescript
// will pick them up if you wish.
// Augmentations for the global scope can only be directly nested
// in external modules or ambient module declarations.
export {}
declare global {
// Definition or type for the function.
type Debug = (label: string) => (message: any, ...args: any[]) => void
// If defining an object you might do something like this
// interface IConfig { a: number, b: number }
// Extend the Global interface for the NodeJS namespace.
namespace NodeJS {
interface Global {
// Reference our above type,
// this allows global.debug to be used anywhere in our code.
debug: Debug
}
}
// This allows us to simply call debug('some_label')('some debug message')
// from anywhere in our code.
const debug: Debug
}
How the above might be used
For completeness in this example all we did was define a global so we can log a simple debug message. Here's how we'd bind the method to our global context.
global.debug = (label: string) => (message: any, ...args: any[]) => console.log(message, ...args)
We can also call our global debug method directly:
debug('express')(`${req.method}: ${req.url}`)
The only thing that works for me is this:
// lib/my.d.ts
import Global = NodeJS.Global;
export interface CDTGlobal extends Global {
cdtProjectRoot: string
}
and then using it in other files like so
import {CDTGlobal} from "../lib/my.d.ts";
declare const global: CDTGlobal;
const cwd = global.cdtProjectRoot; // works
What worked for me is:
declare global {
module NodeJS {
interface Global {
myConfig: any;
}
}
}
global.myConfig = 'it works!';
Only downside is when using it you will have to turn off the ESLint rule #typescript-eslint/no-namespace.
For completeness here is my tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["dom", "es2017"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"removeComments": true,
"resolveJsonModule": true,
"rootDir": "src",
"sourceMap": true,
"strict": true,
"target": "es6"
},
"exclude": ["dist", "node_modules"]
}
Copy my answer of another post:
globalThis is the future.
// Way 1
var abc: number
globalThis.abc = 200 // no error
// Way2
declare var age: number
globalThis.age = 18 // no error
I can get both type check and code intelligence.
declare namespace NodeJS {
interface Global {
property: string
}
}
But interface Global is point to global.GLOBAL.
You can get correct type check, is because:
declare var global: NodeJS.Global & typeof globalThis;
But you can not get better code intelligence by useing global.property, unless use global.GLOBAL.property.
So, you need to define global var global and extend interface Global both:
// myglobal.d.ts
declare namespace NodeJS {
interface Global {
property: string
}
}
declare var global: NodeJS.Global & typeof globalThis
And now you can get property intelligence when you type global.

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