Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintainers #23

Open
vedantroy opened this issue Apr 27, 2021 · 9 comments
Open

Maintainers #23

vedantroy opened this issue Apr 27, 2021 · 9 comments

Comments

@vedantroy
Copy link
Owner

vedantroy commented Apr 27, 2021

See this

This library still needs a maintainer. Feel free to reach out if you want to take a shot at maintaining it.

Note, seems like proteriax is now helping out. But as always, more maintainers are appreciated. There is a pretty big backlog of features that could be implemented.

Update:

  • I have invited proteriax to have push access. If anyone else has wants commit access, let me know!
@alex-kinokon
Copy link
Collaborator

I maintain a Babel metadata plugin that emits runtime metadata decorators that deal with generics, null ability and circular references. This package is of similar scope so I think I can help you.

@vedantroy
Copy link
Owner Author

Great! Can you send me an email at "vroy101 (at) gmail.com" with some basic contact info?

@vedantroy
Copy link
Owner Author

@proteriax. Going to post an initial list of things I want to explore here:

  1. Explore replacing the home-grown type resolution system with the Typescript compiler. Right now, I have written some logic to resolve types (solve generics, simplify unions, simplify intersections). This is done here.
    • resolve solves type aliases
    • instantiate solves generics & collects statistics on type usage, that are used in code gen phase. (If a type is used more than once, we extract it into a common function).
    • flatten solves intersections/unions
    • clean simplifies redundant unions (for example: string | "a" gets simplified to string.

Anyways, I want to see if we can get rid of all of this logic by replacing it with the Typescript compiler. Basically, I want to see if we can invoke the Typescript compiler while the macro is running to get better types.

Potential difficulties:

  • If we run the Typescript compiler on one file at a time then we run into issues like unresolved imports. (Is the Typescript compiler flexible in this regard?)
  • This might be super slow if we run it on one file at a time, on the other hand, running the compiler on all the files at once is potentially annoying for the user b/c then they have to deal with setting up a tsconfig.json.
  1. Implement multi-file types. If we have have the following:
import type { B } from "./baz";

type A = {
   foo: B
}

we could transform it to the following:

import type { B } from "./baz"
import { __ts_macro_B_validator } from "./baz"

// validator code for `A`

and have typecheck.macro generate the validator for A while assuming that the function __ts_macro_B_validator exists.

Then in the file "./baz" we can do:

import { register } from "typecheck.macro"

type B = // some type

register('B')

which will transform into:

const __ts_macro_B_validator = // code
export { __ts_macro_B_validator }
  1. Support mapped types. (For example: Record)

  2. Support transformers. (To enable serialization/deserialization).

@vedantroy
Copy link
Owner Author

Note: I think the right thing to do is to eventually create a separate CLI tool that uses the Typescript compiler API and generates all the validation functions into a single file, which can be imported.

@vedantroy vedantroy pinned this issue May 9, 2021
@kevinramharak
Copy link

I have done some prototyping at ts-transform-runtime-check where I explored a similar idea but with the typescript compiler API.

I'm wondering what the goal of this package would be. I really like the idea of generating runtime type checks to validate input and output data. Having it be a plugin and a seperate command line tool sounds like the best of both worlds.

You seem to be focussing on performance, is that focussed on runtime performance only? Because once you introduce the typescript compiler and type checker it will take a hit when running the tool.

What is the goal of registerType? Is this a implementation restriction? I tried scanning the source code but I can't really tell whats happening. The TS compiler API makes it quite easy to follow a type over multiple files and look at its declaration.

Maybe I can offer some insight in how you would want to use the typescript compiler instead of running your own type checker.

If we run the Typescript compiler on one file at a time then we run into issues like unresolved imports. (Is the Typescript compiler flexible in this regard?)

The compiler uses diagnostics to report errors. As far as I know you can still use most of the pipeline with unresolved imports. Most of the compiler is focussed on being used by tools that work with limited information (for example your IDE).

This might be super slow if we run it on one file at a time, on the other hand, running the compiler on all the files at once is potentially annoying for the user b/c then they have to deal with setting up a tsconfig.json.

This would depend on how you invoke the compiler. You could build a tsconfig dynamically, fetch the one the user provides or just use a provided factory from ts to create a config.

@vedantroy
Copy link
Owner Author

vedantroy commented Jun 1, 2021

registerType originally existed b/c I wanted to support multifile types. I thought I would "register" types into a global namespace that could be shared across files.

It takes a string as a parameter b/c I needed a way to register generic types, for example registerType<Cat>() where type Cat<T> = Record<string, T> wouldn't work (missing type parameter), so I made it have the API registerType('Cat').

But... it turns out that Babel processes files one at a time, so this is not possible.

registerType is probably not needed now since createValidator could probably just do whatever registerType is doing.

One "benefit" of registerType is that it allows us to register types in different scopes than createValidator. For example,

function a() {
    type Z = "foo";
    registerType('Z')
}

function b() {
    const x = createValidator<Z>();
}

Not actually sure if this is a good thing. But yeah, registerType "registers" types in a file into a single per-file-global namespace of types.

I do agree that using the TS compiler would probably invoke a severe performance hit.

You seem to be focussing on performance, is that focussed on runtime performance only? Because once you introduce the typescript compiler and type checker it will take a hit when running the tool.

I'm mostly focused on runtime performance, but I do care about compile time performance as well.

I think if we were to get the Typescript compiler involved, the right way to do so would be to create a separate CLI tool that runs over the user's source code and generates a single file with all the type checkers.

I think the correct API for such a tool would be:

//create-type-guard
type T = { ... }

and the tool would just generate a type checker function that could be imported.

Having it be a plugin and a seperate command line tool sounds like the best of both worlds.

I'm not sure how that would work since the API for both would probably be different?

@alex-kinokon
Copy link
Collaborator

You can take a look at Quramy/ts-graphql-plugin. They scan for all gql tagged template expressions and emit type files in the adjacent __generated__ folder.

@kevinramharak
Copy link

kevinramharak commented Jun 2, 2021

Hm, I don't know about the scope issues. I would suggest sticking to the javascript scope rules. That would probably be more intutive. You can always declare global types with typescript if you want to.

I'm not sure how that would work since the API for both would probably be different?

I tried to express that having the program be exposed trough a transformer and a command line tool would be optimal, they would use the same internal code, just a different manner of invoking the functionality. Tough it is probably easier to focus on one of the two at first.

Using a comment to flag types for generation seems like a good idea. Not sure how the tagged template literals would be an alternative? That doesnt seem that different from using a createValidator<T>() API.

I would suggest using doc type style comments tough. I think that would look a bit better:

/**
 * Something like:
 * @typecheck createValidator
 */
export type T = { ... };

I don't see why you couldnt use both tough. Use the comment style as base and allow for easy use with a command line tool, then improve the experience by supporting an facade api like createValidator<T>() with transformers.

@vedantroy
Copy link
Owner Author

Hm, I don't know about the scope issues. I would suggest sticking to the javascript scope rules. That would probably be more intutive. You can always declare global types with typescript if you want to.

I agree. But turns out implementing scoping is hard, so registerType just ignores scoping.

I tried to express that having the program be exposed trough a transformer and a command line tool would be optimal, they would use the same internal code, just a different manner of invoking the functionality. Tough it is probably easier to focus on one of the two at first.

A transformer feels similar to the current babel-macros style approach. I'm pretty convinced that a CLI-only approach is the right thing to do -- code generation by annotating types is better since there's less magic / more maintainability. You don't need to use a custom typescript compiler harness and the generated code is inspectable. Certainly, a CLI tool would be the right first approach.

Alas -- I think a CLI tool to generate validators would probably not share too much in common with the current codebase. Maybe the type-ir utilities could be reused and maybe the codegen file could be reused as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants