- Se plantea la necesidad de que TypeScript emita información de tipos en tiempo de ejecución y se recopila una lista de Type Mapping, Code Generation / External Tool y proyectos Adapter que buscan esquivar este problema
- El problema central es que, sin un sistema de tipos reflexivo, manejar la serialización y la validación requiere un boilerplate interminable o generación de código bespoke basada en archivos de esquema
- Como soluciones alternativas se proponen io-ts, zod y otros, pero resulta incómodo tener que volver a declarar los tipos al estilo de cada librería, además de que estas librerías no pueden soportar todas las capacidades de tipos de TypeScript
- Aunque se reconoce que la eliminación de tipos de TypeScript tiene la ventaja de permitir que proyectos JavaScript usen el JavaScript emitido sin necesitar conocimiento de TypeScript, se argumenta que la información de tipos podría emitirse en forma de tablas de consulta separadas del código
- Junto con la petición de que esto no se resuelva con decoradores, se proponen enfoques como una función de orden superior reconocida por el compilador como
typescript.generateRuntimeType<T>(), los Type Providers de F# y los Source Generators de C# para permitir el uso de interfaces y el soporte de tipos de librerías externas - Como discusión previa relacionada, se enlaza un issue de GitHub de hace 8 años y se pide que, si hay proyectos que hayan sufrido el mismo problema, se envíe un PR para agregarlos a la lista sin importar la cantidad
1 comentarios
Comentarios de Hacker News
Tuve que leer la explicación del problema unas 4 veces para entender qué estaba pidiendo, y este caso muestra exactamente por qué la escritura concisa es importante
La petición real parece ser seguridad de tipos en tiempo de ejecución, pero eso no parece muy probable porque TypeScript ha trazado la línea de no convertirse en un runtime alternativo o extendido de JavaScript. TypeScript compila a JS y sale de escena; lo que ocurra después en V8, etc., queda fuera de su alcance.
Decir que “TypeScript debería emitir información de tipos en tiempo de ejecución” se parece más a pedir un producto nuevo bastante distinto del TypeScript actual
Por ejemplo, sería bueno poder crear una función genérica
validateque reciba cualquier interfaz y objeto y los valide. Con reflection en tiempo de compilación podría generarse código de validación JS por tipo, o quizá pasarTcomo argumento en runtime para compararlo, pero dentro de la filosofía actual de TypeScript eso parece difícilTypeScript sabe mucha información de tipos durante la compilación, pero cuando termina de compilar la descarta. Podría exportar esa información a un archivo o dejarla como metadata en
Reflect, y aun así la tira; eso parece una limitación especialmente lamentable si se considera la naturaleza de JavaScript, donde “todo es objeto”.Aunque no se obtenga una seguridad de tipos de TS totalmente exacta, igual se podrían agregar algunas comprobaciones con getter/setter o con
Proxyde ES2015, y si la información de tipos quedara disponible en runtime se abrirían varias posibilidades interesantes como metadata ejecutableTS
enumya se emite como un objeto JS y puede consultarse en runtime, pero los tipos unión de literales de cadena no. Además, TypeScript ya soporta funciones type guard, así que tampoco parece tan difícil generar este tipo de funciones automáticamente usando la información que ya existe dentro del sistema de tiposSe puede pensar con una analogía como los archivos PDB. Es información que TypeScript ya tiene y luego desecha, así que no haría falta un producto completamente nuevo
https://github.com/microsoft/TypeScript/issues/3628
Desde la perspectiva del PM de TypeScript, entiendo el deseo. La validación de datos suele requerir comprobaciones de tipos en tiempo de ejecución, y hay muchas librerías para cubrir ese vacío
Pero el simple hecho de que existan tantas librerías con decisiones de diseño distintas ya indica que este problema no es uno resuelto con una respuesta clara. En el diseño inicial de TypeScript ya se sabía esto, y me parece que ese principio se ha mantenido bien.
En cambio, TypeScript sí se ha vuelto lo bastante potente como para expresar con tipos lo que realmente hacen las librerías de comprobación de tipos en runtime, y el usuario puede construir lógica de validación en runtime a partir de los tipos mediante APIs. Ese nivel de flexibilidad se siente razonable
Por ejemplo, imagino un lenguaje donde uno pueda definir como tipo un número dentro de cierto rango o una cadena que cumpla el patrón de un código postal, y el compilador use funciones de validación escritas como funciones normales para verificar su validez. Me da curiosidad si que no exista algo así es una decisión de diseño para evitar complejidad innecesaria o si se debe a limitaciones técnicas como el rendimiento
Incluso ahora, quien lo necesite podría crear un preprocesador que genere objetos de runtime a partir de la información de tipos antes de pasárselos a
tsc, pero ese esfuerzo está disperso y cada quien lo resuelve por su lado. Si hubiera un preprocesador oficial con arquitectura de plugins y un ecosistema alrededor, podría encontrarse una buena soluciónSi solo se enciende la chispa, la comunidad ayudaría con el mantenimiento, y podría cubrir varias necesidades de generación de código, desde generación de clientes hasta aserciones de tipos en runtime
Classdespués de compilarHay una razón para no hacer esto. TypeScript terminaría convirtiéndose en algo así como un runtime sobre JavaScript, y en un nuevo lenguaje que compila a JS
Ahora mismo, TypeScript es más cercano a JavaScript con anotaciones de tipo. Ya hay muchos lenguajes que compilan a JS, así que se puede usar uno de esos. Quienes quieren TypeScript con tipos en runtime parecen querer escribir código más al estilo Java/OOP que JavaScript, pero JavaScript es un lenguaje de tipado dinámico, y eso también es una ventaja
generateTypeInfo!()se expandiera a un objeto de JS que codifique el tipoFoo, seguiría compilando a JavaScript legibleEso sí, ese macro rompe la propiedad de “TS = JS con anotaciones de tipo y compilar solo requiere quitar las anotaciones”. Porque habría que calcular de verdad el tipo estructural de
Foo.Además, los tipos de TypeScript son estructurales, así que si inspeccionas la estructura del objeto en runtime puedes saber el tipo hasta cierto punto, pero si exiges información nominal borrada o nombres de tipos, como si pertenece a una unión de cadenas, enseguida se vuelve un problema difícil por la completitud de Turing del sistema de tipos de TypeScript y las conversiones estructurales implícitas
Hablando en serio, incluso sin runtime, si expones los tipos como datos puedes obtener reflexión. Viendo cuántos desarrolladores se han esforzado por imitar esto, no hacerlo hasta parece una tontería
Los tipos en runtime resuelven el problema de tener que mantener duplicados, por separado, un sistema de tipos para compilar y otro para validar datos, y no parece tener relación directa con OOP.
io-ts, que se suele preferir como biblioteca para rodear este problema, también se apoya fuertemente en la programación funcionalNaNEn los flujos de trabajo que he visto, TypeScript ya es un lenguaje que compila a JS, así que mejor aprovechar esa ventaja al máximo
Es más cercano a anotaciones de tipo + Babel, y añadirle una función más muy útil me parece buena idea. Es raro no poder convertir de forma segura una cadena parseada con
JSON.parsea una estructura tipada, y en la mayoría de otros lenguajes eso sí se puedeAntes que lo de abajo, este es un tema válido que da perfectamente para debatir, y no creo que haya objetivamente una sola respuesta correcta
TypeScript es una capa opcional sobre JavaScript, salvo la excepción de que
Enumexporta objetos, y el código TS se vuelve JS con solo borrar los tipos. Mientras no se desvíe demasiado de ese principio, lo que en realidad se pide se parece más a una biblioteca que genere serializadores/validadores a partir de tipos. Bibliotecas así ya hay muchas, y al final esto parece más una petición para convertir una de ellas en la opción estándar oficial.Personalmente, no quisiera que el lenguaje central de TypeScript incorporara reflexión en runtime. Valoro que en runtime solo exista JavaScript, y que incluso sin sourcemaps el JS generado siga siendo legible y fácil de depurar
Enum, hay otras excepciones que emiten código en runtime, y la mayoría también se consideran errores. Por ejemplo,moduleynamespaceeran estructuras de runtimeAun así, en los últimos años se usaron principalmente dentro del propio TypeScript, y TypeScript también se ha ido alejando de eso. También hay una excepción bastante popular: las propiedades de parámetro, una sintaxis para definir tipos de miembros de clase desde los parámetros del constructor, que parece haber recibido menos críticas porque reduce bastante el boilerplate repetido
En vez de suplicarles a los dioses de TypeScript, quizá sería mejor negociar con el lado de JavaScript para meter verificación de tipos en JS
Si necesitas tipos en runtime, usa type guards, y si lo necesitas seguido, usa
io-tsozodpara escribir los tipos como validadores/códecs/esquemas. Hasta que exista una forma consensuada en JavaScript, no creo que la especificación de TS, el verificador de tipos y la comunidad deban cargar con la validación en runtimezody derivar los tipos desde ahí. Me gusta que la validación evolucione junto con los cambios de tiposHay quienes sienten que una función así debería entrar al lenguaje, pero como cada biblioteca tiene muchas decisiones de diseño discutibles, puede que sea mejor poder elegir una implementación según las necesidades del proyecto.
Eso sí, patrones como la validación en runtime de
zody la derivación de tipos basada en esquemas no siempre encajan bien con el pattern matching dets-pattern. Las definiciones de tipos de estas bibliotecas son como un laberinto, y a veces código que parece que debería funcionar no se comporta como uno espera. Sería buenísimo que la seguridad en runtime y el pattern matching con verificación de exhaustividad se combinaran de forma fluidaSiento que
Promise, los decoradores y el nuevo operador pipe tomaron una dirección de implementación equivocadaRecuerdo que uno de los desarrolladores de TypeScript dijo que, si pudiera empezar de nuevo, no habría metido
enum. Porque es la única función que emite código en runtimeTypeScript no cambia el comportamiento en runtime, y tampoco tiene algo especial de TypeScript como
{#if}Aun así, TypeScript tiene varias funciones que van más allá de “JavaScript + anotaciones de tipo”. Están
namespace, la sintaxis para definir propiedades de clase en los argumentos del constructor, los decoradores experimentales antiguos, y la sintaxis del parámetrothis, que desaparece al compilar pero parece más que solo poner anotaciones de tipo sobre funciones de JSUn mejor título sería algo más cercano a “TypeScript, por favor ofrece reflexión/tipos en runtime”
La mejor solución actual probablemente sea
emitDecoratorMetadata. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadataParece que el autor malinterpretó los objetivos de diseño de TypeScript. La meta no es producir un JS limpio y sin complejidad, sino mantener la semántica en tiempo de ejecución de TypeScript idéntica a la de JavaScript
Salvo la lamentable excepción de
enum, TypeScript se convierte en JavaScript simplemente eliminando las anotaciones de tipo. Todo el ecosistema alrededor de TypeScript depende de una eliminación completa de tipos, y si se aceptara esta propuesta, el soporte de TS en ESBuild, Deno y Bun podría volverse casi imposible. Eso sería porque cada uno tendría que reimplementar todotscen su propio lenguaje.En cambio, las bibliotecas complejas de las que se queja el OP están implementadas en espacio de usuario, así que sí son compatibles con estas herramientas
keyofen una lista de claves de la clase, o hacer que las clases de TS inicialicen las claves conundefinedigual que las clases de ES6Ahora mismo, las clases de TypeScript eliminan todas las claves que no se definan explícitamente, así que si llamas
Object.keys()sobre una instancia nueva, no sale nada. Con solo tener una opción entsconfig.jsonpara transformar las clases de TS para que se comporten como clases de ES6, o permitir clases de ES6 directamente dentro de TS, la generación de código sería muchísimo más sencilla.Además, una sintaxis estática como
tstypeof Foo::barque compilara a"string"sería excelente. Incluso con RTTI básico, podría desaparecer de inmediato bastante código TypeScript sucio y repetitivoMe da gusto ver este post. Empecé con TypeScript en 2018 y, unos dos años después, empecé a creer que esto no era una solución completa en esencia
Vengo de Haskell/C#/F#, y TS, incluso con un sistema de tipos potente, no ofrece muchas de las ventajas que dan esos lenguajes fuera de ciertas partes del desarrollo. Al lidiar con el mundo real, en la práctica es mucho más limitado que C#. Si no sabes que al compilar termina convertido en JS sin verificaciones posteriores, tienes que estar siempre atento a las abstracciones con fugas para evitar bugs que no deberían ser posibles en una herramienta que dice ofrecer tipado estático
Si tienes que desarrollar en .NET, usas C#; si tienes que desarrollar para la web, usas TypeScript. C# tiene tipado nominal y genéricos reificados bastante decentes, mientras que TypeScript tiene tipado estructural y, a cambio de renunciar a la solidez, un sistema de tipos poderoso para expresar relaciones de tipos complejas.
Dudo que un lenguaje que combine ambos resulte bueno, y aunque ambos funcionan bien en sus respectivas direcciones, esas direcciones no encajan bien entre sí
Para validación de datos en runtime, he quedado completamente satisfecho usando https://zod.dev
Me pareció bastante elegante poder expresar la intención ahí mismo con una API fluida, sin tener que definir por separado tipos nominales adicionales
Un ejemplo simple es que se necesitan para distinguir en el dominio entre el número
2y un monto monetario de2