@joookiwi/enumerable
TypeScript icon, indicating that this package has built-in type declarations

3.5.0 • Public • Published

Enumeration (javascript version)

downloads

Table of content

Installation

npm install @joookiwi/enumerable
npm i @joookiwi/enumerable

npm install --save @joookiwi/enumerable
npm i -S @joookiwi/enumerable

npm install --save-dev @joookiwi/enumerable
npm i -D @joookiwi/enumerable

Similar projects (by others)

Here are some related projects. They don't contain any unfinished version (like 0.0.1 or 0.1.0). Some features may be different. They could also have a different approach, but eventually, some features could be added to give simplicity.

Motivation

The Typescript enums are overly simple. They don't have a great support for other values that are not a string or a number). And that is not even including a behaviour enum or a classed-based enum.

The usage of almost no static values is with the mindset of Kotlin.

The goal is to have an implementation of an enum class usable for either Javascript or Typescript.

Future features

Some features are still missing from this project. Notably the way to verify if a value exists within the enum class. No release date or order is made to the features, but they should be added eventually.

  • hasValue(value: PossibleEnumerableValue)
  • hasName(value: PossibleEnumerableValue)
  • hasOrdinal(value: PossibleEnumerableValue)

  • newEnum(values: Iterable<string>)
  • newEnum(values: Iterable<string>, parent: Enumerable)
  • newEnum(values: Iterable<string>, parent: EnumerableWithParent, grandParent: Enumerable)
  • newEnum(values: Iterable<string>, parent: EnumerableWithGrandParent, grandParent: EnumerableWithParent, greatGrandParent: Enumerable)

  • newCompanionEnum(instance: Enumerable)
  • newCompanionEnum(instance: EnumerableWithParent, parentInstance: Enumerable)
  • newCompanionEnum(instance: EnumerableWithGrandParent, parentInstance: EnumerableWithParent, grandParentInstance: Enumerable)
  • newCompanionEnum(instance: EnumerableWithGreatGrandParent, parentInstance: EnumerableWithGrandParent, grandParentInstance: EnumerableWithParent, greatGrandParentInstance: Enumerable)

  • EnumWithDoubleParent
  • EnumWithTripleParent
  • EnumWithDoubleGrandParent
  • EnumWithTripleGrandParent
  • EnumWithDoubleGreatGrandParent
  • EnumWithTripleGreatGrandParent
  • CompanionEnumWithDoubleParent
  • CompanionEnumWithTripleParent
  • CompanionEnumWithDoubleGrandParent
  • CompanionEnumWithTripleGrandParent
  • CompanionEnumWithDoubleGreatGrandParent
  • CompanionEnumWithTripleGreatGrandParent

1. How to use it (the base)

To properly use the enum class, there is really one required field.
public static readonly CompanionEnum.

The rest is only there to give flexibility toward the initialization (in Javascript and in Typescript).

The flexibility is separated in three parts. The excluded values in the companion enum
protected readonly _EXCLUDED_NAMES.
The default value for the getValue, getName and getOrdinal methods. This has an order of precedence depending on the return value (if it is null). It starts by
protected readonly _DEFAULT
or protected readonly _DEFAULT_NAME
or protected readonly _DEFAULT_ORDINAL
or NullEnumerableException thrown.
The third part (optional) is in Typescript to add the ordinal to the enum class type definition.
static O: EnumType.N with O and N as their specific ordinal and name respectively

Javascript
import {CompanionEnum, Enum} from "@joookiwi/enumerable"

export class Example extends Enum {

   static A = new Example()
   static B = new Example()
   static C = new Example()

   static CompanionEnum = class CompanionEnum_Example extends CompanionEnum {
       static #instance
       constructor() { super(Example,) }
       static get get() { return CompanionEnum_Example.#instance ??= new CompanionEnum_Example() }
   }

   // The class content starts here

}
Typescript
// Example.ts
import {CompanionEnum, Enum} from "@joookiwi/enumerable"
import type {CompanionEnumSingleton} from "@joookiwi/enumerable/dist/types"
import type {Names, Ordinals} from "./Example.types"

export class Example extends Enum<Ordinals, Names> {

    public static readonly A = new Example()
    public static readonly B = new Example()
    public static readonly C = new Example()

    // Optional ordinal typing (start)
    public static readonly 0: typeof Example.A
    public static readonly 1: typeof Example.B
    public static readonly 2: typeof Example.C
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumSingleton<Example, typeof Example> =
        class CompanionEnum_Example extends CompanionEnum<Example, typeof Example> {
            static #instance?: CompanionEnum_Example
            private constructor() { super(Example,) }
            public static get get() { return CompanionEnum_Example.#instance ??= new CompanionEnum_Example() }
        }

    private constructor() { super() }

