Skip to content
CatOfJupit3rPublic

Latest commit

 

History

117 Commits

Folders and files

Repository files navigation

enumwaii

enumwaii

String enums that know where they belong.

npm package CI documentation license

Documentation · Getting started · API reference · Examples · ESLint

Enumwaii gives a closed string vocabulary a type-level owner. Members remain ordinary strings at runtime, while TypeScript rejects raw look-alikes and same-text members from distinct value sets.

import { em } from "enumwaii";

const workspaceRoles = em(["ADMIN", "MEMBER"]);
const billingRoles = em(["ADMIN", "VIEWER"]);

const WORKSPACE_ROLE = workspaceRoles.enum;
const BILLING_ROLE = billingRoles.enum;
type WorkspaceRole = (typeof workspaceRoles)["~type"];

function canInviteToWorkspace(role: WorkspaceRole): boolean {
  return role === WORKSPACE_ROLE.ADMIN;
}

canInviteToWorkspace(WORKSPACE_ROLE.ADMIN); // valid
canInviteToWorkspace(BILLING_ROLE.ADMIN); // TypeScript error: wrong owner
canInviteToWorkspace("ADMIN"); // TypeScript error: raw string

declare const externalRole: unknown;
canInviteToWorkspace(workspaceRoles.parse(externalRole)); // validate, then trust

Use enumwaii for authored, behavior-driving vocabularies such as permissions, workflow states, event kinds, access levels, provider codes, form options, and persisted statuses. Keep free-form user text, names, identifiers, and intentionally open-ended values as plain strings.

Install

npm install enumwaii

OR

pnpm add enumwaii

OR

yarn add enumwaii

OR

bun add enumwaii

OR

deno add npm:enumwaii

The package ships ESM and CommonJS entry points with declarations and source maps. Node.js 18 or newer is supported; the ESM build is also tested under Bun, Deno, and Cloudflare Workers without Node compatibility flags.

What it gives you

  • Raw literals cannot enter an owned enum position accidentally.
  • Same-text members from distinct value sets remain incompatible.
  • Members serialize naturally through JSON, URLs, forms, databases, and structured cloning.
  • parse, safeParse, and is validate unknown values where they enter.
  • Every declaration implements Standard Schema v1 directly.
  • pick, omit, extend, and combine preserve or deliberately create identity; derive and deriveTo build exhaustive data from owned members.
  • Optional ESLint rules enforce authoring conventions that TypeScript cannot express reliably.

The ownership brand exists only in TypeScript. Assertions, any, plain JavaScript, and unvalidated external data can still bypass it, so parse JSON, route values, form fields, database rows, provider payloads, and agent output at their boundary.

Boundary behavior

declare const input: unknown;

const strictRole = workspaceRoles.parse(input);
canInviteToWorkspace(strictRole);

const result = workspaceRoles.safeParse(input);
if (result.success) {
  canInviteToWorkspace(result.value);
} else {
  throw result.error;
}

Parsing also distinguishes absence from malformed input:

workspaceRoles.parse(input, { default: WORKSPACE_ROLE.MEMBER });
workspaceRoles.parse(input, { fallback: WORKSPACE_ROLE.MEMBER });

default handles only null and undefined; fallback handles every other invalid value. See runtime boundaries for complete behavior and error handling.

Standard Schema first

An enumwaii declaration already is a Standard Schema schema. Compatible form, RPC, routing, and validation libraries can consume workspaceRoles directly, without another wrapper or validator dependency.

Use an adapter only when an API specifically requires its native schema type:

import { emToZodSchema } from "enumwaii/zod";

export const workspaceRoleSchema = emToZodSchema(workspaceRoles);

Zod and Valibot are optional peers with separate enumwaii/zod and enumwaii/valibot entry points. Read schemas and adapters for installation and type-preserving examples.

Compose and derive

const adminRoles = workspaceRoles.pick([WORKSPACE_ROLE.ADMIN]);
adminRoles.is(WORKSPACE_ROLE.ADMIN);

const labels = workspaceRoles.derive(
  [WORKSPACE_ROLE.ADMIN, "Administrator"],
  [WORKSPACE_ROLE.MEMBER, "Member"],
);

labels.get(WORKSPACE_ROLE.ADMIN);

Tuple derivation is deliberate: object keys erase branded provenance in TypeScript. The derivation guide covers contextual output types, callback transforms, deriveTo, and arrays of target members.

Use .enum for normal application code. .rawEnum, .rawValues, and .cases are narrow integration and discriminated-union escape hatches, not parallel general-purpose APIs. See member surfaces before adopting one.

Optional lint enforcement

The lightweight preset requires no TypeScript project and enforces declaration casing:

import enumwaii from "eslint-plugin-enumwaii";

export default [...enumwaii.configs["flat/recommended"]];

The flat/recommended-type-checked preset additionally catches direct member view access, raw comparisons, raw subset members, .cases misuse, and unsafe union-narrowing patterns. It requires TypeScript parser services; follow the complete ESLint setup.

Tested in real applications

Area Checked-in examples
Web applications Next.js, TanStack Start + Solid, and Vue
Servers Hono, Elysia, oRPC, Effect, and NestJS
Runtimes Node.js, Bun, Deno, and Cloudflare Workers through the shared Hono boundary contract
Data and everyday UI Drizzle + PGlite, Mongoose, TanStack Form, native forms, TanStack Table, URL state, persistence hydration, request validation, and visible failure paths across the examples above

Every example is independently runnable and exercises invalid or ambiguous input instead of demonstrating only a happy path. Open the example catalog for commands and hosted StackBlitz links.

Learn more

Packages

Package Purpose
enumwaii Runtime declarations, Standard Schema, adapters, types, and derivation helpers.
eslint-plugin-enumwaii Syntax-only and type-aware authoring rules.

Contributing

Read CONTRIBUTING.md for setup, tests, documentation, and Changesets. Release and repository administration live in MAINTAINERS.md. Usage questions belong in SUPPORT.md, and security reports should follow SECURITY.md.

License

MIT © CatOfJupit3r

Releases

Used by

Contributors

Languages