Skip to content

Feature: Const-literal typing for import-attribute importsΒ #62919

@sinclairzx81

Description

@sinclairzx81

πŸ” Search Terms

Keywords: Import Attributes, Const Literal Typing, Type Providers, DSL Integration, JSON Schema

Related Issues

βœ… Viability Checklist

⭐ 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.

F# Type Providers (MSDN)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions