Importar classe no arquivo de definição (* d.ts)

104

Quero estender as tipificações Express Session para permitir o uso de meus dados personalizados no armazenamento de sessão. Eu tenho um objeto req.session.userque é uma instância da minha classe User:

export class User {
    public login: string;
    public hashedPassword: string;

    constructor(login?: string, password?: string) {
        this.login = login || "" ;
        this.hashedPassword = password ? UserHelper.hashPassword(password) : "";
    }
}

Então, criei meu own.d.tsarquivo para mesclar a definição com as tipificações de sessão expressa existentes:

import { User } from "./models/user";

declare module Express {
    export interface Session {
        user: User;
    }
}

Mas não está funcionando - o VS Code e o tsc não percebem. Então, criei uma definição de teste com tipo simples:

declare module Express {
    export interface Session {
        test: string;
    }
}

E o campo de teste está funcionando bem, então a importação causa problema.

Eu também tentei adicionar em /// <reference path='models/user.ts'/>vez de importar, mas o tsc não viu a classe de usuário - como posso usar minha própria classe no arquivo * d.ts?

EDITAR: Eu defino tsc para gerar arquivos de definição na compilação e agora tenho meu user.d.ts:

export declare class User {
    login: string;
    hashedPassword: string;
    constructor();
    constructor(login: string, password: string);
}

E o próprio arquivo de digitação para estender Express Sesion:

import { User } from "./models/user";
declare module Express {
    export interface Session {
        user: User;
        uuid: string;
    }
}

Mas ainda não funciona quando a declaração de importação no topo. Alguma ideia?

Michał Lytek
fonte

Respostas:

295

Após dois anos de desenvolvimento em TypeScript, finalmente consegui resolver esse problema.

Basicamente, o TypeScript tem dois tipos de declaração de tipos de módulo: "local" (módulos normais) e ambiente (global). O segundo tipo permite escrever declarações de módulos globais que são mescladas com as declarações de módulos existentes. Quais são as diferenças entre esses arquivos?

d.tsos arquivos são tratados como declarações de módulo de ambiente apenas se não tiverem importações. Se você fornecer uma linha de importação, ela agora é tratada como um arquivo de módulo normal, não global, portanto, aumentar as definições dos módulos não funciona.

É por isso que todas as soluções que discutimos aqui não funcionam. Mas, felizmente, desde o TS 2.9, podemos importar tipos para a declaração de módulos globais usando a import()sintaxe:

declare namespace Express {
  interface Request {
    user: import("./user").User;
  }
}

Então a linha import("./user").User;faz a mágica e agora tudo funciona :)

Michał Lytek
fonte
4
Essa é a maneira certa de fazer isso, pelo menos com as versões recentes do texto datilografado
Jefferson Tavares
1
Essa abordagem é a solução ideal ao declarar interfaces que estendem módulos globais, como o processobjeto do Node .
Teffen Ellis
2
Obrigado, esta foi a única resposta clara para corrigir meus problemas com a extensão Express Middleware!
Katsuke de
2
Obrigado @ Michał Lytek. Gostaria de saber se há alguma referência de documentação oficial para esta abordagem?
Gena
5

ATUALIZAR

Desde o typescript 2.9, você parece ser capaz de importar tipos para módulos globais. Veja a resposta aceita para mais informações.

RESPOSTA ORIGINAL

Acho que o problema que você está enfrentando é mais sobre como aumentar as declarações de módulos do que sobre a digitação de classes.

A exportação é boa, como você notará se tentar compilar isto:

// app.ts  
import { User } from '../models/user'
let theUser = new User('theLogin', 'thePassword')

Parece que você está tentando aumentar a declaração do módulo de Expresse está muito perto. Isso deve funcionar:

// index.d.ts
import { User } from "./models/user";
declare module 'express' {
  interface Session {
    user: User;
    uuid: string;
  }
}

No entanto, a exatidão desse código depende, é claro, da implementação original do arquivo de declaração expressa.

Pelle Jacobs
fonte
Se eu mover a instrução de importação dentro eu recebo erro: Import declarations in a namespace cannot reference a module.. Se eu copiar e colar o código que eu tenho: Import or export declaration in an ambient module declaration cannot reference module through relative module name.. E se eu tentar usar o caminho não relativo, não consigo localizar meu arquivo, então movi a pasta de declarações para node_modules e o caminho de adição, "declarations/models/user"mas ainda assim o d.ts inteiro não está funcionando - não consigo ver a própria extensão da sessão expressa no intelisense ou tsc.
Michał Lytek
Não estou familiarizado com esses erros, desculpe. Talvez haja algo diferente em sua configuração? Isso é para compilar para você? gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad .
Pelle Jacobs
Dessa forma, ele funciona, mas ainda não funciona no aplicativo real. Eu tenho lá um objeto de solicitação expressa com objeto de sessão e outro tipo declarado - no namespace Express, não no módulo 'express': github.com/DefinitelyTyped/DefinitelyTyped/blob/master/…
Michał Lytek
5
Também não funciona para mim. Depois de adicionar as instruções de importação ao meu arquivo tsd.d.ts, todo o arquivo para de funcionar. (Recebo erros no restante do meu aplicativo para coisas definidas naquele arquivo.)
Vern Jensen
5
Eu tive o mesmo problema. Funciona se você usar a importação em um módulo declarado no seu .d.ts: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder
4

Graças à resposta de Michał Lytek . Aqui está outro método que usei em meu projeto.

Podemos importá User- lo e reutilizá- lo várias vezes sem escrever em import("./user").Usertodos os lugares e até mesmo estendê- lo ou reexportá- lo.

declare namespace Express {
  import("./user");  // Don't delete this line.
  import { User } from "./user";

  export interface Request {
    user: User;
    target: User;
    friend: User;
  }

  export class SuperUser extends User {
    superPower: string;
  }

  export { User as ExpressUser }
}

Diverta-se :)

h00w
fonte
0

Não é possível apenas seguir a lógica com express-session:

own.d.ts:

import express = require('express');
import { User } from "../models/user";

declare global {
    namespace Express {
        interface Session {
            user: User;
            uuid: string;
        }
    }
}

Principalmente index.ts:

import express from 'express';
import session from 'express-session';
import own from './types/own';

const app = express();
app.get('/', (req, res) => {
    let username = req!.session!.user.login;
});

Pelo menos parece compilar sem problemas. Para o código completo, consulte https://github.com/masa67/so39040108

masa
fonte
1
Você não deve importar arquivos de declaração, porque tscnão os compilará. Eles devem estar na compilação, mas não na saída
Balint Csak