Maintainers
vedantroy opened this issue · 9 comments
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!
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.
Great! Can you send me an email at "vroy101 (at) gmail.com" with some basic contact info?
@proteriax. Going to post an initial list of things I want to explore here:
- 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 tostring
.
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.
- 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 }
-
Support mapped types. (For example:
Record
) -
Support transformers. (To enable serialization/deserialization).
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.
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.
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?
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.
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.
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.