    // The class content starts here

}
// Example.types.ts
type Enum = {
   A: 0
   B: 1
   C: 2
}

export type Names = keyof Enum
export type Ordinals = Enum[Names]

2. Utilizing the default value

To have a default value, it can be triggered in two different ways. The first is via its initialization and the second, via its setter methods.

It may be possible to change the default value after its initialization by calling the CompanionEnum.defaultValue setter.

Example.CompanionEnum.defaultValue = 'A'             // By name
Example.CompanionEnum.defaultValue = 1               // By ordinal
Example.CompanionEnum.defaultValue = Example.C       // By enumerable (current)
Example.CompanionEnum.defaultValue = ParentExample.C // By enumerable (parent)

The other way to have a default value (for the getValue, getName or getOrdinal) is during its initialization The method CompanionEnum.defaultValue will only be called if no value has been set. Note that null or undefined are a valid value to unset, but nothing can uninitialized the value once it has been set. It looks in order _DEFAULT (for a valid Enumerable), _DEFAULT_NAME (for a string) and _DEFAULT_ORDINAL (for a number or a bigint). And sending a primitive or an object doesn't matter at the end of the process.

Javascript
class CompanionEnum_Example extends CompanionEnum {
    _DEFAULT = condition1 ? Example.B : null
    _DEFAULT_NAME = condition2 ? 'C' : null
    get _DEFAULT_ORDINAL() {
        // Some code to get something for an ordinal
        return defaultValueByOrdinal
    }
}
Typescript
class CompanionEnum_Example extends CompanionEnum<Example, typeof Example> {
    protected override readonly _DEFAULT = condition1 ? Example.B : null
    protected override readonly _DEFAULT_NAME = condition2 ? 'C' as const satisfies Names : null
    protected override get _DEFAULT_ORDINAL() {
        // Some code to get something for an ordinal
        return defaultValueByOrdinal satisfies PossibleNumeric<Ordinals>
    }
}

3. Excluding some values

It can happen to have interpreted values in the instance. Or it is also possible to have values that should not be counted as a enum instance. In cases like these, override the excluded names in the companion enum. Note that a method (getter, setter or function) will never be counted as a possible instance of enum.

Javascript
class Example extends Enum {

    static A = new Example()
    static B = new Example()
    static C = new Example()
    static D = someReason ? Example.A : Example.B
    static SOME_FIELD = Example.D
    static get ALREADY_EXSLUDED_GETTER() { return Example.A }

    static CompanionEnum = class CompanionEnum_Example extends CompanionEnum {
        _EXCLUDED_NAMES = ['D', "SOME_FIELD",]
    }

}
Typescript
class Example extends Enum<Ordinals, Names> {

    public static readonly A = new Example()
    public static readonly B = new Example()
    public static readonly C = new Example()
    public static readonly D = someReason ? Example.A : Example.B
    public static readonly SOME_FIELD = Example.D
    public static get ALREADY_EXSLUDED_GETTER() { return Example.A }

    public static readonly CompanionEnum: CompanionEnumSingleton<Example, typeof Example> =
        class CompanionEnum_Example extends CompanionEnum<Example, typeof Example> {
        protected readonly _EXCLUDED_NAMES = ['D', "SOME_FIELD",]
    }

}

4. Inheritance by a single parent

In practice, it is possible to have a desire to inherit another enum, but in practice, enum class are final and cannot be extended.

But it can be done indirectly via EnumWithParent, EnumWithNullableParent, EnumerableWithNullableParent and EnumerableWithParent in juncture with CompanionEnumWithParent or CompanionEnumWithParentDeclaration.

There are some possibilities on how it can be implemented, but the result is always the same.

Note that there are some things to handle correctly when using the EnumWithParent or EnumWithNullableParent instances:

  • The symbols ENUM_REFERENCE_BY_ITS_NAME_SYMBOL is there as an alternative to no arguments
  • The symbols NULL_ENUM_REFERENCE_SYMBOL is there as an alternative to null/undefined
  • Giving no arguments or null/undefined is different
  • Giving a name (string) is permitted
  • Giving an ordinal (number or bigint) is prohibited (the order cannot be assured from the child compared to the parent)
Javascript
// ParentEnum.js
import {CompanionEnum, Enum} from "@joookiwi/enumerable"

export class ParentEnum extends Enum {

    static A = new ParentEnum()
    static B = new ParentEnum()

