-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
π Search Terms
Keywords: Import Attributes, Const Literal Typing, Type Providers, DSL Integration, JSON Schema
Related Issues
- Feature Request: F# style Type Provider support?Β #3136
- import ConstJson from './config.json' as const;Β #32063
- Treat JSON types more literallyΒ #26552
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
It should be possible to import a fully typed value representation via import-attributes
Problem
When importing JSON with import-attributes, TypeScript produces a non-exact data structure.
// ----------------------------------------------------------------
// vector.json
// ----------------------------------------------------------------
{ "x": 1, "y": 2, "z": 3 }
// ----------------------------------------------------------------
// index.ts
// ----------------------------------------------------------------
import Vector from "./vector.json" with { type: "json" };
const { x, y, z } = Vector // Vector = { x: number, y: number, z: number }
//
// expect = { x: 1, y: 2, z: 3 }Type Provider (Potential)
If the import variable Vector (above) had a constant type associated with it { x: 1, y: 2, z: 3 }, this could enable F#-like Type Providers being implemented in TypeScript.
An F# type provider is a component that provides types, properties, and methods for use in your program. Type Providers generate what are known as Provided Types, which are generated by the F# compiler and are based on an external data source.
Where the "external data source" in this case would be the value imported via typed import-attribute, and where libraries can facilitate "Provided Types" via TS type level programming.
π Motivating Example
Import as Json (Type Provider)
It is possible for libraries to remap typed schema structures into TS types. The minimum requirement is that the type be a literal representation of the data.
Reference: Inline Provided Type
import { type Static } from 'typebox'
// Vector3 is Provided Type
type Vector3 = Static<{ // type Vector3 = {
type: 'object', // x: number;
required: ['x', 'y', 'z'], // y: number;
properties: { // z: number;
x: { type: 'number' }, // }
y: { type: 'number' },
z: { type: 'number' }
}
}>... however, import-attributes currently produce opaque value types which makes remapping impossible.
Reference: Remote Provided Type | Not Possible
// ----------------------------------------------------------------
// vector.json
// ----------------------------------------------------------------
{
"type": "object",
"required": ["x", "y", "z"],
"properties": {
"x": { "type": "number" },
"y": { "type": "number" },
"z": { "type": "number" }
}
}
// ----------------------------------------------------------------
// index.ts
// ----------------------------------------------------------------
import { type Static } from 'typebox'
import Vector3 from './vector.json' with { type: 'json' }
// Vector3 is Provided Type | Incorrect
type Vector3 = Static<typeof Vector3> // type Vector3 = {
// y?: unknown;
// y?: unknown;
// y?: unknown;
// }Import as Text (Type Provider)
If import-attributes with { type: 'text' } could return literal string types. This would enable type safe DSL parsers to be developed for formats such as GraphQL, gRPC IDL or even TypeScript itself.
Reference: Provided DSL Type
import { type TScript, type Static } from 'typebox'
// Definition Parsing
type Math = TScript<{}, `
export type Vector2 = {
x: number
y: number
}
export type Vector3 = {
x: number
y: number
z: number
}
`>
// Vector3 | Vector2 as Provided Types
type Vector2 = Static<Math['Vector2']> // type Vector2 = {
// x: number;
// y: number;
// }
type Vector3 = Static<Math['Vector3']> // type Vector3 = {
// x: number;
// y: number;
// z: number;
// }... and lead on to runtime type safe libraries derived entirely from remote DSL
Reference: Runtime Type Provider from DSL
import { Compile } from 'typebox/compile'
import Type from 'typebox'
// import MathDef from './math.ts' with { type: 'text' }
const Math = Type.Script(` // <-- Pass imported MathDef here
type Vector2 = {
x: number
y: number
}
type Vector3 = {
x: number
y: number
z: number
}
`)
const Vector3 = Compile(Math.Vector3)
const Vector2 = Compile(Math.Vector2)
const Result = Vector3.Parse(null) // const Result: {
// x: number;
// y: number;
// z: number;
// }π» Use Cases
What do you want to use this for?
- Use the TypeScript compiler to auto sync MCP protocol schematics loaded via URI (Deno). Example
- Use the TypeScript compiler to parse and infer WAT/WASM interface definitions Example
- Use the TypeScript compiler to parse foreign IDL formats like GraphQL Reference
- Use the TypeScript compiler to derive structures from WebGL/WebGPU (WGSL) compute programs Reference
What shortcomings exist with current approaches?
I think most would typically reach for code generation tooling to generate types and definitions for the cases mentioned, but this does introduce an inherent disconnect between TypeScript and the source format ... I do see some potential to put the programmable aspects of the TypeScript type system to work, where it should be possible to interactively derive types from remote formats at editor time ... but only if the content of those formats is observable to TypeScript as types (as Json or Text)
Mostly, it would be amazing if edits made to a remote format (such as GQL IDL) could surface immediately as type errors in dependent TypeScript code (without having to run offline code generation after the fact). Typed import-attributes could provide a path towards this, particularly if the TS LSP could track / watch for edits in non-TS files.
What workarounds are you using in the meantime?
For derived inference, the current workaround is to encode structures in .ts files (or strings), not in the native file format. This means editor tooling for the file format becomes unavailable.
// workable: would prefer to edit in '.graphql' and load via { type: 'text' } attribute
export const GraphQLTypes = `
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
`