Extend typescript definitelyTyped definition file - javascript

I'm getting the following error:
src/models/priceAdjustment.ts(55,2): error TS2345: Argument of type '{ as: string; foreignKey: string; gql: any; }' is not assignable to parameter of type 'AssociationOptionsHasMany'.
Object literal may only specify known properties, and 'gql' does not exist in type 'AssociationOptionsHasMany'.
The error is obvious, I'm trying to assign a "gql" key which doesn't exist (I'm attaching it on so I can access it later on in my code somewhere else).
What I'd like to do, is extend the model.hasMany interface here, and add my own key. Is this possible? If so, how do I?
My current workaround is to cast the object to <any> before passing it in, which feels like a dirty workaround.
I have attempted the following:
import Sequelize from 'sequelize';
export declare module Sequelize {
export interface MyAssociationOptionsHasMany extends Sequelize.AssociationOptionsHasMany {
gql?: any
}
export interface MyAssociations extends Sequelize.Associations {
hasMany(target: Sequelize.Model<any, any>, options?: MyAssociationOptionsHasMany): void;
}
}
And now I get the following error:
TypeError: Cannot read property '239' of undefined
at getDeclarationSpaces (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33121:54)
at checkExportsOnMergedDeclarations (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:33067:41)
at checkModuleDeclaration (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35036:17)
at checkSourceElement (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35505:28)
at Object.forEach (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:275:30)
at checkSourceFileWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35566:20)
at checkSourceFile (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35551:13)
at getDiagnosticsWorker (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35604:17)
at Object.getDiagnostics (C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:35593:24)
at C:\Users\user\AppData\Roaming\npm\node_modules\typescript\lib\tsc.js:56188:85

You can do this via Declaration Merging. The best way I have found this to work is by importing the library into an "extension" file and extending the library from there. For instance,
extensions/library-name.extension.ts:
import AssociationOptionsHasMany from '/path/to/definition';
import 'library-name';
declare module 'library-name' {
interface InterfaceToExtend {
hasMany(options: AssociationOptionsHasMany): ReturnType;
}
}
In the above example, we want to extend an Interface created in the library-name module. Since we are using imports, TypeScript automatically creates a module for the file, so we have to tell TypeScript that we want to extend the interface in the library-name module, not the module created for this file. To do that, we simply declare the module using the same name as the library we imported.
After telling TypeScript in which module the interface should be extended, we just use Declaration Merging by defining an interface with the same name as the interface that should be extended and adding whatever we need to it. In this case, that would be a new method that takes as an argument the specific type we want to pass it.
I have also had to update the tsconfig.json file so the extensions would be loaded first. You can do that by including the extensions directory before the rest of the app:
{
/* Blah, settings, blah */
includes: ['./app/extensions/*.ts', './app/**/*.ts'],
/* Blah, settings, blah */
}
If you're using Sublime, it might complain still, but actually compiling the code or exiting out of Sublime and reopening it seems to work just fine.
A word of warning. Declaration Merging is powerful, but can result in unexpected behavior. In general, try to avoid extending components you (or someone else) have defined elsewhere. If may be a sign that you are using these components incorrectly or the need for refactoring. Remember, the benefit of TypeScript is strict(er) typing.

Related

How to override an existing node_module interface property

I have a custom type declaration file, in which I would like to override an already existing interface property (not create a new property, but modify existing)
I have tried several approaches, such as Omiting the property (shown below), with no luck.
src/types/example.d.ts
----------
import package, { AnInterface } from 'example'
interface CustomInstance extends Omit<Instance, 'clients'> {
clients: string[];
}
declare module "example-module" {
export interface Instance extends CustomInstance {
test: number
clients: CustomType
}
}
src/main.ts
-----------
example.test // number as expected
example.clients // is always the type from node_modules, not my .d.ts file
If I try to add clients without doing any extending, I getSubsequent property declarations must have the same type. Property 'clients' must be of type 'EDictionary', but here has type 'CustomType'
however if I try to extend the custom instance (as above) I get the error Type 'Instance' recursively references itself as a base type. (which even if I ts-ignore, my application will still use the type defined in node_modules).
You can't un-define something with d.ts, you can only define it more precisely.
Best you can to remove something is re-export everything from your module, changing its types
In your case you may want to not un-define clients but to narrow it down, then that will be
import { EDictionary } from 'example-module';
declare module "example-module" {
export interface Instance { // i.e. extends example-module.Instance
test: number
clients: CustomType & EDictionary // narrow it down in a compatible manner
}
}

Is there a way to declare a typescript class type that is similar to `any`?

I'm slowly converting a project to typescript and there is a dependency I'd like to add some types to via global.d.ts.
For example, that dependency has SomeClass I'd like to declare with types, but another dozen other classes I'd like to skip for now.
But, when I add types for SomeClass, it causes everything else to become errors "no exported member".
Is there a way to skip the others or declare them as any for now? Doing export type SkipThisClassForNow = any doesn't play nice with new SkipThisClassForNow().
Without any type declarations, typescript treats everything imported from that dependency as any and works. Is there a way to force typescript to behave that way? Or are type declarations like this none or all?
The example below exports Module which has index signature which will give the type any for any key that is not defined. Keys which are defined will export the defined type.
declare module 'dependency' {
class SomeClass {
constructor(a: boolean)
}
interface Module {
[key: string]: any
SomeClass: typeof SomeClass
}
const module: Module;
export = module;
}

