UNPKG

iron-enum

Version:

Rust like enums for Typescript

428 lines 14.3 kB
"use strict"; /** * # IronEnum * A **zero‑dependency** helper that brings *Rust‑like tagged unions* (aka algebraic data * types) to TypeScript **while staying 100 % runtime‑light**. You get: * * * **Ergonomic, type‑safe constructors** – payload gets the right shape or you don’t compile. * * **Pattern‑matching helpers** (`match`, `matchAsync`). * * **Fluent guards** (`if.*`, `ifNot.*`). * * **Rust‑inspired convenience wrappers** – `Option`, `Result`, `Try`, `TryInto`. * * **Smart payload rules** – when *every* property of the payload object is optional the * argument itself becomes optional, so `Enum.Variant()` and `Enum.Variant({})` are both * legal. * * ```ts * const Status = IronEnum<{ * Loading: undefined * Ready: { finishedAt: Date } * }>(); * * const a = Status.Loading(); // no payload * const b = Status.Ready({ finishedAt: new Date() }); * * console.log(a.match({ * Loading: () => "still working…", * Ready: ({ finishedAt }) => `done at ${finishedAt}`, * })); * ``` * * ## Table of contents (public surface) * 1. `IronEnum` – generic builder * 2. `EnumFactory` / `EnumMethods` – runtime value API (guards + matchers) * 3. `Option`, `Some`, `None` – maybe‑value helpers * 4. `Result`, `Ok`, `Err` – success/error helpers * 5. `Try`, `TryInto` – convert imperative `throw`s into functional `Result`s * * --------------------------------------------------------------------------- */ 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; /** * Creates a single variant object internally, with all the associated utility methods. * * @param allVariants - The entire variants record for reference (not used directly, but typed). * @param tag - The variant key being constructed. * @param data - The associated data for this variant key. * @returns An object with `tag`, `data`, and all the utility methods (`if`, `ifNot`, `match`, etc.). */ function enumFactory(allVariants, tag, data) { if (tag === "_") { throw new Error('Variant key "_" is reserved; cannot use "_" as a variant name.'); } return { tag, data: data, payload: data, toJSON: () => ({ [tag]: data }), key: () => tag, if: new Proxy({}, { get: (_tgt, prop) => { return (callback, elseCallback) => { if (prop === tag) { if (callback) { const result = callback(data); return result === undefined ? true : result; } return true; } else if (elseCallback) { const result = elseCallback({ [tag]: data }); return result === undefined ? false : result; } return false; }; } }), ifNot: new Proxy({}, { get: (_tgt, prop) => { return (callback, elseCallback) => { if (prop !== tag) { if (callback) { const result = callback({ [tag]: data }); return result === undefined ? true : result; } return true; } else if (elseCallback) { const result = elseCallback({ [tag]: data }); return result === undefined ? false : result; } return false; }; } }), match: (callbacks) => { const maybeFn = callbacks[tag]; if (maybeFn) { return maybeFn(data); } const catchAll = callbacks._; if (catchAll) { return catchAll(); } throw new Error(`No handler for variant "${String(tag)}" and no "_" fallback`); }, matchAsync: async (callbacks) => { const maybeFn = callbacks[tag]; if (maybeFn) { return await maybeFn(data); } const catchAll = callbacks._; if (catchAll) { return await catchAll(); } throw new Error(`No handler for variant "${String(tag)}" and no "_" fallback`); } }; } /** * Constructs an enum "builder" object. * * Each key in the `ALL` record becomes a function to produce that variant value. * * Usage Example: * ```ts * const MyEnum = IronEnum<{ Foo: string, Bar: number }>(); * const fooValue = MyEnum.Foo("some text"); * const barValue = MyEnum.Bar(123); * * ``` * * Note: Do not use the "_" key in your variants — it is reserved for catch-all logic. */ function IronEnum() { // Using a Proxy to dynamically handle variant construction // and the special "parse" method at runtime. return new Proxy({}, { get: (_tgt, prop) => { if (prop === "_") { return new Proxy({}, { get: (_tgt2, prop2) => { if (prop2 == "parse") { return (dataObj) => { const keys = Object.keys(dataObj); if (keys.length !== 1) { throw new Error(`Expected exactly 1 variant key, got ${keys.length}`); } const actualKey = keys[0]; return enumFactory({}, actualKey, dataObj[actualKey]); }; } throw new Error(`Property '${prop2}' not availalbe at runtime!`); } }); } return (...args) => { // if caller omitted the argument *and* the payload type is an object, // inject an empty object so runtime matches the static type const data = args.length === 0 ? {} : args[0]; return enumFactory({}, prop, data); }; } }); } /** * A Result type constructor for a success or error scenario, similar to Rust's `Result<T, E>`. * * Example usage: * ```ts * const NumResult = Result<number, string>(); * const okVal = NumResult.Ok(42); * console.log(okVal.unwrap()); // 42 * * const errVal = NumResult.Err("something happened"); * console.log(errVal.unwrap_or(0)); // 0 * ``` */ const Result = () => (() => { const resultEnum = IronEnum(); return { _: resultEnum._, Ok: (value) => ({ ...resultEnum.Ok(value), unwrap: () => value, unwrap_or: x => value, isOk: () => true, isErr: () => false, unwrap_or_else: x => value, ok: () => (0, exports.Option)().Some(value) }), Err: (value) => ({ ...resultEnum.Err(value), unwrap: () => { if (value instanceof Error) { throw value; } else if (typeof value == "string") { throw new Error(value); } else if (typeof value !== "undefined" && value !== null && typeof value.toString == "function") { throw new Error(value.toString()); } else { throw new Error(`Called .unwrap() on an Result.Err enum!`); } }, isOk: () => false, isErr: () => true, unwrap_or: x => x, unwrap_or_else: x => x(), ok: () => (0, exports.Option)().None() }) }; })(); exports.Result = Result; /** * A convenience function to build a `Result.Ok` variant inline, useful when you only care about a single Ok type. * * ```ts * const val = Ok(123); * console.log(val.unwrap()); // 123 * ``` */ const Ok = (value) => (0, exports.Result)().Ok(value); exports.Ok = Ok; /** * A convenience function to build a `Result.Err` variant inline, useful when you only care about a single Err type. * * ```ts * const errorVal = Err("something went wrong"); * console.log(errorVal.match({ * Ok: (v) => `Got OK with value ${v}`, * Err: (e) => `Got Err: ${e}` * })); * ``` */ const Err = (error) => (0, exports.Result)().Err(error); exports.Err = Err; /** * An Option type constructor for a possibly-undefined value, similar to Rust's `Option<T>`. * * Example usage: * ```ts * const NumOption = Option<number>(); * * const someVal = NumOption.Some(123); * console.log(someVal.unwrap()); // 123 * * const noneVal = NumOption.None(); * console.log(noneVal.unwrap_or(0)); // 0 * ``` */ const Option = () => (() => { const optEnum = IronEnum(); return { _: optEnum._, Some: (value) => ({ ...optEnum.Some(value), isSome: () => true, isNone: () => false, unwrap: () => value, unwrap_or: x => value, unwrap_or_else: x => value, ok_or: (err) => (0, exports.Result)().Ok(value), ok_or_else: (err) => (0, exports.Result)().Ok(value), _try: () => null }), None: () => ({ ...optEnum.None(), isSome: () => false, isNone: () => true, unwrap: () => { throw new Error(`Called .unwrap() on an Option.None enum!`); }, unwrap_or: x => x, unwrap_or_else: x => x(), ok_or: (err) => (0, exports.Result)().Err(err), ok_or_else: (err) => (0, exports.Result)().Err(err()), _try: () => null }) }; })(); exports.Option = Option; /** * A convenience function to construct `Option.Some` inline with a given value. * * ```ts * const s = Some("Hello"); * console.log(s.unwrap()); // "Hello" * ``` */ const Some = (value) => (0, exports.Option)().Some(value); exports.Some = Some; /** * A convenience function to construct `Option.None` inline with no associated data. * * ```ts * const n = None(); * console.log(n.unwrap_or("default")); // "default" * ``` */ const None = () => (0, exports.Option)().None(); exports.None = None; /** * A utility for wrapping function calls (both synchronous and asynchronous) * in a `Result` type, capturing successful outputs or caught exceptions. * * This helps simplify error handling and improves readability by avoiding * `try/catch` blocks scattered throughout your code. * * ## Example (Sync): * ```ts * const result = Try.sync(() => riskyOperation()); * if (result.if.Ok()) { * console.log("Success:", result.value); * } else { * console.error("Error:", result.error); * } * ``` * * ## Example (Async): * ```ts * const result = await Try.async(() => fetchData()); * if (result.if.Ok()) { * console.log("Fetched:", result.value); * } else { * console.error("Fetch failed:", result.error); * } * ``` * * @property sync - Wraps a synchronous function and returns a `ResultFactory` * with `Ok` or `Err` depending on whether an exception is thrown. * * @property async - Wraps an asynchronous function (returning a Promise) and * returns a `Promise<ResultFactory>` containing the result or error. */ exports.Try = { sync: (callback) => { try { const output = callback(); return (0, exports.Result)().Ok(output); } catch (e) { return (0, exports.Result)().Err(e); } }, async: async (callback) => { try { const output = await callback(); return (0, exports.Result)().Ok(output); } catch (e) { return (0, exports.Result)().Err(e); } } }; /** * A higher-order utility that converts a function (sync or async) into one that * returns a `Result` instead of throwing exceptions. This is especially useful * when you want to consistently return a safe result wrapper from potentially * failing operations, while preserving the original function signature. * * This differs from `Try` by returning a **new function** rather than calling * the function immediately. * * ## Example (Sync): * ```ts * const parseIntSafe = TryInto.sync((str: string) => { * const num = parseInt(str); * if (isNaN(num)) throw new Error("Invalid number"); * return num; * }); * * const result = parseIntSafe("42"); * if (result.if.Ok()) { * console.log(result.value); * } else { * console.error(result.error) * } * ``` * * ## Example (Async): * ```ts * const fetchSafe = TryInto.async(async (url: string) => { * const res = await fetch(url); * if (!res.ok) throw new Error("Bad response"); * return res.json(); * }); * * const result = await fetchSafe("/api/data"); * if (result.if.Ok()) { * console.log(result.value); * } else { * console.log(result.error); * } * ``` * * @property sync - Wraps a synchronous function and returns a new function * that returns a `Result` instead of throwing. * @property async - Wraps an asynchronous function and returns a new function * that returns a `Promise<Result>` instead of rejecting. */ exports.TryInto = { sync: (callback) => { return (...args) => { try { const output = callback(...args); return (0, exports.Result)().Ok(output); } catch (e) { return (0, exports.Result)().Err(e); } }; }, async: (callback) => { return async (...args) => { try { const output = await callback(...args); return (0, exports.Result)().Ok(output); } catch (e) { return (0, exports.Result)().Err(e); } }; } }; //# sourceMappingURL=mod.js.map