Type an untyped package with types from another package - typescript-typings

Is it possible to re-export an existing typed module and use its types for an untyped module?
I am importing a library react-final-form-html5-validation that is just a tiny wrapper around typed react-final-form.
Are there any options for this?
I have tried many many many things ......., but this looks most promising, however, react-final-form does not declare a namespace and so I cannot use its types like I can use the React below.
project/index.d.ts
/// <reference types="react-final-form" />
//// <reference types="./node_modules/react-final-form/dist/index.d.ts" />
//// <reference path="./node_modules/react-final-form/dist/index.d.ts" />
//// <amd-dependency path="./node_modules/react-final-form/dist/index.d.ts" />
// importing resolves the types, but also turns it into a module, which in turn breaks it
// import * as RFF from react-final-form;
// import { FieldProps } from react-final-form;
// export * from react-final-form;
declare module 'react-final-form-html5-validation' {
var Field: React.ComponentType<FieldProps>
}
project/src/index.tsx
import { Field, Abc } from 'react-final-form-html5-validation'
// correct: Module '"react-final-form-html5-validation"' has no exported member 'Abc'
// later in App.render() { return ( .....
<Field />
// Field is of type React.ComponentType<any>

I also ran into the same problem, and I'm not very experienced in typescript so I'm not sure if this is the correct method of handling this very well, but I think I have a solution that seems to be working.
I implemented the types to match the ones from flow that were provided in the react-final-form-html5-validation files, which was basically FieldProps, but with some extra props to account for the error messages. I've only tested a little bit, but it seems to be working at the minute. As I said though, I'm fairly new to typescript and have only a basic understanding.
declare module 'react-final-form-html5-validation' {
import { ComponentType } from 'react';
import { FieldProps } from 'react-final-form';
type Messages = {
badInput?: string;
patternMismatch?: string;
rangeOverflow?: string;
rangeUnderflow?: string;
stepMismatch?: string;
tooLong?: string;
tooShort?: string;
typeMismatch?: string;
valueMissing?: string;
};
export type Html5ValidationFieldProps = FieldProps<any> & Messages;
export const Field: ComponentType<Html5ValidationFieldProps>;
}
The <FieldProps<any> part was taken from the main react-final-form which seems to be how the Field element is exported from there too.

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!

Wrap a problematic npm package, while maintaining type information

The Problem
We're authoring an npm package containing React components that will be used in various (internal) web sites. There is a problematic npm package dependency that we are forced to use in our react .tsx files, that has these problems:
It doesn't expose any useful types despite having .d.ts files in it... they're empty.
It tries to run when required or imported server-side, instead of waiting until called, so we have to avoid a top-level import and instead do if (window) { const module = require('package-name') } and then use it inside that block only.
It is a frequent source of errors so everything in that library needs to be run inside of a try ... catch block.
Well, At Least We Have Types
We have already created our own types file which addressed problem #1:
// problematic-package-types.d.ts
declare module 'problematic-package' {
function doErrorProneButNecessaryThing(
foo: Record<string, unknown>,
bar: string
): void
}
The Needed Solution
The long term solution is to fix this problematic library and we're looking into how to get that done (but it's not in our direct control).
In the short term, though, we need a solution now.
Note that we are configuring dynamic requires in our npm package bundler to import them only at use-time, not treating them like other imports/requires. As our package is consumed inside other applications, we don't have full control over how that application bundling works or when the components are required, so our components may end up being required server-side when they shouldn't, and we have to tolerate that. We're still learning about some aspects of this.
My Wild (But Failed) Stab
My goal is to do something more DRY like this, where we solve all three problems of strong typing, detecting server-side execution & doing nothing, and adding error handling:
// hoping to leverage our module declaration above without importing anything
import type * as ProblematicPackage from 'problematic-package'
import wrapProblematicRequire from '../utils/my-sanity-preserving-module'
const wrappedProblematicPackage = wrapProblematicRequire<ProblematicPackage>()
// then later...
const foo: Record<string, unknown> = { property1: 'yes', property2: false }
const bar = 'yodeling'
wrappedProblematicPackage.invokeIfWindowReady(
'doErrorProneButNecessaryThing',
foo,
bar
)
However, TypeScript doesn't like the import type which unfortunately makes sense:
Cannot use namespace 'ProblematicPackage' as a type.
The Plea
How do I get the type information we've placed into problematic-package-types.d.ts to use as desired?
Or ANYTHING else. Honestly, I'm open to whatever, no matter how crude or hacky, so long as we get some clarity and reliability at call sites, with full type information as described. Suggestions/advice?
Full Details
Here is the full implementation of the wrapProblematicRequire function. I haven't tested it. It's probably awful. I'm sure it could be far better but I don't have time to get this helper module super clean right now. (My attempt to handle function type information isn't quite right.)
type Func = (...args: any[]) => any
type FunctionNames<T, TName extends keyof T> = T[TName] extends Func ? TName : never
type FunctionNamesOf<T> = FunctionNames<T, keyof T>
const wrapProblematicRequire = <T>(packageName: string) => ({
invokeIfWindowReady<TName extends FunctionNamesOf<T>>(
name: T[TName] extends Func ? TName : never,
...args: T[TName] extends Func ? Parameters<T[TName]> : never
): T[TName] extends Func ? ReturnType<T[TName]> : never {
if (!window) {
// #ts-ignore
return undefined
}
try {
// #ts-ignore
return require(packageName)[name] as T[TName](...args)
} catch (error: unknown) {
// ToDo: Log errors
// #ts-ignore
return undefined
}
}
})
export default wrapProblematicRequire
P.S. await import('problematic-package') didn't seem to work. Yes, problems abound.
Cannot use namespace 'ProblematicPackage' as a type.
Well, you can get the typeof that namespace, which seems to be what you want.
To test this, I setup the following:
// problem.js
export function doErrorProneButNecessaryThing(n) {
return n;
}
export function doErrorProneButNecessaryThing2(s) {
return s;
}
console.log('did side effect');
// problem.d.ts
export function doErrorProneButNecessaryThing(n: number): number;
export function doErrorProneButNecessaryThing2(s: string): string;
And now you can do:
import type * as ProblemNs from './problem';
type Problem = typeof ProblemNs;
// works
type A = Problem['doErrorProneButNecessaryThing'] // type A = (n: number) => number
Then the wrapProblematicRequire function just takes the name of the function as a generic, pulls the args for it, and pulls the return type.
const wrapProblematicRequire = <TName extends FunctionNamesOf<Problem>>(
name: TName,
...args: Parameters<Problem[TName]>
): ReturnType<Problem[TName]> | undefined => {
if (!window) return;
const problem = require('./problem'); // type is any, but types are enforced above
try {
return problem[name](...args);
} catch (err) {
console.log('error!');
}
};
Here require('./problem') returns the any type, but the generics keep everything key safe as long as typeof ProblemNs can be trusted.
Now to test that:
console.log('start');
const result: number = wrapProblematicRequire(
'doErrorProneButNecessaryThing',
123
);
console.log('end');
Which logs:
start
did side effect
end
Which seems to work!
Codesandbox

extend express request object with sequelize model using typescript 2

I have some models, such as:
import * as Sequelize from 'sequelize'; // !
export interface UserAttributes {
id?: number;
email?: string;
googleToken?: string;
}
export interface UserInstance extends Sequelize.Instance<UserInstance, UserAttributes>, UserAttributes {
}
export interface UserModel extends Sequelize.Model<UserInstance, UserAttributes> {
}
// ... model definition
I need use import statement to use generic type Sequelize.Instance<TInstance, TAttributes>
I wright some middleware to add user instance to request object (or req.session object, doesn't metter)
Then I need typescript to know my new extending, for this I have file src/_extra.ts with content:
/// <reference path="./models/db.ts" /> -- also does not work
declare namespace Express {
export interface Request {
oauthClient: Googleapis.auth.OAuth2; // Googleapis is namespace
user?: UserInstance; // or ContactsProject.Models.UserInstance;
}
export interface Session {/* ... some same */}
}
I need to import my UserInstance type from my model file. Imports break all my _extra namespace definitions, inside namespace I can't use imports.
I have tried create namespace ContactsProject, but I getting same problem: if I use imports, typescript don't see my namespace, if I don't, I cant define my UserInstance type.
Sequelize doesn't declare namespaces.
I just want import some generic type in my namespace.
I use typescript Version 2.0.3.
When you use an import statement in a file, TypeScript treats it as a module. Therefor any namespace you declare inside it will not be global anymore, but local to that module.
Import your interface, and then wrap the namespace declaration in a declare 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.

What's the correct way to use requireJS with typescript?

The examples I have found here and here say to use module(). However, when I compile I get "warning TS7021: 'module(...)' is deprecated. Use 'require(...)' instead."
So a couple of basic questions:
When using typescript and requireJS, how do I access a class in one
.ts file from another .ts file where requireJS will load the second
file and give me the class in the first file?
Is there a way to do the standard requireJS approach with two .ts files where the define() at the top loads the second ts file and returns back the object it builds at the end?
Sort-of the same as question #2. From a java script file, can I use the define() construct on a type script file to get the instantiated object? If so, how?
Update: The following gives me a tsc compile error:
///<reference path='../../libs/ExtJS-4.2.0.d.ts' />
///<reference path='../../libs/require.d.ts' />
import fdm = require("./file-definitions");
require(["../../scripts/ribbon"], function () {
export module Menu {
export class MainMenu {
I would have commented on David's reply to basarat's answer (regarding modules and classes), but I don't have the reputation. I know this question is stale, but I didn't find an answer elsewhere.
I succeeded by using basarat's videos, combined with some other resources, to figure it out for classes like David Thielen needed. Note that I no longer have entries for my ts source files, but I do have amd-dependency and import statements. In Eclipse with palantir's TS plugin, my code completion and ability to jump from usage to definition is working with just the amd-dependency and import statements. The header files still need statements since they have nothing to do with deployment and are only used by the TS compiler. Note also that the .ts file extensions are used for reference statements but not the amd and import statements.
In Utils.ts I have:
///<reference path="headers/require.d.ts" />
export function getTime(){
var now = new Date();
return now.getHours()+":"+now.getMinutes()+':'+now.getSeconds();
}
In OntologyRenderScaler I have:
///<reference path="headers/require.d.ts" />
///<reference path="headers/d3.d.ts" />
///<reference path="headers/jquery.d.ts" />
///<amd-dependency path="Utils" />
import Utils = require('./Utils');
export class OntologyRenderScaler {
...
Utils.getTime();
...
}
In OntologyMappingOverview.ts I have:
///<reference path="headers/require.d.ts" />
///<reference path="headers/d3.d.ts" />
///<reference path="headers/jquery.d.ts" />
///<amd-dependency path="Utils" />
///<amd-dependency path="OntologyGraph" />
///<amd-dependency path="OntologyFilterSliders" />
///<amd-dependency path="FetchFromApi" />
///<amd-dependency path="OntologyRenderScaler" />
///<amd-dependency path="GraphView" />
///<amd-dependency path="JQueryExtension" />
import Utils = require('./Utils');
import OntologyGraph = require('./OntologyGraph');
import OntologyRenderScaler = require('./OntologyRenderScaler');
import OntologyFilterSliders = require('./OntologyFilterSliders');
import GraphView = require('./GraphView');
export class OntologyMappingOverview extends GraphView.BaseGraphView implements GraphView.GraphView {
ontologyGraph: OntologyGraph.OntologyGraph;
renderScaler: OntologyRenderScaler.OntologyRenderScaler;
filterSliders: OntologyFilterSliders.MappingRangeSliders;
...
this.renderScaler = new OntologyRenderScaler.OntologyRenderScaler(this.vis);
...
}
I didn't manage (yet!) to get things working like codeBelt suggested above, but an exchange we had on his blog revealed that if I get his approach working (with export MyClass at the bottom of the file), then I would not need to double up the imported identifer with the class name. I suppose it would export the class of interest rather than the namespace it is defined in (the implicit external module, i.e. the TypeScript file name).
For :
When using typescript and requireJS, how do I access a class in one
.ts file from another .ts file where requireJS will load the second
file and give me the class in the first file? Is there a way to do the
standard requireJS approach with two .ts files where the define() at
the top loads the second ts file and returns back the object it builds
at the end?
simply :
// from file a.ts
export class Foo{
}
// from file b.ts
// import
import aFile = require('a')
// use:
var bar = new aFile.Foo();
and compile both files with --module amd flag.
For :
Sort-of the same as question #2. From a java script file, can I use
the define() construct on a type script file to get the instantiated
object? If so, how?
To use a.ts from b.js simply :
// import as a dependency:
define(["require", "exports", 'a'], function(require, exports, aFile) {
// use:
var bar = new aFile.Foo();
});
This is similar to what you would get if you compile b.ts
You want the export statement below the class you are creating.
// Base.ts
class Base {
constructor() {
}
public createChildren():void {
}
}
export = Base;
Then to import and use into another class you would do:
// TestApp.ts
import Base = require("view/Base");
class TestApp extends Base {
private _title:string = 'TypeScript AMD Boilerplate';
constructor() {
super();
}
public createChildren():void {
}
}
export = TestApp;
I have been playing with typescript, trying to integrate it in our existing javascript/requirejs project.
As setup, I have Visual Studio 2013 with Typescript for vs v 0.9.1.1. Typescript is configured (in visual studio) to compile modules in amd format.
This is what I have found works for me (there might be a better way of course)
Use amd-dependency to tell the typescript compiler adds the required module to the list of components which must be loaded
In the constructor of the class being exported, use requirejs’s require function to actually fetch the imported module (at this point this is synchronous because of the previous step). To do this you must reference require.d.ts
As a side note, but since it is in my view essential to typescript, and because it gave me a bit of a headache, in the example I show two ways to export classes which use interfaces. The problem with interfaces is that they are used for type checking, but they produce no real output (the generated .js file is empty), and it causes problems of the type ‘’export of a private class”
I have found 2 ways of exporting classes which implement an interface:
Simply add an amd-dependency to the interface (as is in the Logger.ts file)
And export a typed variable holding a new instance of the class
The exported class can be consumed directly (ie myClass.log(‘hello’));
Don’t add the amd- dependency to the interface (as is in the Import.ts file)
And export a function (ie Instantiate()) which returns a variable of type any holding a new instance of the class
The exported class can be consumed via this function (ie myClass.instantiate().log(‘hello’))
It seems like the first option is better: you don’t need to call the instantiate function, plus you get a typed class to work with. The downside is that the [empty] interface javascript file does travel to the browser (but it’s cached there anyway, and maybe you are even using minification in which case this does not matter at all).
In the next blocks of code there are 2 typescript modules loaded with requires (amd): Logger and Import.
ILogger.ts file
interface ILogger {
log(what: string): void;
}
Logger.ts file
///<reference path="./ILogger.ts"/>
//this dependency required, otherwise compiler complaints of private type being exported
///<amd-dependency path="./ILogger"/>
class Logger implements ILogger {
formatOutput = function (text) {
return new Date() + '.' + new Date().getMilliseconds() + ': ' + text;
};
log = function (what) {
try {
window.console.log(this.formatOutput(what));
} catch (e) {
;
}
};
}
//this approach requires the amd-dependency above for the interafce
var exportLogger: ILogger = new Logger();
export = exportLogger;
Using Logger.ts in another ts file(Import.ts)
///<reference path="../../../ext/definitions/require.d.ts"/>
///<amd-dependency path="Shared/Logger"/>
///<amd-dependency path="./IImport"/>
class _Import implements IImport{
ko: any;
loggerClass: ILogger;
constructor() {
this.ko = require('knockout');//require coming from require.d.ts (in external_references.ts)
this.loggerClass = require('Shared/Logger');
}
init(vm: any) {
this.loggerClass.log('UMF import instantiated...');
}
}
////Alternative Approach:
////this approach does not require adding ///<amd-dependency path="./IImport"/>
////this can be consumed as <imported_module_name>.instantiate().init();
//export function instantiate() {
// var r : any = new _Import();// :any required to get around the private type export error
// return r;
//}
//this module can be consumed as <imported_module_name>.init();
var exported: IImport = new _Import();
export = exported;
IImport.ts file
interface IImport {
init(vm: any): void;
}
To consume the Import module straight from javascript use something like (sorry I have not tried this one, but it should work)
define (['Import'], function (import)
{
//approach 1
import.init();
////approach 2
//import.instantiate().init();
});

Resources