    static CompanionEnum = class CompanionEnum_ParentEnum extends CompanionEnum {
        static #instance
        constructor() { super(ParentEnum,) }
        static get get() { return CompanionEnum.#instance ??= new CompanionEnum() }
    }

}
EnumWithParent (from a value)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumWithNullableParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithNullableParent {

    /** @readonly */ static A = new ChildEnum(ParentEnum.A,)
    /** @readonly */ static B = new ChildEnum(ParentEnum.B,)

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor(parent = null) { super(parent,) }

}
EnumWithParent (inferred, but slowest)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumWithParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithParent {

    /** @readonly */ static A = new ChildEnum()
    /** @readonly */ static B = new ChildEnum()

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor() { super() }

}
EnumWithParent (by name, but slower)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumWithParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithParent {

    /** @readonly */ static A = new ChildEnum('A',)
    /** @readonly */ static B = new ChildEnum('B',)

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor(parent) { super(parent,) }

}
EnumWithNullableParent (value upfront)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumWithNullableParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithNullableParent {

    /** @readonly */ static A = new ChildEnum(ParentEnum.A,)
    /** @readonly */ static B = new ChildEnum(ParentEnum.B,)
    /** @readonly */ static C = new ChildEnum()
    /** @readonly */ static D = new ChildEnum()

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor(parent = null) { super(parent,) }

}
EnumWithNullableParent (name upfront, but slower)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumWithNullableParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithNullableParent {

    /** @readonly */ static A = new ChildEnum('A',)
    /** @readonly */ static B = new ChildEnum('B',)
    /** @readonly */ static C = new ChildEnum()
    /** @readonly */ static D = new ChildEnum()

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor(parent = null) { super(parent,) }

}
EnumWithNullableParent (null upfront, but slowest)
// ChildEnum.ts
import {CompanionEnumWithParent, EnumConstants, EnumWithNullableParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

export class ChildEnum extends EnumWithNullableParent {

    /** @readonly */ static A = new ChildEnum()
    /** @readonly */ static B = new ChildEnum()
    /** @readonly */ static C = new ChildEnum(null,)
    /** @readonly */ static D = new ChildEnum(null,)

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */ constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    /** @private */constructor(parent = EnumConstants.ENUM_REFERENCE_BY_ITS_NAME_SYMBOL) { super(parent,) }

}
EnumerableWithParent (manually declared)
// ChildEnum.ts
import {CompanionEnumWithParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

/** @implements {EnumerableWithParent} */
export class ChildEnum extends Enum {

    /** @readonly */ static A = new ChildEnum(ParentEnum.A,)
    /** @readonly */ static B = new ChildEnum(ParentEnum.B,)

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    #parent
    /** @private */constructor(parent) { super(); this.#parent = parent }
    get parent() { return this.#parent }

}
EnumerableWithNullableParent (manually declared)
// ChildEnum.ts
import {CompanionEnumWithParent} from "@joookiwi/enumerable"

import {ParentEnum} from "./ParentEnum"

/** @implements {EnumerableWithNullableParent} */
export class ChildEnum extends Enum {

    /** @readonly */ static A = new ChildEnum(ParentEnum.A,)
    /** @readonly */ static B = new ChildEnum(ParentEnum.B,)
    /** @readonly */ static C = new ChildEnum()
    /** @readonly */ static D = new ChildEnum()

    static CompanionEnum = class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
        static #instance
        /** @private */constructor() { super(ChildEnum, ParentEnum,) }
        static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    #parent
    /** @private */constructor(parent = null) { super(); this.#parent = parent }
    get parent() { return this.#parent }

}
Typescript
// ParentEnum.ts
import {CompanionEnum, Enum} from "@joookiwi/enumerable"

import type {ParentOrdinals, ParentNames} from "./ParentEnum.types"

export class ParentEnum extends Enum<ParentOrdinals, ParentNames> {

   public static readonly A = new ParentEnum()
   public static readonly B = new ParentEnum()

    // Optional ordinal typing (start)
   public static readonly 0: typeof ParentEnum.A
   public static readonly 1: typeof ParentEnum.B
    // Optional ordinal typing (end)

   public static readonly CompanionEnum: CompanionEnumSingleton<ParentEnum, typeof ParentEnum> =
        class CompanionEnum_ParentEnum extends CompanionEnum<ParentEnum, typeof ParentEnum> {
            static #instance?: CompanionEnum_ParentEnum
            private constructor() { super(ParentEnum,) }
            public static get get() { return CompanionEnum.#instance ??= new CompanionEnum() }
        }

   private constructor() { super() }

}
// ParentEnum.types.ts
type ParentEnumType = {
    A: 0
    B: 1
}
export type ParentNames = keyof ParentEnumType
export type ParentOrdinals = ParentEnumType[ParentNames]
// ChildEnum.types.ts

type ChildEnumType = {
    A: 0
    B: 1
    C: 2
    D: 3
}
export type ChildNames = keyof ChildEnumType
export type ChildOrdinals = ChildEnumType[ChildNames]
EnumWithParent (from a value)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithNullableParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithNullableParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum(ParentEnum.A,)
    public static readonly B = new ChildEnum(ParentEnum.B,)
    public static readonly C = new ChildEnum()
    public static readonly D = new ChildEnum()

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor(parent: NullOr<ParentEnum> = null, ) { super(parent,) }

}
EnumWithParent (inferred, but slowest)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum()
    public static readonly B = new ChildEnum()

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor() { super() }

}
EnumWithParent (by name, but slower)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum('A',)
    public static readonly B = new ChildEnum('B',)

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor(parent: ParentNames,) { super(parent,) }

}
EnumWithNullableParent (value upfront)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum(ParentEnum.A,)
    public static readonly B = new ChildEnum(ParentEnum.B,)

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor(parent: ParentEnum, ) { super(parent,) }

}
EnumWithNullableParent (name upfront, but slower)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithNullableParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithNullableParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum('A',)
    public static readonly B = new ChildEnum('B',)
    public static readonly C = new ChildEnum()
    public static readonly D = new ChildEnum()

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    public static readonly 2: typeof ChildEnum.C
    public static readonly 3: typeof ChildEnum.D
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor(parent: NullOr<ParentNames> = null,) { super(parent,) }

}
EnumWithNullableParent (null upfront, but slowest)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, NullOr, PossibleEnumSymbol} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, EnumConstants, EnumWithNullableParent} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

export class ChildEnum
        extends EnumWithNullableParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum()
    public static readonly B = new ChildEnum()
    public static readonly C = new ChildEnum(null,)
    public static readonly D = new ChildEnum(null,)

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    public static readonly 2: typeof ChildEnum.C
    public static readonly 3: typeof ChildEnum.D
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    private constructor(parent: NullOr<PossibleEnumSymbol> = EnumConstants.ENUM_REFERENCE_BY_ITS_NAME_SYMBOL,) { super(parent,) }

}
EnumerableWithParent (manually declared)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, EnumerableWithParent, NullOr} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, Enum} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

