Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force Override Declarations Types #36146

Open
2 tasks
saandre15 opened this issue Jan 12, 2020 · 46 comments
Open
2 tasks

Force Override Declarations Types #36146

saandre15 opened this issue Jan 12, 2020 · 46 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@saandre15
Copy link

saandre15 commented Jan 12, 2020

Search Terms

declaration
module
merging
override
interface

Suggestion

Add a way to force override declarations for modules in the workspace. Typescript allows you to create a global.d.ts to add global declarations. How about an override.d.ts or an *.override.d.ts file to override existing modules declarations in the user workspace.

Use Cases

Lets say you install a node module package and type. You see that the type parameter isn't type safe so you use declaration merging to create a more type safe declaration. However, when you use the module type as a variables type or choose to extending it, it automatically uses the types folder declaration first since the type parameter used fits the node_modules declaration type parameter, however that type would not fit the type parameter the user created. This is because declaration merging selects the most appropriate types in order. If the type was to not match the first declaration type it would move on until reaching the appropriate type, which wouldn't work in this use case.

Examples

// Node Module module declaration file
declare module "react-router" {
  interface RouteComponentProps<Params extends { [K in keyof Params]?: string | undefined }> {
    params: Params;
  }
}

// User Defined Type declaration file
export type NoRequired<T extends {}> = {
  [C in keyof T]: T[C] extends Required<T>[C] ? never : T[C];
};

declare module "react-router" {
  interface RouteComponentProps<Params extends NoRequired<Params> }> {
    params: Params;
  }
}

// Using the Interface
import { RouteComponentProps } from "react-router";

// I don't want to extend RouteComponentProps<{ page: string }>, but I can.
export interface CRUDComponentProps
  extends RouteComponentProps<{ page: string }> {
  serverName: string;
  clientName: string;
}

As you can see the type is accepted as the node modules declaration type instead of a user defined declaration type. There no other way of overriding it other than removing it manually from the the node_modules type file.

Checklist

My suggestion meets these guidelines:

  • A way of force overriding a type (preferably an override.d.ts or an *.override.d.ts file)
  • A way to sort the order in which declaration merging happens
@DanielRosenwasser
Copy link
Member

I guess this would work so long as there's only one file like this. I do have some apprehension since users might use this more often in place of actually fixing things on DefinitelyTyped.

@saandre15
Copy link
Author

This could be used as a temporary measure because type definitions have to be reviewed before being updated on DefinitelyTyped. There also no need for one override file, since you can create a user based folder called override and place declaration files with the name of the module your trying to override. Because files can’t have the same name there will be no duplicates of modular overrides.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Feb 5, 2020
@lixpng
Copy link

lixpng commented Mar 20, 2020

I want this feature too. I use webpack in my project, it has declare the process variable, just like this:

declare global {
    interface __WebpackProcess {
        env: any
    }
    declare var process: __WebpackProcess
}

It declare process.env as any type, but I want change it to a more accurate type, I don't know how to do it.

@DanielRosenwasser
Copy link
Member

This is like the dual of #31894.

@jamescdavis
Copy link

jamescdavis commented Jun 2, 2020

I have a need for this too. For example, I'd like to tighten up the type for QUnit's assert.ok to

ok(state: boolean, message?: string): void;

in a project (but don't think the DT types should be that strict). There's no good way to do this (at least that I'm aware of) short of redefining all of the QUnit types locally and not including @types/qunit.

@samson-sham
Copy link

I have a similar need, where I imported a library which is partly typed, and when I do declaration merging/module augmenting for that library, I'm being locked in "declarations must have the same type" issue for days.

@svallory
Copy link

svallory commented Aug 7, 2020

It would be useful also for "special cases" where typings are used to discourage use of API params which are actually valid. E.g. Stipe's create source API method: stripe/stripe-node#974

@daniellwdb
Copy link

daniellwdb commented Aug 20, 2020

Plugins are a good example of a valid use case. fastify has route methods, for example:

get: RouteShorthandMethod<RawServer, RawRequest, RawReply>; but when you create a plugin that changes the behaviour of one of the route methods. you cannot reflect that runtime behaviour in the type system, in this case I'd like to do something like: get: TypedRouteShorthandMethod<RawServer, RawRequest, RawReply>;.

Currently the ugly hack is to automate the process of commenting existing declarations using patch-package (links to real life example of a plugin)

@dbartholomae
Copy link

I'm running into similar problems: I have a library that uses JSX but not React. Since React types are global, when both my library and React are installed, the types clash. I would love to be able to import my types just in the files that use my library without disturbing the typing in other files.

@bengotow
Copy link

bengotow commented Sep 3, 2020

+1! Just wanted to add another use case for this I've encountered:

We use the TypeORM library in a large web application and a few of it's "findBy"-style methods are overly permissive. It's easy to pass arguments which actually have no effect on the generated SQL but seem like they would. We'd like to disallow certain usages as a team by narrowing the type declarations but the package provides it's own types so our only option is to fork it. We'd love to have an overrides file where we could forcibly declare a narrower input type for just a few methods and call it a day!

@svallory
Copy link

@bengotow you could prevent that by writing a custom tslint rule

@Thyiad
Copy link

Thyiad commented Mar 30, 2021

How it's going

@kaangokdemir
Copy link

Any progress?

@GiancarlosIO
Copy link

GiancarlosIO commented Jun 1, 2021

it would be great to have because with that we can fix cases like this emotion-js/emotion#1257

@CrackThrough
Copy link

I love this idea. I really hope this gets added in next major update!

@ptim
Copy link

ptim commented Sep 9, 2021

My use case: adding project-specific documentation to the existing types for NextJS.

Currently the ugly hack is to automate the process of commenting existing declarations using patch-package (links to real life example of a plugin)
#issuecomment-677936683

Tx 👍

There is some interesting discussion of further workarounds/hacks over at SO: How to overwrite incorrect TypeScript type definition installed via @types/package.

@anuoua
Copy link

anuoua commented Nov 10, 2021

Any update?

1 similar comment
@DualWield
Copy link

Any update?

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus and removed Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Jan 20, 2022
@RyanCavanaugh
Copy link
Member

We had some casual conversations about this and are basically terrified of how it would work in practice as soon as more than one person starts using it. What happens when multiple files try to override? What happens when something on DefinitelyTyped claims to override something? What happens if two packages override a global in ways that they both depend on "sticking"? How does a user resolve conflicts? Can you override an entire namespace?

We'll look at it again, though.

@dbartholomae
Copy link

Thanks! It might make sense to look at two solutions for similar problems in the ecosystem:

Based on the discussions in this thread, the first idea might work for TypeScript by having a setting in tsconfig. From my understanding, only one tsconfig file is used at a time, so there would be no way for different settings to contradict each other.

@RyanCavanaugh
Copy link
Member

Yeah, I think a tsconfig-based mechanism is the thing that makes the most sense. The problem from there is figuring out the granularity. For "These defs are totally bad and I want to use my own", that's fine. But it'd be cumbersome to say "If you want to fix this one type in this file, you need to make an entire copy of it (and keep it in sync with everything else)".

Then there's the separate question if this is an override of a module name or a file - both have use cases IMHO, since presumably at least some of the time you need to override a global definition.

@antoniopresto
Copy link

antoniopresto commented Jul 21, 2022

This just worked 🎉

// override.d.ts (note the d.ts ⚠️)

declare global {
  module '@darch/schema' {
    export * from '@darch/schema';  // 👈🏼 export the same module

    // Function override
    export function createType(def:{
      union: [
        'string',
        'int',
        '[float]', //
      ],
    }): "WHATEVER2";
  }
}

Kapture 2022-07-20 at 22 06 26

@dac09
Copy link

dac09 commented Jul 21, 2022

@antoniopresto - interesting solution - hadn't thought of this before!

I get the error "Ambient modules cannot be nested in other modules or namespaces.ts(2435)" when I wrap my module in declare global.

Screenshot 2022-07-21 at 15 57 03

If I try to override the module just by declare module, I'm seeing this error in on the export line: "Exports and export assignments are not permitted in module augmentations.ts(2666)"

Screenshot 2022-07-21 at 15 55 47

I wonder if there's something setting your project that's letting you do this? Does @darch/schema exist as a module in your project?


I'm really hoping for something like an override keyword, as mentioned in a comment above.

@antoniopresto
Copy link

@dac09 @darch/schema is installed.
this is my tsconfig, nothing special, I think.

{
  "ts-node": {
    "transpileOnly": true, 
    "compilerOptions": {
      "module": "commonjs"
  },
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src",
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"],
      "@core/*": ["core/*"],
      "@services/*": ["services/*"],
      "@appTypes/*": ["types/*"],
      "@entity/*": ["entity/*"]
    },
    "lib": ["es5", "es6", "es7", "esnext", "dom", "es2020"],
    "declaration": true,
    "target": "es5",
    "module": "esnext",
    "removeComments": false,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "strict": true,
    "skipLibCheck": true,
    "strictPropertyInitialization": false,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "downlevelIteration": true,
    "noImplicitAny": false
  },
  "include": ["./src/**/*"]
}

@antoniopresto
Copy link

antoniopresto commented Jul 21, 2022

@dac09 are you also using a *.d.ts file?
I tried ts 4.5.4 and the latest 4.7.4 - both worked

@dac09
Copy link

dac09 commented Jul 26, 2022

@dac09 are you also using a *.d.ts file?
I tried ts 4.5.4 and the latest 4.7.4 - both worked

Hi Antonio, yes I am. The file is an ambient declaration that currently “merges” the interfaces for “CurrentUser” (and works) - so don’t see any reason why it’s different. Anyone else have any thoughts what the difference might be? I have a feeling it’s the “skipLibCheck” flag being enabled in your project - the implications of which I’m unsure of.

You can actually see this behaviour in any RedwoodJS project, where the ambient declarations sit in “.redwood/types”, after type generation.

@chenzhutian
Copy link

Any progress on this? Thanks!

@romelperez
Copy link

The solution from @antoniopresto works for me in TypeScript v4.9.

@antoniopresto
Copy link

Just a reminder, this not work for libraries, since we are using global declaration, which is ignored from bundled typings

@denbon05
Copy link

denbon05 commented Feb 14, 2023

Can you help me understand what's wrong here?

// "typescript": "^4.6.3"
// node-docker-api.d.ts
import { Container as OriginContainer } from 'node-docker-api/lib/container';

declare module 'node-docker-api/lib/container' {
  type Container = Omit<OriginContainer, 'data'> & {
    Warnings: string[];
    data: {
      Id: string;
      Names: string[];
      Image: string;
      ImageID: string;
      Command: string;
      Created: number;
      Ports: number[];
      Labels: object;
      State: 'exited' | 'running';
      Status: string;
      HostConfig: { NetworkMode: string };
      NetworkSettings: any;
      Mounts: any[];
    };
    id: string;
  };
}

// another module
import { Container } from 'node-docker-api/lib/container';
// ...
const container: Container = containers.find(...)

if (container.data.State !== 'exited') {
    await container.stop();
}

// tsconfig.json
{
....
"typeRoots": [
      "node_modules/@types",
      "./src/types"
    ],
    "types": [
      "jest"
    ],
}

image

I see that this is has some impact but doesn't override existed interface

I've been trying interface instead of type, rename module name, restart ts server and few another ways to initiate declare module and it doesn't seem to work

@antoniopresto
Copy link

// "typescript": "^4.6.3"
// node-docker-api.d.ts
import { Container as OriginContainer } from 'node-docker-api/lib/container';

declare module 'node-docker-api/lib/container' {
  type Container = Omit<OriginContainer, 'data'> & {
    Warnings: string[];
declare module and it doesn't seem to work
declare global {
  module '@darch/schema' {
    export * from '@darch/schema';  // 👈🏼 export the same module

@denbon05 you missed the re export line? ⤴️

@denbon05
Copy link

@antoniopresto yes I tried this and still have an error :/

@EladBezalel
Copy link

EladBezalel commented May 3, 2023

I managed to do the override like so:

declare module '@darch/schema' {
  function createTypeFn(def:{
    union: [
      'string',
      'int',
      '[float]',
    ],
  }): "WHATEVER2";
  
  export { createTypeFn as createType };
}

@Heniker
Copy link

Heniker commented May 11, 2023

Another similar issue - #25495

@samson-sham
Copy link

How do I override Components inside lib.dom, which is not a module to be import.
I tried to re-declare the same Component, but ts throws TS2403...

@LeandrodeLimaC
Copy link

LeandrodeLimaC commented Jun 8, 2023

Just a reminder, this not work for libraries, since we are using global declaration, which is ignored from bundled typings

Hello @antoniopresto, thanks for the info! Do you have any tips on how I can override a type so that the bundle can recognize it?

I have a components package and I want to override a type (DefaultTheme) from a dependency called styled-components. This way, our developers won't have to manually override it in their apps.

When I create a .d.ts file, it successfully overrides the type within the package. However, it is ignored when the package is bundled and used by another app.

@zWingz
Copy link

zWingz commented Sep 15, 2023

Any progress on this? Thanks!

@narwold
Copy link

narwold commented Jan 17, 2024

I have another instance of this where I'm using the styled-components guide for adding a global theme type per https://styled-components.com/docs/api#create-a-declarations-file, but their base type has an index ([key: string]: any) that I can't get rid of in my extension, so it's not strictly forcing consumers to use theme.someKnownKey.

This is what I've tried that doesn't seem to work:

type ThemeType = typeof myTheme

declare module 'styled-components' {
  export interface DefaultTheme extends ThemeType {
    [key: string]: never
  }
}

It will allow me to access keys off of myTheme but doesn't complain when I try to access an unknown key. It calls it any per styled-components' internal index.

Any ideas on how to solve this?

@cike8899
Copy link

cike8899 commented Apr 23, 2024

I managed to do the override like so:

declare module '@darch/schema' {
  function createTypeFn(def:{
    union: [
      'string',
      'int',
      '[float]',
    ],
  }): "WHATEVER2";
  
  export { createTypeFn as createType };
}

This works for me

@dmrickey
Copy link

I managed to do the override like so:

declare module '@darch/schema' {
  function createTypeFn(def:{
    union: [
      'string',
      'int',
      '[float]',
    ],
  }): "WHATEVER2";
  
  export { createTypeFn as createType };
}

How do you override a single function in the package without having to redeclare everything in module you're overwriting/over-declaring?

@rgembalik
Copy link

rgembalik commented Nov 29, 2024

I've a similar use case which is much more functional:
My api sdk has a class, that can be used for search:

export class MyAPI {
  ...
  search: SearchAPI<any> //some search api class among other built-in apis
  ...
}

The result depends on the user account, so the generic sdk needs generic response. However, I also have a CLI that creates my-api.d.ts file, with typings of that account to provide fully typed support of the results if you wish:

declare module "my-api-sdk" {
   export interface MyAPI {
     search: SearchAPI<TicketRecord | ConcertRecord | ArtistRecord>
   }
}

It would be awesome if my typegen CLI could provide such typings for whoever uses my API. I could see a bunch of use cases like this (HeadlessCMS, ORM, etc).

Is there any other way to do this currently?

@antoniopresto
Copy link

@rgembalik #36146 (comment)

@rgembalik
Copy link

@rgembalik #36146 (comment)

I saw this solution, but I also have similar issue as was mentioned above - I would have to redeclare the entire class to overwrite single property. To me, much cleaner solution would be explicit type override in declaration merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests