iron-enum
Version:
Rust like enums for Typescript
462 lines • 13.2 kB
JavaScript
;
/**
* IronEnum – Zero-dependency Rust-style tagged-union helpers for TypeScript.
*
* @packageDocumentation
*
* A lightweight library for creating type-safe discriminated unions (tagged unions)
* with pattern matching, inspired by Rust's enum system. IronEnum provides a
* runtime representation for TypeScript union types with zero dependencies.
*
* @example Basic usage
* ```ts
* // Define an enum with three variants
* const Status = IronEnum<{
* Loading: undefined;
* Ready: { finishedAt: Date };
* Error: { message: string; code: number };
* }>();
*
* // Create instances
* const loading = Status.Loading();
* const ready = Status.Ready({ finishedAt: new Date() });
*
* // Pattern match to handle all cases
* const message = ready.match({
* Loading: () => "Still working…",
* Ready: ({ finishedAt }) => `Done at ${finishedAt.toISOString()}`,
* Error: ({ message }) => `Failed: ${message}`
* });
* ```
*
* @example Using Result and Option types
* ```ts
* // Result type for error handling
* function divide(a: number, b: number): Result<number, string> {
* return b === 0 ? Err("Division by zero") : Ok(a / b);
* }
*
* // Option type for nullable values
* function findUser(id: string): Option<User> {
* const user = users.find(u => u.id === id);
* return user ? Some(user) : None();
* }
* ```
*
* @module
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TryInto = exports.Try = exports.None = exports.Some = exports.Option = exports.Err = exports.Ok = exports.Result = void 0;
exports.IronEnum = IronEnum;
// -----------------------------------------------------------------------------
// Factory Implementation Helpers
// -----------------------------------------------------------------------------
/**
* Creates the `if` method for enum instances.
*
* @internal
*/
function makeIf(tag, data) {
return function _if(key, success, failure) {
const isHit = key === tag;
if (isHit) {
if (success) {
const r = (data == null
? success()
: success(data));
return (r === undefined ? true : r);
}
return true;
}
if (failure) {
const r = failure({ [tag]: data });
return (r === undefined ? false : r);
}
return false;
};
}
/**
* Creates the `ifNot` method for enum instances.
*
* @internal
*/
function makeIfNot(tag, data) {
return function _ifNot(key, success, failure) {
const isMiss = key !== tag;
if (isMiss) {
if (success) {
const r = success({ [tag]: data });
return (r === undefined ? true : r);
}
return true;
}
if (failure) {
const r = failure({ [tag]: data });
return (r === undefined ? false : r);
}
return false;
};
}
/**
* Core factory function that creates enum instances with all methods attached.
*
* @internal
*/
function enumFactory(allVariants, tag, data) {
if (tag === "_")
throw new Error("'_' is reserved as a fallback key.");
return {
tag,
data,
payload: data,
toJSON: () => ({ [tag]: data }),
key: () => tag,
if: makeIf(tag, data),
ifNot: makeIfNot(tag, data),
match: (callbacks) => {
const cb = callbacks[tag] ?? callbacks._;
if (!cb)
throw new Error(`No handler for variant '${String(tag)}' and no '_' fallback`);
return cb(data);
},
matchAsync: async (callbacks) => {
const cb = callbacks[tag] ?? callbacks._;
if (!cb)
throw new Error(`No handler for variant '${String(tag)}' and no '_' fallback`);
return cb(data);
},
};
}
/**
* Creates a new enum type with the specified variants.
*
* This is the main entry point for creating enums. It returns an object
* where each variant name is a constructor function.
*
* @template ALL - The VariantsRecord defining all variants
* @param args - Optional configuration
* @param args.keys - Pre-define variant keys for better performance (avoids Proxy)
* @returns Enum instance with variant constructors
*
* @example Basic enum
* ```ts
* const Direction = IronEnum<{
* North: undefined;
* South: undefined;
* East: undefined;
* West: undefined;
* }>();
*
* const heading = Direction.North();
* ```
*
* @example Enum with payloads
* ```ts
* const Result = IronEnum<{
* Success: { value: number };
* Failure: { error: string };
* }>();
*
* const ok = Result.Success({ value: 42 });
* const err = Result.Failure({ error: "Invalid input" });
* ```
*
* @example Performance optimization with pre-defined keys
* ```ts
* const Status = IronEnum<{
* Idle: undefined;
* Running: { pid: number };
* Stopped: { code: number };
* }>({
* keys: ["Idle", "Running", "Stopped"]
* });
* ```
*/
function IronEnum(args) {
const keys = args?.keys;
// Shared parse implementation
const parse = (dataObj) => {
const objKeys = Object.keys(dataObj);
if (objKeys.length !== 1)
throw new Error(`Expected exactly 1 variant key, got ${objKeys.length}`);
const actualKey = objKeys[0];
if (keys?.length && !keys.includes(actualKey))
throw new Error(`Unexpected variant '${actualKey}'`);
return enumFactory({}, actualKey, dataObj[actualKey]);
};
if (keys?.length) {
// Pre-allocated object version (better performance, no Proxy)
const out = { _: { parse } };
for (const key of keys) {
out[key] = ((data) => enumFactory({}, key, data));
}
return out;
}
// Proxy version (dynamic property access)
return new Proxy({}, {
get: (_tgt, prop) => {
if (prop === "_")
return { parse };
return (...args) => {
const data = args.length === 0
? {}
: args[0];
return enumFactory({}, prop, data);
};
},
});
}
/**
* Creates a Result type for Rust-style error handling.
*
* Result represents either success (Ok) or failure (Err), forcing
* explicit error handling through pattern matching.
*
* @template T - The success value type
* @template E - The error value type
* @returns Result enum constructor
*
* @example
* ```ts
* // Define a Result type
* type DivResult = Result<number, string>;
* const DivResult = Result<number, string>();
*
* function divide(a: number, b: number): DivResult {
* if (b === 0) {
* return DivResult.Err("Division by zero");
* }
* return DivResult.Ok(a / b);
* }
*
* // Use the Result
* const result = divide(10, 2);
* result.match({
* Ok: (value) => console.log(`Result: ${value}`),
* Err: (error) => console.error(`Error: ${error}`)
* });
* ```
*/
const Result = () => {
const R = IronEnum();
return {
_: R._,
Ok: (value) => ({
...R.Ok(value),
_: { new: R },
unwrap: () => value,
unwrap_or: () => value,
unwrap_or_else: () => value,
isOk: () => true,
isErr: () => false,
ok: () => (0, exports.Option)().Some(value),
}),
Err: (error) => ({
...R.Err(error),
_: { new: R },
unwrap: () => {
throw error instanceof Error
? error
: new Error(String(error ?? "Err"));
},
unwrap_or: (x) => x,
unwrap_or_else: (cb) => cb(),
isOk: () => false,
isErr: () => true,
ok: () => (0, exports.Option)().None(),
}),
};
};
exports.Result = Result;
/**
* Convenience function to create a successful Result.
*
* @template T - The success value type
* @param value - The success value
* @returns Result in Ok state
*
* @example
* ```ts
* const result = Ok(42);
* console.log(result.isOk()); // true
* ```
*/
const Ok = (value) => (0, exports.Result)().Ok(value);
exports.Ok = Ok;
/**
* Convenience function to create a failed Result.
*
* @template E - The error value type
* @param error - The error value
* @returns Result in Err state
*
* @example
* ```ts
* const result = Err("Something went wrong");
* console.log(result.isErr()); // true
* ```
*/
const Err = (error) => (0, exports.Result)().Err(error);
exports.Err = Err;
/**
* Creates an Option type for Rust-style nullable value handling.
*
* Option represents either a value (Some) or no value (None),
* providing a type-safe alternative to null/undefined.
*
* @template T - The value type when present
* @returns Option enum constructor
*
* @example
* ```ts
* // Define an Option type
* type MaybeUser = Option<User>;
* const MaybeUser = Option<User>();
*
* function findUser(id: string): MaybeUser {
* const user = database.find(u => u.id === id);
* return user ? MaybeUser.Some(user) : MaybeUser.None();
* }
*
* // Use the Option
* const maybeUser = findUser("123");
* maybeUser.match({
* Some: (user) => console.log(`Found: ${user.name}`),
* None: () => console.log("User not found")
* });
*
* // Convert to Result
* const userResult = maybeUser.ok_or("User not found");
* ```
*/
const Option = () => {
const O = IronEnum();
return {
_: O._,
Some: (value) => ({
...O.Some(value),
_: { new: O },
isSome: () => true,
isNone: () => false,
unwrap: () => value,
unwrap_or: () => value,
unwrap_or_else: () => value,
ok_or: (err) => (0, exports.Result)().Ok(value),
ok_or_else: (errFn) => (0, exports.Result)().Ok(value),
}),
None: () => ({
...O.None(),
_: { new: O },
isSome: () => false,
isNone: () => true,
unwrap: () => {
throw new Error("Called unwrap() on Option.None");
},
unwrap_or: (x) => x,
unwrap_or_else: (cb) => cb(),
ok_or: (err) => (0, exports.Result)().Err(err),
ok_or_else: (errFn) => (0, exports.Result)().Err(errFn()),
}),
};
};
exports.Option = Option;
/**
* Convenience function to create an Option with a value.
*
* @template T - The value type
* @param value - The value to wrap
* @returns Option in Some state
*
* @example
* ```ts
* const maybeNumber = Some(42);
* console.log(maybeNumber.isSome()); // true
* console.log(maybeNumber.unwrap()); // 42
* ```
*/
const Some = (value) => (0, exports.Option)().Some(value);
exports.Some = Some;
/**
* Convenience function to create an empty Option.
*
* @returns Option in None state
*
* @example
* ```ts
* const empty = None();
* console.log(empty.isNone()); // true
* empty.unwrap(); // Throws error
* ```
*/
const None = () => (0, exports.Option)().None();
exports.None = None;
// -----------------------------------------------------------------------------
// Try/TryInto Utilities (Exception Handling)
// -----------------------------------------------------------------------------
/**
* Utilities for converting exception-throwing code into Result-returning code.
* Provides both immediate execution (Try) and function wrapping (TryInto).
*
* @example
* ```ts
* // Immediate execution
* const result = Try.sync(() => JSON.parse('{"valid": true}'));
*
* // Async execution
* const asyncResult = await Try.async(async () => {
* const response = await fetch('/api/data');
* return response.json();
* });
*
* // Function wrapping
* const safeParse = TryInto.sync(JSON.parse);
* const safeResult = safeParse('{"valid": true}');
* ```
*/
exports.Try = {
sync(cb) {
try {
return (0, exports.Result)().Ok(cb());
}
catch (e) {
return (0, exports.Result)().Err(e);
}
},
async async(cb) {
try {
return (0, exports.Result)().Ok(await cb());
}
catch (e) {
return (0, exports.Result)().Err(e);
}
},
};
/**
* Transforms exception-throwing functions into Result-returning functions.
* Useful for safely wrapping existing APIs that might throw.
*
* @example
* ```ts
* // Wrap a potentially throwing function
* const safeParse = TryInto.sync(JSON.parse);
*
* // Now it returns a Result instead of throwing
* const result = safeParse('{"valid": true}');
* if (result.isOk()) {
* console.log("Parsed:", result.unwrap());
* }
*
* // Works with async functions too
* const safeReadFile = TryInto.async(fs.promises.readFile);
* const fileResult = await safeReadFile('./data.json', 'utf8');
* ```
*/
exports.TryInto = {
sync(cb) {
return (...args) => exports.Try.sync(() => cb(...args));
},
async(cb) {
return async (...args) => exports.Try.async(() => cb(...args));
},
};
//# sourceMappingURL=mod.js.map