class ChildEnum extends Enum<ChildOrdinals, ChildNames>
    implements EnumerableWithParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum(ParentEnum.A,)
    public static readonly B = new ChildEnum(ParentEnum.B,)

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance?: CompanionEnum_ChildEnum
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    readonly #parent
    private constructor(parent: ParentEnum,) { super(); this.#parent = parent }
    public get parent(): ParentEnum { return this.#parent }

}
EnumerableWithNullableParent (manually declared)
// ChildEnum.ts
import type {CompanionEnumWithParentSingleton, EnumerableWithNullableParent, NullOr} from "@joookiwi/enumerable"
import {CompanionEnumWithParent, Enum} from "@joookiwi/enumerable"

import type {ChildOrdinals, ChildNames} from "./ChildEnum.types"
import {ParentEnum} from "./ParentEnum"

class ChildEnum extends Enum<ChildOrdinals, ChildNames>
    implements EnumerableWithNullableParent<ChildOrdinals, ChildNames, ParentEnum> {

    public static readonly A = new ChildEnum(ParentEnum.A,)
    public static readonly B = new ChildEnum(ParentEnum.B,)
    public static readonly C = new ChildEnum()
    public static readonly D = new ChildEnum()

    // Optional ordinal typing (start)
    public static readonly 0: typeof ChildEnum.A
    public static readonly 1: typeof ChildEnum.B
    public static readonly 2: typeof ChildEnum.C
    public static readonly 3: typeof ChildEnum.D
    // Optional ordinal typing (end)

    public static readonly CompanionEnum: CompanionEnumWithParentSingleton<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> =
            class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
        static #instance?: CompanionEnum_ChildEnum
        private constructor() { super(ChildEnum, ParentEnum,) }
        public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
    }

    readonly #parent
    private constructor(parent: NullOr<ParentEnum> = null,) { super(); this.#parent = parent }
    public get parent(): NullOr<ParentEnum> { return this.#parent }

}

Inheritance by a composed parent

This part hasn't been done in the project yet, but it will be added eventually.

5. Inheritance by more than one parent (2 or 3)

The inheritance can continue from up to three instances in the chain.

The logic is always the same toward the nullable and non-nullable instances.

There is also a logic where an instance with nullable value can inherit both instances (nullable and non-null). But the opposite is not true.

non-null parent + non-null grand-parent (valid)
class Enum1 extends Enum { /*class content*/ }
class Enum2 extends EnumWithParent<Enum1> { /*class content*/ }
class Enum3 extends EnumWithGrandParent<Enum1, Enum2> { /*class content*/ }
nullable parent + non-null grand-parent (invalid)
class Enum1 extends Enum { /*class content*/ }
class Enum2 extends EnumWithNullableParent<Enum1> { /*class content*/ }     // Is valid
class Enum3 extends EnumWithGrandParent<Enum1, Enum2> { /*class content*/ } // Is not possible
non-null parent + nullable grand-parent (valid)
class Enum1 extends Enum { /*class content*/ }
class Enum2 extends EnumWithParent<Enum1> { /*class content*/ }
class Enum3 extends EnumWithNullableGrandParent<Enum1, Enum2> { /*class content*/ }
nullable parent + nullable grand-parent (valid)
class Enum1 extends Enum { /*class content*/ }
class Enum2 extends EnumWithNullableParent<Enum1> { /*class content*/ }
class Enum3 extends EnumWithNullableGrandParent<Enum1, Enum2> { /*class content*/ }

The same logic is applicable for the great-grandparent instances.

6. Inheritance by more than three parents

If the inheritance is needed, it is still possible to extend the currently defined predefined EnumWith*** or EnumerableWith***.

7. Companion enum

The companion enum instances are deeply linked to the enum class instances. But they don't really need to be directly used.

(All the empty bracket {} are a custom implementation)

Javascript
CompanionEnum (simplest)
import {CompanionEnum} from "@joookiwi/enumerable"
class CustomCompanionEnum extends CompanionEnum {
    /** @protected */ constructor() { super(Enum1,) }
}
CompanionEnumDeclaration (hardest, but fully customizable)
/** @implements CompanionEnumDeclaration */
class CustomCompanionEnum {

    /** @readonly */ #instance
    /** @protected */constructor() { this.#instance = Enum1 }
    get instance() { return this.#instance }

    get defaultValue() {}
    set defaultValue(value) {}
    setDefaultValue(value) { this.defaultValue = value; return this }

    get values() {}
    get names() {}
    get ordinals() {}
    get iterator() { return this.values[Symbol.iterator]() }

    getValue(value) {}
    getName(value) {}
    getOrdinal(value) {}

    [Symbol.iterator]() { return this.values[Symbol.iterator]() }
    get [Symbol.toStringTag] () { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
CompanionEnumWithParent (simplest)
import {CompanionEnumWithParent} from "@joookiwi/enumerable"
class CustomCompanionEnum extends CompanionEnumWithParent {
    /** @protected */ constructor() { super(Enum1, Enum2,) }
}
CompanionEnumWithParentDeclaration (hardest, but fully customizable)
/** @implements CompanionEnumWithParentDeclaration */
class CustomCompanionEnum {

    /** @readonly */ #instance
    /** @readonly */ #parentInstance
    /** @protected */constructor() {
        this.#instance = Enum1
        this.#parentInstance = Enum2
    }
    get instance() { return this.#instance }
    get parentInstance() { return this.#parentInstance }

    get defaultValue() {}
    set defaultValue(value) {}
    setDefaultValue(value) { this.defaultValue = value; return this }

    get values() {}
    get names() {}
    get ordinals() {}
    get iterator() { return this.values[Symbol.iterator]() }

    getValue(value) {}
    getName(value) {}
    getOrdinal(value) {}

    [Symbol.iterator]() { return this.values[Symbol.iterator]() }
    get [Symbol.toStringTag] () { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
CompanionEnumWithGrandParent (simplest)
import {CompanionEnumWithGrandParent} from "@joookiwi/enumerable"
class CustomCompanionEnum extends CompanionEnumWithGrandParent {
    /** @protected */ constructor() { super(Enum1, Enum2, Enum3,) }
}
CompanionEnumWithGrandParentDeclaration (hardest, but fully customizable)
/** @implements CompanionEnumWithGrandParentDeclaration */
class CustomCompanionEnum {

    /** @readonly */ #instance
    /** @readonly */ #parentInstance
    /** @readonly */ #grandParentInstance
    /** @protected */constructor() {
        this.#instance = Enum1
        this.#parentInstance = Enum2
        this.#grandParentInstance = Enum3
    }
    get instance() { return this.#instance }
    get parentInstance() { return this.#parentInstance }
    get grandParentInstance() { return this.#grandParentInstance }

    get defaultValue() {}
    set defaultValue(value) {}
    setDefaultValue(value) { this.defaultValue = value; return this }

    get values() {}
    get names() {}
    get ordinals() {}
    get iterator() { return this.values[Symbol.iterator]() }

    getValue(value) {}
    getName(value) {}
    getOrdinal(value) {}

    [Symbol.iterator]() { return this.values[Symbol.iterator]() }
    get [Symbol.toStringTag] () { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
CompanionEnumWithGreatGrandParent (simplest)
import {CompanionEnumWithGreatGrandParent} from "@joookiwi/enumerable"
class CustomCompanionEnum extends CompanionEnumWithGreatGrandParent {
    /** @protected */ constructor() { super(Enum1, Enum2, Enum3, Enum4,) }
}
CompanionEnumWithGreatGrandParentDeclaration (hardest, but fully customizable)
/** @implements CompanionEnumWithGreatGrandParentDeclaration */
class CustomCompanionEnum {

    /** @readonly */ #instance
    /** @readonly */ #parentInstance
    /** @readonly */ #grandParentInstance
    /** @readonly */ #greatGrandParentInstance
    /** @protected */constructor() {
        this.#instance = Enum1
        this.#parentInstance = Enum2
        this.#grandParentInstance = Enum3
        this.#greatGrandParentInstance = Enum4
    }
    get instance() { return this.#instance }
    get parentInstance() { return this.#parentInstance }
    get grandParentInstance() { return this.#grandParentInstance }
    get greatGrandParentInstance() { return this.#greatGrandParentInstance }

    get defaultValue() {}
    set defaultValue(value) {}
    setDefaultValue(value) { this.defaultValue = value; return this }

    get values() {}
    get names() {}
    get ordinals() {}
    get iterator() { return this.values[Symbol.iterator]() }

    getValue(value) {}
    getName(value) {}
    getOrdinal(value) {}

    [Symbol.iterator]() { return this.values[Symbol.iterator]() }
    get [Symbol.toStringTag] () { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
Typescript
CompanionEnum (simplest)
import {CompanionEnum} from "@joookiwi/enumerable"
class CustomCompanionEnum
        extends CompanionEnum<Enum1, typeof Enum1> {
    protected constructor() { super(Enum1,) }
}
CompanionEnumDeclaration (hardest, but fully customizable)
import type {CollectionHolder, CollectionIterator} from "@joookiwi/collection"
import type {CompanionEnumDeclaration, CompanionEnumName,
  NameOf, Nullable, OrdinalOf, PossibleEnumerableValue} from "@joookiwi/enumerable"

class CustomCompanionEnum
        implements CompanionEnumDeclaration<Enum1, typeof Enum1> {

  readonly #instance
  protected constructor() { this.#instance = Enum1 }
  public get instance(): typeof Enum1 { return this.#instance }

  public get defaultValue(): Enum1 {}
  public set defaultValue(value: Nullable<PossibleEnumerableValue<Enum1>>,) {}
  public setDefaultValue(value: Nullable<PossibleEnumerableValue<Enum1>>,): this { this.defaultValue = value; return this }

  public get values(): CollectionHolder<Enum1> {}
  public get names(): CollectionHolder<NameOf<Enum1>> {}
  public get ordinals(): CollectionHolder<OrdinalOf<Enum1>> {}
  public get iterator(): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }

  public getValue(value: Nullable<PossibleEnumerableValue<Enum1>>,): Enum1 {}
  public getName(value: Nullable<PossibleEnumerableValue<Enum1>>,): NameOf<Enum1> {}
  public getOrdinal(value: Nullable<PossibleEnumerableValue<Enum1>>,): OrdinalOf<Enum1> {}

  public [Symbol.iterator](): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }
  public get [Symbol.toStringTag](): CompanionEnumName { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }

}
CompanionEnumWithParent (simplest)
import {CompanionEnumWithParent} from "@joookiwi/enumerable"

class CustomCompanionEnum
        extends CompanionEnumWithParent<
                Enum1, typeof Enum1,   // enum
                Enum2, typeof Enum2> { // parent
    protected constructor() {
      super(Enum1, Enum2,)
    }
}
CompanionEnumWithParentDeclaration (hardest, but fully customizable)
import type {CollectionHolder, CollectionIterator} from "@joookiwi/collection"
import type {CompanionEnumWithParentDeclaration, CompanionEnumName,
  NameOf, Nullable, OrdinalOf, PossibleEnumerableValue} from "@joookiwi/enumerable"

class CustomCompanionEnum
        implements CompanionEnumWithParentDeclaration<
                     Enum1, typeof Enum1,   // enum
                     Enum2, typeof Enum2> { // parent

    readonly #instance
    readonly #parentInstance
    protected constructor() {
        this.#instance = Enum1
        this.#parentInstance = Emum2
    }
    public get instance(): typeof Enum1 { return this.#instance }
    public get parentInstance(): typeof Enum2{ return this.#parentInstance }

    public get defaultValue(): Enum1 {}
    public set defaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2>,) {}
    public setDefaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2>,): this { this.defaultValue = value; return this }

    public get values(): CollectionHolder<Enum1> {}
    public get names(): CollectionHolder<NameOf<Enum1>> {}
    public get ordinals(): CollectionHolder<OrdinalOf<Enum1>> {}
    public get iterator(): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }

    public getValue(value: PossibleEnumerableValue<| Enum1 | Enum2>,): Enum1 {}
    public getName(value: PossibleEnumerableValue<| Enum1 | Enum2>,): NameOf<Enum1> {}
    public getOrdinal(value: PossibleEnumerableValue<| Enum1 | Enum2>,): OrdinalOr<Enum1> {}

    public [Symbol.iterator](): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }
    public get [Symbol.toStringTag](): CompanionEnumName { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
CompanionEnumWithGrandParent (simplest)
import {CompanionEnumWithGrandParent} from "@joookiwi/enumerable"
class CustomCompanionEnum
        extends CompanionEnumWithGrandParent<
                  Enum1, typeof Enum1,   // enum
                  Enum2, typeof Enum2,   // parent
                  Enum3, typeof Enum3> { // grand-parent
    constructor() { super(Enum1, Enum2, Enum3,) }
}
CompanionEnumWithGrandParentDeclaration (hardest, but fully customizable)
import type {CollectionHolder, CollectionIterator} from "@joookiwi/collection"
import type {CompanionEnumWithGrandParentDeclaration, CompanionEnumName,
  NameOf, Nullable, OrdinalOf, PossibleEnumerableValue} from "@joookiwi/enumerable"

class CustomCompanionEnum
        implements CompanionEnumWithGrandParentDeclaration<
                     Enum1, typeof Enum1,   // enum
                     Enum2, typeof Enum2,   // parent
                     Enum3, typeof Enum3> { // grand-parent

    readonly #instance
    readonly #parentInstance
    readonly #grandParentInstance
    protected constructor() {
        this.#instance = Enum1
        this.#parentInstance = Enum2
        this.#grandParentInstance = Enum3
    }
    public get instance(): typeof Enum1 { return this.#instance }
    public get parentInstance(): typeof Enum2 { return this.#parentInstance }
    public get grandParentInstance(): typeof Enum3 { return this.#grandParentInstance }

    public get defaultValue(): Enum1 {}
    public set defaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3>,) {}
    public setDefaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3>,): this { this.defaultValue = value; return this }

    public get values(): CollectionHolder<Enum1> {}
    public get names(): CollectionHolder<NameOf<Enum1>> {}
    public get ordinals(): CollectionHolder<OrdinalOf<Enum1>> {}
    public get iterator(): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }

    public getValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3>,): Enum1 {}
    public getName(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3>,): NameOf<Enum1> {}
    public getOrdinal(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3>,): OrdinalOf<Enum1> {}

    public [Symbol.iterator](): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }
    public get [Symbol.toStringTag](): CompanionEnumName { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}
CompanionEnumWithGreatGrandParent (simplest)
import {CompanionEnumWithGreatGrandParent} from "@joookiwi/enumerable"
class CustomCompanionEnum
        extends CompanionEnumWithGreatGrandParent<
                  Enum1, typeof Enum1,   // enum
                  Enum2, typeof Enum2,   // parent
                  Enum3, typeof Enum3,   // grand-parent
                  Enum4, typeof Enum4> { // great-grandparent
    protected constructor() { super (Enum1, Enum2, Enum3, Enum4,)}
}
CompanionEnumWithGreatGrandParentDeclaration (hardest, but fully customizable)
import type {CollectionHolder, CollectionIterator} from "@joookiwi/collection"
import type {CompanionEnumWithGreatGrandParentDeclaration, CompanionEnumName, NameOf, Nullable, OrdinalOf, PossibleEnumerableValue} from "@joookiwi/enumerable"

class CustomCompanionEnum
        implements CompanionEnumWithGreatGrandParent<
                     Enum1, typeof Enum1,   // enum
                     Enum2, typeof Enum2,   // parent
                     Enum3, typeof Enum3,   // grand-parent
                     Enum4, typeof Enum4> { // great-grandparent

    readonly #instance
    readonly #parentInstance
    readonly #grandParentInstance
    readonly #greatGrandParentInstance
    protected constructor() {
        this.#instance = Enum1
        this.#parentInstance = Enum2
        this.#grandParentInstance = Enum3
        this.#greatGrandParentInstance = Enum4
    }
    public get instance(): typeof Enum1 { return this.#instance }
    public get parentInstance(): typeof Enum2 { return this.#parentInstance }
    public get grandParentInstance(): typeof Enum3 { return this.#grandParentInstance }
    public get greatGrandParentInstance(): typeof Enum4 { return this.#greatGrandParentInstance }

    public get defaultValue(): Enum1 {}
    public set defaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3 | Enum4>,) {}
    public setDefaultValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3 | Enum4>,): this { this.defaultValue = value; return this }

    public get values(): CollectionHolder<Enum1> {}
    public get names(): CollectionHolder<NameOf<Enum1>> {}
    public get ordinals(): CollectionHolder<OrdinalOf<Enum1>> {}
    public get iterator(): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }

    public getValue(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3 | Enum4>,): Enum1 {}
    public getName(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3 | Enum4>,): NameOf<Enum1> {}
    public getOrdinal(value: PossibleEnumerableValue<| Enum1 | Enum2 | Enum3 | Enum4>,): OrdinalOf<Enum1> {}

    public [Symbol.iterator](): CollectionIterator<Enum1> { return this.values[Symbol.iterator]() }
    public get [Symbol.toStringTag](): CompanionEnumName { return "CompanionEnum" /* or EnumConstants.COMPANION_ENUM_TO_STRING_TAG */ }
  
}

Common mistakes

Reversing the inheritance

The "inheritance" is only dependent on the ordering of the constructors received in the companion enum class.

So, by reversing the ordering, then the implementation is correctly used.

Use super(ChildEnum, ParentEnum,) instead of super(ParentEnum, ChildEnum,)

Javascript

Change the implementation from:

class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
     static #instance
     constructor() { super(ParentEnum, ChildEnum,) }
     static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
 }

to

class CompanionEnum_ChildEnum extends CompanionEnumWithParent {
     static #instance
     constructor() { super(ChildEnum, ParentEnum,) }
     static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
 }
Typescript

Change the implementation from:

class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ParentEnum, typeof ParentEnum, ChildEnum, typeof ChildEnum> {
   static #instance?: CompanionEnum_ChildEnum
   private constructor() { super(ParentEnum, ChildEnum,) }
   public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
}

to

class CompanionEnum_ChildEnum extends CompanionEnumWithParent<ChildEnum, typeof ChildEnum, ParentEnum, typeof ParentEnum> {
   static #instance?: CompanionEnum_ChildEnum
   private constructor() { super(ChildEnum, ParentEnum,) }
   public static get get() { return CompanionEnum_ChildEnum.#instance ??= new CompanionEnum_ChildEnum() }
}

Forgetting the type declaration on the companion enum

In Typescript, it can be possible to have an error even though, it cannot be one in Javascript.

By declaring a class onto a field, then, the type inference would not work properly if it has a reference of itself.

That is why every companion enum has a singleton type declaration.

// Bad example
class Example extends Enum {
    // ts-error: Field cannot infer the type
    public static readonly Field = class NestedExample {
        field = Example
    }
}
// Correct example
class Example extends Enum {
    public static readonly Field: NestedExampleDeclaration<typeof Example> = class NestedExample {
        readonly field = Example
    }
}
interface NestedExampleDeclaration<T> {
    readonly field: T
}

Calling a companion enum methods, but giving an error on valid instances

In Typescript, there is the type variance for the generics.

It can be summed to the

  • invariance (T) → (the type directly)
  • covariance (out T) → (the type or a child)
  • contravariance (in T) → (the type or a parent)

The covariance should be used instead of the invariance when having a specific name & ordinal on the enums.

// Bad example
class Example<const NAME extends Names = Names, const ORDINAL extends Ordinals = Ordinals,>
    extends Enum<NAME, ORDINAL> {
    // Whatever is needed inside
}

// This gives a typescript error
//      TS2375: Type  Example_A  is not assignable to type  Example<Ordinals, Names>  with 'exactOptionalPropertyTypes: true'"
Example.CompanionEnum.get.getValue(Example.A,)
// Correct example
class Example<const out NAME extends Names = Names, const out ORDINAL extends Ordinals = Ordinals,>
    extends Enum<NAME, ORDINAL> {
    // Whatever is needed inside
}

// Is now correcty handled
Example.CompanionEnum.get.getValue(Example.A,)

Having an Enumerable to not have a value from getLastPrototype

Maybe this method did throw you something like

NullReferenceException: No Enumerable-like could be found from the prototype chain "EnumLike → EnumLike → Object".

This is likely due to having one requirement (ordinal, name, Symbol.toPrimitive or Symbol.toStringTag) set as a field instead of a method (or getter method).

The values are not expected to change, but it is expected to have at least one class with everything the Enumerable have.

A simple change can be done from:

Javascript (by fields)
class EnumLike {
    constructor() {
        this.ordinal = someOrdinalCode
        this.name = someNameCode
        this[Symbol.toPrimitive] = () => someToPrimitiveCode
        this[Symbol.toStringTag] = "Enum"
    }
}

to be transformed to

class EnumLike {
    #ordinal
    #name
    constructor() {
        this.#ordinal = someOrdinalCode
        this.#name = someNameCode
    }
    get ordinal() { return this.#ordinal }
    get name() { return this.#name }
    [Symbol.toPrimitive]() { return somePrimitiveCode }
    get [Symbol.toStringTag]() { return "Enum" }
}
Javascript (by constructor default value)
class EnumLike {
    constructor(ordinal = someOrdinalCode, name = someNameCode){
        this.ordinal = ordinal
        this.name = ordinal
        this[Symbol.toPrimitive] = () => someToPrimitiveCode
        this[Symbol.toStringTag] = "Enum"
    }
}

to be transformed to

class EnumLike {
    #ordinal
    #name
    constructor(ordinal = someOrdinalCode, name = someNameCode) {
        this.#ordinal = ordinal
        this.#name = name
    }
    get ordinal() { return this.#ordinal }
    get name() { return this.#name }
    [Symbol.toPrimitive]() { return somePrimitiveCode }
    get [Symbol.toStringTag]() { return "Enum" }
}
Typescript (by simple fields)
class EnumLike {
    public readonly ordinal = someOrdinalCode
    public readonly name = someNameCode
    public readonly [Symbol.toPrimitive] = () => someToPrimitiveCode
    public readonly [Symbol.toStringTag] = "Enum"
}

to be transformed to

class EnumLike {
    readonly #ordinal
    readonly #name
    constructor() {
        this.#ordinal = someOrdinalCode
        this.#name = someNameCode
    }
    public get ordinal() { return this.#ordinal }
    public get name() { return this.#name }
    public [Symbol.toPrimitive]() { return somePrimitiveCode }
    public get [Symbol.toStringTag]() { return "Enum" as const }
}
Typescript (by constructor initialization fields)
class EnumLike {
    public readonly [Symbol.toPrimitive] = () => someToPrimitiveCode
    public readonly [Symbol.toStringTag] = "Enum"
    constructor(public readonly ordinal = someOrdinalCode, 
                public readonly name = someNameCode,){}
}

to be transformed to

class EnumLike {
    readonly #ordinal
    readonly #name
    constructor(ordinal = someOrdinalCode,
                name = someNameCode,) {
        this.#ordinal = ordinal
        this.#name = name
    }
    public get ordinal() { return this.#ordinal }
    public get name() { return this.#name }
    public [Symbol.toPrimitive]() { return somePrimitiveCode }
    public get [Symbol.toStringTag]() { return "Enum" as const }
}

Known error

There is a known error for the optional index type being set on an Enum on Typescript exclusively. Currently, it is unknown where it is located, but just commenting the type resolve the thing. An alternative to it is to put the optional types over the declared constants.

// Known error
class ChildEnum {
    public static readonly A = new ChildEnum()
    public static readonly 0: typeof ChildEnum.A
}
// ChildEnum.A is fine
// ChildEnum[0] is always being set to undefined
// Solution 1
class ChildEnum {
    public static readonly A = new ChildEnum()
    // public static readonly 0: typeof ChildEnum.A
}
// ChildEnum.A is fine
// ChildEnum[0] is fine
// Solution 2
class ChildEnum {
    public static readonly 0: typeof ChildEnum.A
    public static readonly A = new ChildEnum()
}
// ChildEnum.A is fine
// ChildEnum[0] is fine

Contribution

You can contribute to the project in 2 different ways:

Package Sidebar

Install

npm i @joookiwi/enumerable

Weekly Downloads

16

Version

3.5.0

License

MIT

Unpacked Size

1.53 MB

Total Files

677

Last publish

Collaborators

  • joookiwi