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

Quest: TypeScript support #347

Open
3 tasks
taras opened this issue Apr 7, 2019 · 0 comments
Open
3 tasks

Quest: TypeScript support #347

taras opened this issue Apr 7, 2019 · 0 comments
Labels

Comments

@taras
Copy link
Member

taras commented Apr 7, 2019

From the first day that we set out to build Microstates, our goal has been to create the most ergonomic tool for working with the state in JavaScript. To accomplish this goal, we have to respond to the changing tides of the JavaScript community to ensure that Microstates is a tool that developers use with their tools of choice.

TypeScript is increasingly becoming a tool of choice for developers working on large projects. Large projects are where Microstates are particularly helpful. By combining TypeScript and Microstates, developers get the benefits of type safety that TypeScript provides and ergonomic runtime state machines that Microstates provide.

TypeScript has been improving quickly and introducing many features that make using Microstates and TypeScript easier and more feasible. Now, we need to make changes to Microstates to bridge the gap and make the two tools work well together.

One of the features that Microstates introduced very early on was a DSL that made it easy to describe Microstates types. Microstates DSL in JavaScript uses class properties to compose one type into another. For example,

class Person {
  name = String;
  age = Number;
}

When we create a Microstate from the class Person, Microstates instantiates the Person class to capture references to classes that we composed into the Person class. We later construct Microstates for each of the corresponding classes and assign them into the original Microstate. We used this method because it allowed us to build objects at runtime without requiring an extra transpolation step.

This API works well in JavaScript, but it does not work well with TypeScript. It doesn't work well in TypeScript because TypeScript infers types from the code and it understands this data structure look like this.

class Person {
  name: Function
  age: Function
}

When it encounters an instance of a Person, it assumes that name and age are functions rather than Microstates. To express this correctly in TypeScript, we'd need to write the following.

class Person {
  name: String;
  age: Number;
}

In here lies the first challenge. To use Microstates with TypeScript, we need to use TypeScript as the DSL rather than our created DSL. Unfortunately, this is not very simple because when TypeScript compiles classes into JavaScript, it removes all of the information about the actual types. The result looks something like this,

class Person {
}

TypeScript doesn't give us enough information to be able to construct the Microstate at runtime. Likely, this is possible with TypeScript when using decorators and reflect-metadata package. Angular uses this combination for their dependency injection system.

With decorators in TypeScript, our API would look like this.

import { state } from '@microstates/decorator';

class Person {
  @state name: string;
  @state age: number;
}

Internally, @state decorator would tell TypeScript that name is actually a Microstate<StringType>. reflect-metadata would give us the information that we need to build the Microstate.

It is not yet clear how to how to handle transitions in TypeScript. Transitions in Microstates return a different value depending on their type is used. For example,

create(String).set('hello world')
// returns Microstate<StringType>

class Person {
  name = String;
}

create(Person).name.set('Taras');
// returns Microstate<Person>

In Microstates, the type that consumes the StringType dictates the return value of StringType's transitions. This kind of dynamic return value doesn't play well with TypeScript because TypeScript expects that methods return method's return value is static.

It is not yet clear what approach we'll take to making transitions work well in TypeScript, but this should stop us from taking steps towards better TypeScript compatibility. Here is a list of steps that we can take to allow progress to emerge a future solution to the transitions problem.

TODO

  1. Introduce type definitions for all primitive types - Add basic TypeScript definition #345
  2. Introduce a decorator that will provide type information instead of relying on class properties. (class properties DSL will remain in JavaScript but will not be used in TypeScript)
  3. Find a solution to transitions problem

These steps can be shipped incrementally. We'll be happy to support anyone who's interested in moving this forward.

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

No branches or pull requests

1 participant