Skip to content

Use Typescript Branded types pattern for better enums implementation #82

@vegegoku

Description

@vegegoku

In the current implementation of enums we had to introduce some limitations and workarounds, the first one is that instead of using actual enum classes we use normal classes with static final members, then we introduced two annotations TsTypeDef and TsTypeRef and we use those to create type Aliases and assume that all the static final members will exported with the defined type alias, example :

Simulate an enum with constant values :

@JsType
@TsTypeDef(tsType = "string")
public class EnumSimulation {
  public static final String VALUE_1 = "value1";
  public static final String VALUE_2 = "value2";
  public static final String VALUE_3 = "value3";

  private static final String[] VALUES = JsObject.freeze(new String[] {VALUE_1, VALUE_2, VALUE_3});

  @JsMethod
  public String[] values() {
    return VALUES;
  }
}

Which will be exported as

	type EnumSimulationType = string;
	export class EnumSimulation {
		static readonly VALUE_1:EnumSimulationType;
		static readonly VALUE_2:EnumSimulationType;
		static readonly VALUE_3:EnumSimulationType;

		values():string[];
	}

The the enum clients will use it like this

public void useEnum(@TsTypeRef(EnumSimulation.class) String param) {}

which will be exported like this

useEnum(param:EnumSimulationType):void;

The plan with the new Branded types pattern is to introduce a better typing for this, for example

this enum

package com.vertispan.tsdefs.tests.enums;

public enum Direction {

    LEFT,
    RIGHT,
    UP,
    DOWN
}

can be exported to typescript like this

declare const __brand: unique symbol
type Brand<B> = { [__brand]: B }
export type Branded<T, B> = T & Brand<B>

type DirectionType = Branded<string, "Direction">;

export class Direction {
    static readonly LEFT="LEFT" as DirectionType;
    static readonly RIGHT ="RIGH" as DirectionType;
    static readonly UP ="UP" as DirectionType;
    static readonly DOWN ="DOWN" as DirectionType;
}

export class DirectionClient {
    followDirection(direction: DirectionType) {
        console.info('Direction : '+direction);
    }
}

function testDirection(): string {
    let a = new DirectionClient();
    a.followDirection(Direction.LEFT)
    return "";
}

testDirection();

The type definition for this could be something like this :

declare const __brand: unique symbol
type Brand<B> = { [__brand]: B }
export type Branded<T, B> = T & Brand<B>

type DirectionType = Branded<string, "Direction">;

export class Direction {
    static readonly LEFT:DirectionType;
    static readonly RIGHT:DirectionType;
    static readonly UP:DirectionType;
    static readonly DOWN:DirectionType;
}

export class DirectionClient {
    followDirection(direction: DirectionType):void;
}

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions