UNPKG

iron-enum

Version:

Rust like enums for Typescript

462 lines 13.2 kB
"use strict"; /** * 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