Importar clase en archivo de definición (* d.ts)

96

Quiero extender la tipificación de Express Session para permitir el uso de mis datos personalizados en el almacenamiento de sesiones. Tengo un objeto req.session.userque es una instancia de mi clase User:

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

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

Así que creé mi own.d.tsarchivo para fusionar la definición con las tipificaciones de sesión exprés existentes:

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

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

Pero no funciona en absoluto: VS Code y tsc no lo ven. Así que creé una definición de prueba con un tipo simple:

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

Y el campo de prueba funciona bien, por lo que la importación causa el problema.

También intenté agregar en su /// <reference path='models/user.ts'/>lugar la importación, pero el tsc no vio la clase de usuario: ¿cómo puedo usar mi propia clase en el archivo * d.ts?

EDITAR: configuré tsc para generar archivos de definición en la compilación y ahora tengo mi user.d.ts:

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

Y el propio archivo de mecanografía para ampliar Express Sesion:

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

Pero aún no funciona cuando se importa la declaración en la parte superior. ¿Algunas ideas?

Michał Lytek
fuente

Respuestas:

271

Después de dos años de desarrollo de TypeScript, finalmente logré resolver este problema.

Básicamente, TypeScript tiene dos tipos de declaración de tipos de módulo: "local" (módulos normales) y ambiental (global). El segundo tipo permite escribir declaraciones de módulos globales que se fusionan con declaraciones de módulos existentes. ¿Cuáles son las diferencias entre estos archivos?

d.tsLos archivos se tratan como declaraciones de módulo ambiental solo si no tienen ninguna importación. Si proporciona una línea de importación, ahora se trata como un archivo de módulo normal, no como el global, por lo que aumentar las definiciones de módulos no funciona.

Por eso todas las soluciones que discutimos aquí no funcionan. Pero, afortunadamente, desde TS 2.9 podemos importar tipos en la declaración de módulos globales usando la import()sintaxis:

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

Entonces la línea import("./user").User;hace la magia y ahora todo funciona :)

Michał Lytek
fuente
4
Esta es la forma correcta de hacerlo, al menos con las versiones recientes de mecanografiado
Jefferson Tavares
1
Este enfoque es la solución ideal al declarar interfaces que extienden módulos globales como el processobjeto de Node .
Teffen Ellis
1
Gracias, esta fue la única respuesta clara para solucionar mis problemas con la extensión de Express Middleware.
Katsuke
Gracias, ayudó mucho. No tenía otra forma de hacer esto en un proyecto existente. Prefiera esto a extender la clase Request. Dziękuję bardzo.
Christophe Geers
1
Gracias @ Michał Lytek Me pregunto si existe alguna referencia de documentación oficial para este enfoque.
Gena
3

ACTUALIZAR

Desde mecanografiado 2.9, parece que puede importar tipos en módulos globales. Consulte la respuesta aceptada para obtener más información.

RESPUESTA ORIGINAL

Creo que el problema al que te enfrentas es más sobre el aumento de las declaraciones de módulos que la escritura de clases.

La exportación está bien, como notará si intenta compilar esto:

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

Parece que está intentando aumentar la declaración del módulo de Express, y está muy cerca. Esto debería funcionar:

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

Sin embargo, la exactitud de este código depende, por supuesto, de la implementación original del archivo de declaración expresa.

Pelle Jacobs
fuente
Si muevo la declaración de importación dentro me sale error: Import declarations in a namespace cannot reference a module.. Si copiar y pegar el código que tengo: Import or export declaration in an ambient module declaration cannot reference module through relative module name.. Y si trato de usar una ruta no relativa, no puedo ubicar mi archivo, así que moví la carpeta de declaraciones a node_modules y "declarations/models/user"agregué la ruta, pero aún así todo el d.ts no funciona; no puedo ver la extensión propia de la sesión exprés en intelisense o tsc.
Michał Lytek
No estoy familiarizado con estos errores, lo siento. ¿Quizás haya algo diferente en tu configuración? ¿Esto lo compila para usted? gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad .
Pelle Jacobs
De esta manera funciona, pero aún no funciona en una aplicación real. Tengo un objeto de solicitud expresa con el objeto de sesión y tiene otro tipo declarado: en el espacio de nombres Express, no en el módulo 'express': github.com/DefinitelyTyped/DefinitelyTyped/blob/master/…
Michał Lytek
5
A mí tampoco me funciona. Una vez que agrego las declaraciones de importación a mi archivo tsd.d.ts, todo el archivo deja de funcionar. (Recibo errores en el resto de mi aplicación para las cosas definidas en ese archivo.)
Vern Jensen
5
Yo tuve el mismo problema. Funciona si usa la importación en un módulo declarado dentro de su .d.ts: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder
2

Gracias a la respuesta de Michał Lytek . Aquí hay otro método que utilicé en mi proyecto.

Podemos importarlo Usery reutilizarlo varias veces sin escribir en import("./user").Usertodas partes, e incluso extenderlo o reexportarlo .

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 }
}

Que te diviertas :)

h00w
fuente
-1

¿No es posible simplemente seguir la lógica con express-session:

own.d.ts:

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

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

En el principal 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;
});

Al menos esto parece compilarse sin problemas. Para obtener el código completo, consulte https://github.com/masa67/so39040108

masa
fuente
1
No debe importar archivos de declaración, porque tscno los compilará. Están destinados a estar en la compilación pero no en la salida
Balint Csak