How can I reference a type in lib.dom.d.ts in another declaration file?

I'm pretty new to TypeScript and trying to make automated tests where I dependency-inject IndexedDB into my code, but the library I'm using to mock IDB doesn't have a .d.ts, so I tried to roll my own simple declaration file to get around this, which looked something like this:
declare let fakeIndexedDB: IDBFactory;
declare module 'fake-indexeddb' {
export var fakeIndexedDB;
}
However, when I tried using this type, I got the error:
type 'typeof import("fake-indexeddb")' is missing the following properties from type 'IDBFactory': cmp, deleteDatabase, open
Mousing over in VSCode, it looks like the type of IDBFactory is this type from lib.dom.d.ts:
declare var IDBFactory: {
prototype: IDBFactory;
new(): IDBFactory;
};
But what I wanted to import was the interface type directly above it. How would I say in my declaration file that I want to reference the interface in lib.dom.ts, not the var that uses it? I can see that jsdom was able to make a class in their .d.ts that references DOM types with both an interface and var, but they also don't use "declare module".
Problem
The problem with this code:
declare let fakeIndexedDB: IDBFactory;
declare module 'fake-indexeddb' {
export var fakeIndexedDB;
}
is that the type of the exported fakeIndexedDB is any. It's a different variable than the one declared above it. Basically, fake-indexeddb defined like that is a module that exports a single variable called fakeIndexedDB of unspecified type.
Solution
What you should do instead is this:
declare module 'fake-indexeddb' {
const fakeIndexedDB: IDBFactory;
export = fakeIndexedDB;
}
Whether to use export =, export or export default depends on how the actual JavaScript library is built. The above syntax is recommended if fake-indexeddb exports a single member and it's meant to work well when imported using the require function. See if it works, and if not, consult the source code.

Is there a point to doing 'import type' rather than 'import' with Flow?

Flow allows you to use the following syntax to import types:
// SomeClass.js
export default class SomeClass {}
// SomeFile.js
import type SomeClass from './SomeClass';
What's the benefit of using import type instead of import? Does it tell Flow more information and let it perform better static analysis?
For the specific case of classes, it is either example will work. The key thing is that it breaks down like this:
import type ... from imports a Flow type
import ... from imports a standard JS value, and the type of that value.
A JS class produces a value, but Flowtype also interprets a class declaration as a type declaration, so it is both.
So where is import type important?
If the thing you're importing doesn't have a value, using a value import will in some cases be interpreted as an error, because most JS tooling doesn't know that Flow exists.
export type Foo = { prop: number }; for instance can only be imported with import type { Foo } from ..., since there is no value named Foo
If the thing you're importing has a JS value, but all you want is the type
Importing only the type can make code more readable, because it is clear from the imports that only the type is used, so nothing in the file could for instance, create a new instance of that class.
Sometimes importing only the type will allow you to avoid dependency cycles in your files. Depending on how code is written, it can sometimes matter what order things are imported in. Since import type ... only influences typechecking, and not runtime behavior, you can import a type without actually requiring the imported file to execute, avoiding potential cycles.
As stated at this link
With import type, you want to import the type of the class and not really the class itself.
An example given at the same link as below
// #flow
// Post-transformation lint error: Unused variable 'URI'
import URI from "URI";
// But if you delete the require you get a Flow error:
// identifier URI - Unknown global name
module.exports = function(x: URI): URI {
return x;
}
Due to we imported URI into this component, linter will check if we have used the class in this component. However, we only using it as a flow type checking and hence linter will throwing an error saying we imported unused variable.

Typescript duplicated type declarations

I'm experiencing issues with duplicated Typescript type declarations in the following case:
I've got the following dependency tree for my application A:
A->#angular/http:2.3.1
A->B->#angular/http:2.3.1
Both A and B are managed by npm. After running
npm install
the filesystem looks like this:
A/node_modules/
#angular/http
...
B/node_modules
#angular/http
The problem seems to be that now there are two type declarations of #angular/http types like Response, or Headers. And somehow the Typescript transpiler seems unable to handle that - resulting in the following error message:
TS2453:The type argument for type parameter 'T' cannot be inferred
from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'Response' is not a valid type argument
because it is not a supertype of candidate 'Response'. Types of
property 'headers' are incompatible. Type 'Headers' is not assignable
to type 'Headers'. Types have separate declarations of a private
property 'mayBeSetNormalizedName'.
Reading the message, I guess this is a hickup of Typescript not being able to match the duplicated type declarations.
Anybody experienced the same issue? How to handle that problem? How to handle such name collisions?
Meanwhile I found out that you can fix this error by explicitly importing the according types inside the using class of A. In my case (cp. error message above), I needed to:
import {Response, Headers} from '#angular/http';
I had the same problem. There are basically two ways to solve this.
Make a UMD module of project B. This might take a lot of time
use as any as TheRequiredObject see below.
Let assume you got this class in project b:
export class B{
getSome(): Observable {
return this.http.get('some_url');
}
}
and this is what you want in project a:
export class A{
getSomeFromB: Observable{
return B.getSome() as any as Observable;
}
}

Categories