npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sg.js/enum-utils

v1.0.2

Published

Use enums with familiar, immutable Set and Map interfaces.

Downloads

359

Readme

Enum Utils

Make enums more intuitive to use with familiar Set and Map interfaces! Works with numeric, string, and heterogeneous enums, and allows for easy enum type guarding, creation of enum subsets, and safe mapping of enums to anything else—even other enums!

Package: GitHub, npm  |  Releases: Changelog  |  Author: Shaun Grady

Install

npm install @sg.js/enum-utils

Requires TypeScript >=4.7

What's Inside

Classes

EnumSet: Use an enum as an immutable Set.

  • Construction via fromEnum(Enum) method takes a single enum argument.
  • has(value) is a type guard for narrowing serialized values types to the enum.
  • subset(Enum[]) safely creates an EnumSet from a subset of enum members.
  • toEnumMap(mapping) creates an EnumMap, safely mapping an enum (or enum subset) to anything.
  • Shares Set iterable methods.

EnumMap: Use an enum as an immutable Map.

  • Construction via fromEnum(Enum, { [Enum]: any }) provides exhaustive type safety for map keys while preventing numeric enum keys from being coerced to strings.
  • has(value) is a type guard for narrowing serialized values types to the enum.
  • get() has dynamically-typed return value; has() guarded values always return a value and illegal enum values always return undefined.
  • Shares Map iterable methods.

Functions

  • enumToSet: Convert an enum object to Set of enum members.
  • isEnumMember: Type guard for a given enum.
  • isValidEnumMember: Type guard for values that are strings and finite numbers.

Types

  • EnumSetMembers<EnumSet>: Returns either the original enum, or a union type of enum members for enum subsets.
  • EnumMapMembers<EnumMap>: Returns either the original enum, or a union type of enum members for enum subsets.
  • EnumMapValues<EnumMap>: Returns a union type of the EnumMap's values; works best when the map object includes a const assertion.
  • EnumMember: A finite number or string.

Table of Contents

Usage Example

enum Priority {
  Low = 'L',
  Medium = 'M',
  High = 'H',
  ThisIsFine = 'OhNo',
}

// Define our base EnumSet with all Priority members
const priorities = EnumSet.fromEnum(Priority);
// EnumSets are immutable, so aliasing to another variable is safe.
const adminPriorities = priorities;

// Non-admins will only be allowed to use a subset of priorities.
const userPriorities = adminPriorities.subset([
  Priority.Low,
  Priority.Medium,
  Priority.High,
]);

// Create a map with values constrained to a union of i18n keys.
priorityI18nMap = priorities.toEnumMap<I18nKey>({
  [Priority.Low]: 'common.low',
  [Priority.Medium]: 'common.medium',
  [Priority.High]: 'common.high',
  [Priority.ThisIsFine]: 'common.makeItStop',
});
// EnumMaps can also be constructed like this:
//   EnumMap.fromEnum(Priority, { … } as const)
// However, value types aren't as easily constrained with a single type argument,
// so using the `as const` assertion for the mapping is recommended
// for maximum type safety.

const PrioritySelect = () => {
  const { t } = useTranslation();

  // Determine which Priority to set based on user's role
  const { isAdmin } = useSession();
  const allowedPriorities = isAdmin ? adminPriorities : userPriorities;

  // This component allows a `priorityLock` search param to be set that disables
  // the priority select with the given priority. Very secure. 👌
  const { query } = useRouter();
  const priorityLock: unknown = query.priorityLock;
  const hasPriorityLock = priorityLock != null;

  // Guard `priorityLock` to our Priority enum type
  if (hasPriorityLock && !allowedPriorities.has(priorityLock)) {
    const priorityList = [...allowedPriorities].join(', ');
    throw new Error(
      `searchParam 'lockPriority' must be one of: ${priorityList}.`,
    );
  }

  const [priority, setPriority] = useState<Priority>(
    priorityLock ?? allowedPriorities.values().next().value,
  );

  return (
    <Select
      onchange={setPriority}
      disabled={hasPriorityLock}
      optionValues={Array.from(allowedPriorities)}
      renderOption={(priority: Priority) => t(priorityI18nMap.get(priority))}
    />
  );
};

EnumSet

Construction

EnumSet()

Although new EnumSet() can be called with the same value signature of Set, the type arguments aren't very developer-friendly; instead, it's recommended to make use of the EnumSet.fromEnum() static method.

Static methods

EnumSet.fromEnum()

Creates a new EnumSet instance from the given enum object.

fromEnum(Enum);
enum Color {
  Red,
  Green,
  Blue,
}

const colors = EnumSet.fromEnum(Color);

Instance methods

EnumSet.prototype.has()

The has() method returns a boolean indicating whether an enum member with the specified value exists in the EnumSet object or not, acting as a type guard.

has(value);
const colors = EnumSet.fromEnum(Color);
const value: unknown = router.query.color;
let color: Color;

if (colorToHexMap.has(value)) {
  color = value;
}

EnumSet.prototype.subset()

The subset() method returns a new EnumSet instance containing only the enum members specified, which must be members of the EnumSet.

subset([Enum]);
enum Locale {
  enUS = 'en-US',
  enGB = 'en-GB',
  frCA = 'fr-CA',
  esMX = 'es-MX',
  jaJP = 'ja-JP',
}

const siteLocales = EnumSet.from(Locale);

const videoLocales = siteLocales.subset([
  Locale.enUS,
  Locale.enGB,
  Locale.esMX,
]);

if (videoLocales.has(value)) {
  // typeof value ⮕ `Locale.enUS | Locale.enGB | Locale.esMX`
}

EnumSet.prototype.toEnumMap()

Returns an EnumMap instance that maps each enum member in the EnumSet to a corresponding value in the given mappings object. The mapping object value types may either be inferred or defined by the optional type argument. If inferred, it's recommended to use the as const assertion on the mapping object to narrow the value types.

toEnumMap(mapping);

Given the following locales EnumSet instance…

enum Locale {
  enUS = 'en-US',
  enGB = 'en-GB',
  frCA = 'fr-CA',
}

const locales = EnumSet.from(Locale);

An EnumMap can be created with string type values thusly:

const localeFileSuffixes = locales.toEnumMap({
  [Locale.enUS]: 'en',
  [Locale.enGB]: 'en',
  [Locale.frCA]: 'fr-ca',
});

const value: string = localeFileSuffixes.get(Locale.enUS);

However, there are a few ways to increase the type safety of your map values. We can define the map's value type by passing it as the first type argument of the method:

const localeI18nKeys = locales.toEnumMap<I18nKeys>({
  [Locale.enUS]: 'common.americanEnglish',
  [Locale.enGB]: 'common.britishEnglish',
  [Locale.frCA]: 'common.canadianFrench',
});

const i18nValue: I18nKeys = localeFileSuffixes.get(Locale.enUS);

The value type for the map can be narrowed further with the as const assertion:

const localeI18nKeys = locales.toEnumMap({
  [Locale.enUS]: 'common.americanEnglish',
  [Locale.enGB]: 'common.britishEnglish',
  [Locale.frCA]: 'common.canadianFrench',
} as const);

const localeValues:
  | 'common.americanEnglish'
  | 'common.britishEnglish'
  | 'common.canadianFrench' = localeFileSuffixes.get(Locale.enUS);

However, the above example isn't protecting against strings that don't exist in our I18nKeys union type. For the greatest type safety, the as const assertion can be paired with the satisfies operator:

const localeI18nKeys = locales.toEnumMap({
  [Locale.enUS]: 'common.americanEnglish',
  [Locale.enGB]: 'common.britishEnglish',
  // @ts-expect-error
  [Locale.frCA]: 'foo.bar',
} as const satisfies Record<Locale, I18nKeys>);

This approach combines the type narrowing of as const with the type checking of satisfies. It ensures that all keys of the Locale enum are present and that all values are valid I18nKeys. This provides the strongest type safety, catching errors at compile time.

EnumSet.prototype.size

EnumSet.prototype.keys()

EnumSet.prototype.values()

EnumSet.prototype.entries()

EnumSet.prototype.forEach()

These methods behave identically to the Set class. See MDN documentation for more details.

EnumMap

Construction

EnumMap()

Although new EnumMap() can be called with the same value signature of Map, the type arguments aren't very developer-friendly; instead, it's recommended to make use of the EnumMap.fromEnum() static method, or the EnumSet.prototype.toEnumMap() instance method, which allows for typing the mapping keys more easily.

Static methods

EnumMap.fromEnum()

Returns an EnumMap instance that maps each enum member in the given enum to a corresponding value in the given mappings object. The mapping object value types may either be inferred or defined by the optional type argument. If inferred, it's recommended to use the as const assertion on the mapping object to narrow the value types.

See EnumSet.prototype.toEnumMap() for nuances related to type narrowing and safety with the mapping object.

fromEnum(Enum, mapping as const);
enum Color {
  Red,
  Green,
  Blue,
}

const colorHexMap = EnumMap.fromEnum(Color, {
  [Color.Red]: '#f00',
  [Color.Green]: '#0f0',
  [Color.Blue]: '#00f',
} as const);

Instance methods

EnumMap.prototype.has()

The has() method returns a boolean indicating whether an enum member with the specified value exists in the EnumMap object or not, acting as a type guard.

has(value);
const colorToHexMap = EnumMap.fromEnum(Color, {
  [Color.Red]: '#f00',
  [Color.Green]: '#0f0',
  [Color.Blue]: '#00f',
} as const);

const value: unknown = router.query.color;
let color: Color;

if (colorToHexMap.has(value)) {
  color = value;
}

EnumMap.prototype.get()

The get() method returns a specified element from an EnumMap object. If the key's value has been guarded by the has() method, then the return type will be non-nullish. If the key is an illegal enum member type, then return type will be undefined.

get(value);
const colorToHexMap = EnumMap.fromEnum(Color, {
  [Color.Red]: '#f00',
  [Color.Green]: '#0f0',
  [Color.Blue]: '#00f',
} as const);

const value: unknown = router.query.color;
let colorHex: '#f00' | '#0f0' | '#00f';

if (colorToHexMap.has(value)) {
  colorHex = colorToHexMap.get(value);
}

EnumMap.prototype.size

EnumMap.prototype.keys()

EnumMap.prototype.values()

EnumMap.prototype.entries()

EnumMap.prototype.forEach()

These methods behave identically to the Map class. See MDN documentation for more details.

enumToSet()

Converts an enum runtime object to an array of its members. This is safe to use with numeric, string, and heterogeneous enums.

enum Fruit {
  Apple = 'apple',
  Banana = 'banana',
  Orange = 'orange',
}

const fruitSet: EnumSet<Fruit> = enumToSet<Fruit>(Fruit);

console.log(fruitSet); // ⮕ Set { 'apple', 'banana', 'orange' }

isEnumMember()

Checks if the given value is a valid member of the specified enum object, acting as a type guard.

enum CatBreed {
  Siamese,
  NorwegianForestCat,
  DomesticShorthair,
}

if (isEnumMember(CatBreed, 'Greyhound')) {
  // …
}

isValidEnumMember()

Type guards values as an eligible enum member: a finite number or string.

isValidEnumMember('foo'); // ⮕ true
isValidEnumMember(42); // ⮕ true

isValidEnumMember(NaN); // ⮕ false
isValidEnumMember({}); // ⮕ false