{"_id":"@afitzek/unenum","name":"@afitzek/unenum","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@afitzek/unenum","version":"1.0.0","license":"MIT","author":{"name":"Peter Boyer","email":"https://github.com/peterboyer"},"repository":{"type":"git","url":"git+https://github.com/peterboyer/unenum.git"},"description":"0kb, Rust-like Enums for TypeScript. Forked just for publishing.","main":"./cjs/index.js","module":"./mjs/index.js","types":"./index.d.ts","exports":{".":{"types":"./index.d.ts","import":"./mjs/index.js","default":"./cjs/index.js"},"./global":{"types":"./global.d.ts","import":"./mjs/global.js","default":"./cjs/global.js"},"./global.enum":{"types":"./global.enum.d.ts","import":"./mjs/global.js","default":"./cjs/global.js"},"./global.result":{"types":"./global.result.d.ts","import":"./mjs/global.js","default":"./cjs/global.js"},"./global.future":{"types":"./global.future.d.ts","import":"./mjs/global.js","default":"./cjs/global.js"}},"keywords":["es","enum","result","future","match","ok","error","async","promise","monad","fp","functional","ts","typescript"],"gitHead":"6ea0bd35cd7d49d12199496d42cfc305e38ccc27","bugs":{"url":"https://github.com/peterboyer/unenum/issues"},"homepage":"https://github.com/peterboyer/unenum#readme","_id":"@afitzek/unenum@1.0.0","_nodeVersion":"19.4.0","_npmVersion":"9.2.0","dist":{"integrity":"sha512-NBOl7CbF5xHs3egm3Ccfj5DdICPfuLQNjmWwxBgbs51We+P6HAMDvbpfV4GcyYjmLMYefPF7pRd6Cly3i/Zxxg==","shasum":"bcc412c6c17b38c7cc7d420fd373df6dd975002c","tarball":"https://registry.npmjs.org/@afitzek/unenum/-/unenum-1.0.0.tgz","fileCount":42,"unpackedSize":59966,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDTBliz4kNlv2GkBpjJQOb42vm4FfCKCs1zootanHv4BgIgOwwqtuFLqYhXYcA9VvW7wt1SDgPuerOP0gQt/URJT44="}]},"_npmUser":{"name":"afitzek","email":"andreas.fitzek@gmail.com"},"directories":{},"maintainers":[{"name":"afitzek","email":"andreas.fitzek@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/unenum_1.0.0_1687892685955_0.15529342954681113"},"_hasShrinkwrap":false}},"time":{"created":"2023-06-27T19:04:45.849Z","1.0.0":"2023-06-27T19:04:46.188Z","modified":"2023-06-27T19:04:46.500Z"},"maintainers":[{"name":"afitzek","email":"andreas.fitzek@gmail.com"}],"description":"0kb, Rust-like Enums for TypeScript. Forked just for publishing.","homepage":"https://github.com/peterboyer/unenum#readme","keywords":["es","enum","result","future","match","ok","error","async","promise","monad","fp","functional","ts","typescript"],"repository":{"type":"git","url":"git+https://github.com/peterboyer/unenum.git"},"author":{"name":"Peter Boyer","email":"https://github.com/peterboyer"},"bugs":{"url":"https://github.com/peterboyer/unenum/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# unenum\n\n**A 0kb, Rust-like Enum/ADT mechanism for TypeScript with zero runtime\nrequirements.**\n\n[Overview](#overview) • [Installation](#installation) • [`Enum`](#enumvariants)\n• [Patterns](#patterns) • [`Result`](#resultvalue-error) •\n[`Future`](#futurevalueorenum) • [`match`](#matchvalue-matcher---) •\n[`safely`](#safelyfn---result)\n\n</div>\n\n<br />\n\n## Overview\n\nTypeScript should have a more versitile and ergonomic Enum/ADT mechanism that\n_feels_ like native utility, as opposed its\n[limited](https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls),\n[misused](https://bluepnume.medium.com/nine-terrible-ways-to-use-typescript-enums-and-one-good-way-f9c7ec68bf15),\nand [redundant](https://www.youtube.com/watch?v=jjMbPt_H3RQ) built-in `enum`\nkeyword which can be mostly replaced with a plain key-value mapping object\nusing `as const`.\n\n<br />\n\nIntroducing `unenum`; a Rust-inspired, discriminable Enum/ADT type generic,\nfeaturing:\n\n- **Zero dependencies**; `unenum` is extremely lightweight.\n- **Zero runtime requirements**; `unenum` can be completely compiled away -- no\n  runtime or bundle size cost.\n- **`Enum` variants that can define custom per-instance data**; impossible with\n  native TypeScript `enum`s.\n\n<br />\n\n`unenum` wants to _feel_ like a native TypeScript utility type, _like a\npattern_, rather than a library:\n\n- **`Enum`s are defined as `type` statements**; instead of factory functions.\n- **`Enum`s are instantiated with plain object `{ ... }` syntax**; instead of\n  constructors.\n- **`Enum`s can be consumed (and narrowed) with plain `if` statements**;\n  instead of imported match utilities.\n\n<br />\n\nHere's an example of `unenum`'s [`Enum`](#enumvariants) compared with Rust's\n[`enum`](https://doc.rust-lang.org/rust-by-example/custom_types/enum.html):\n\n<table width=\"100%\">\n<tr>\n<td>\n<pre lang=\"ts\">// TypeScript\n \ntype WebEvent = Enum<{\n\t// Unit\n\tPageLoad: undefined;\n\tPageUnload: undefined;\n\t// Tuple (not practical; use object instead)\n\tKeyPress: { key: string };\n\tPaste: { content: string };\n\t// Object\n\tClick: { x: number; y: number };\n}>\n \nconst event: WebEvent = { is: \"PageLoad\" };\nconst event: WebEvent = { is: \"PageUnload\" };\nconst event: WebEvent = { is: \"KeyPress\", key: \"x\" };\nconst event: WebEvent = { is: \"Paste\", content: \"...\" };\nconst event: WebEvent = { is: \"Click\", x: 10, y: 10 };\n \nfunction inspect(event: WebEvent) {\n \n\tif (event.is === \"PageLoad\") console.log(event);\n\telse if (event.is === \"PageUnload\") console.log(event);\n\telse if (event.is === \"KeyPress\") console.log(event, event.key);\n\telse if (event.is === \"Paste\") console.log(event, event.content);\n\telse if (event.is === \"Click\") console.log(event, event.x, event.y);\n \n}\n</pre></td>\n\n<td>\n<pre lang=\"rust\">// Rust\n \nenum WebEvent {\n\t// Unit\n\tPageLoad,\n\tPageUnload,\n\t// Tuple\n\tKeyPress(char),\n\tPaste(String),\n\t// Struct\n\tClick { x: i64, y: i64 },\n}\n \nlet event = WebEvent::PageLoad;\nlet event = WebEvent::PageUnload;\nlet event = WebEvent::KeyPress('x')\nlet event = WebEvent::Paste(\"...\".to_owned());\nlet event = WebEvent::Click { x: 10, y: 10 };\n \nfn inspect(event: WebEvent) {\n\tmatch event {\n\t\tWebEvent::PageLoad => println!(event),\n\t\tWebEvent::PageUnload => println!(event),\n\t\tWebEvent::KeyPress(c) => println!(event, c),\n\t\tWebEvent::Paste(s) => println!(event, s),\n\t\tWebEvent::Click { x, y } => println!(event, x, y),\n\t}\n}\n</pre></td>\n\n</table>\n\n<br />\n\n## Installation\n\n```sh\nnpm install unenum\n```\n\nFor Applications\n([Global](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-d-ts.html)):\n\n```ts\nimport \"unenum/global\";\n```\n\nFor Libraries\n([Imported](https://www.typescriptlang.org/docs/handbook/2/modules.html#import-type)):\n\n```ts\nimport type { Enum, ... } from \"unenum\";\n```\n\n<br />\n\n## `Enum<Variants>`\n\nCreates a union of mutually exclusive, discriminable variants.\n\n```ts\nimport \"unenum/global.enum\"; // global\nimport type { Enum } from \"unenum\"; // imported\n\ntype Foo = Enum<{\n\tA: undefined;\n\tB: { b: string };\n\tC: { c: number };\n}>;\n-> | { is: \"A\" }\n   | { is: \"B\"; b: string }\n   | { is: \"C\"; c: number }\n```\n\n### `Enum.Keys<Enum>`\n\nInfers all possible variants' keys of the given Enum.\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\n\nEnum.Keys<Foo>\n-> \"A\" | \"B\" | \"C\"\n```\n\n### `Enum.Values<Enum>`\n\nInfers all possible variants' values of the given Enum.\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\n\nEnum.Values<Foo>\n-> | { b: string }\n   | { c: number }\n```\n\n### `Enum.Props<Enum, All?>`\n\nInfers only _common_ variants' properties' names of the given Enum. If `All` is\n`true`, then _all_ variants' properties' names are inferred.\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { x: string }; C: { x: string; y: number } }>;\n\nEnum.Props<Foo>\n-> \"x\"\n\nEnum.Props<Foo, true>\n-> \"x\" | \"y\"\n```\n\n### `Enum.Pick<Enum, VariantKeys>`\n\nNarrows a given Enum by including only the given variants by key.\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\n\nEnum.Pick<Foo, \"A\" | \"C\">\n-> | { is: \"A\" }\n   | { is: \"C\"; c: number }\n```\n\n### `Enum.Omit<Enum, VariantKeys>`\n\nNarrows a given Enum by excluding only the given variants by key.\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\n\nEnum.Omit<Foo, \"A\" | \"C\">\n-> | { is: \"B\"; b: string }\n```\n\n<br />\n\n## Patterns\n\n`Enum`s are [disciminated\nunions](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)\nthat use `is` as a property to differentiate between variants. TypeScript\nsupports [type\nnarrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) by\nanalysing control flow statements like `if` and `return` to determine when\ncertain `Enum` variants are accessible, allowing for safe property access.\n\nIf a function's return type is not explicitly annotated it will be inferred\ninstead, which _will_ lead to inaccurate `Enum` return types. Explicitly\nspecifying an `Enum` (e.g. `Foo`) as a return type will ensure that a function\nreturns a valid `Enum` variant and provides autocompletion to help instantiate\n`Enum` variants with all their properties (e.g. `return { is: \"B\", b: \"...\"\n}`).\n\n### With explicit return types (recommended)\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\n\nfunction getFoo(value: string | number): Foo {\n\tif (!value) {\n\t\treturn { is: \"A\" };\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn { is: \"B\", b: value };\n\t}\n\n\treturn { is: \"C\", c: value };\n}\n```\n\n> **Note**\n>\n> If you _need_ to limit the range of possible `Enum` variants that can be\n> returned (or used as a value/parameter/etc), use\n> [`Enum.Pick`](#enumpickenum-variantkeys) or\n> [`Enum.Omit`](#enumomitenum-variantkeys).\n\n### With `if` statements (recommended)\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\nconst foo: Foo = { ... };\n\nif (foo.is === \"A\") {\n\treturn 123;\n}\n\nif (foo.is === \"B\") {\n\treturn foo.b === \"\" ? \"empty\" : \"abc\";\n}\n\nreturn null;\n```\n\n> **Note**\n>\n> `if` statements are the most universal and native way to handle `Enum`\n> variants without any dependencies.\n\n### With `match` function (dependency)\n\nSee [`match`](#matchvalue-matcher---).\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\nconst foo: Foo = { ... };\n\nimport { match } from \"unenum\";\nmatch(foo, {\n\tA: () => 123,\n\tB: ({ b }) => b === \"\" ? \"empty\" : \"abc\",\n\tC: () => null,\n});\n```\n\n> **Note**\n>\n> Using the `match` utility will make `unenum` a runtime dependency with a\n> non-0kb bundle-size cost instead of being a type-only utility. However,\n> `match` is tiny and very helpful for reducing complexity of conditional\n> variable assignments instead of needing to write one-off functions,\n> [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)s, or ternary\n> expressions.\n\n### With ternary expressions\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\nconst foo: Foo = { ... };\n\nfoo.is === \"A\"\n\t? 123\n: foo.is === \"B\"\n\t? (foo.b === \"\" ? \"empty\" : \"abc\")\n: null;\n```\n\n> **Note**\n>\n> Ternary expressions are often criticised for poor readibility, where\n> sufficiently complex and nested expression (such as the above example) are\n> strong candidates for refactoring into functions that may use `if` statements\n> and the early-return pattern to cleanly narrow down an `Enum`'s variants.\n\n### With `switch` statements\n\n```ts\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\nconst foo: Foo = { ... }\n\nswitch (foo.is) {\n\tcase \"A\": {\n\t\treturn 123;\n\t}\n\tcase \"B\": {\n\t\treturn foo.b === \"\" ? \"empty\" : \"abc\";\n\t}\n\tdefault: {\n\t\treturn null;\n\t}\n}\n```\n\n> **Note**\n>\n> `switch` statements are severely limited because they can only branch based on\n> an `Enum`'s `is` variant discriminant. `if` statements allow for more\n> versitile conditional expressions that may accomodate evaluating other\n> variables or even properties on the `Enum` variant itself (e.g. `if (foo.is\n> === \"B\" && foo.b === \"hello\") ...`).\n\n## Included Enums\n\n### `Result<Value?, Error?>`\n\nRepresents either success `value` (`Ok`) or failure `error` (`Error`).\n\n_`Result` uses `value?: never` and `error?: never` to allow for shorthand access\nto `.value` or `.error` if you want to safely default to `undefined` if either\nproperty is not available._\n\n```ts\nimport \"unenum/global.result\"; // global\nimport type { Result } from \"unenum\"; // imported\n\nResult\n-> | { is: \"Ok\"; value: unknown; error?: never }\n   | { is: \"Error\"; error: unknown; value?: never }\n\nResult<number>\n-> | { is: \"Ok\"; value: number; error?: never }\n   | { is: \"Error\"; error: unknown; value?: never }\n\nResult<number, \"FetchError\">\n-> | { is: \"Ok\"; value: number; error?: never }\n   | { is: \"Error\"; error: \"FetchError\"; value?: never }\n```\n\n```ts\nconst getUser = async (name: string): Promise<Result<User, \"NotFound\">> => {\n\treturn { is: \"Ok\", value: user };\n\treturn { is: \"Error\", error: \"NotFound\" };\n}\n\nconst $user = await getUser(\"foo\");\nif ($user.is === \"Error\") { return ... }\nconst user = $user.value;\n\nconst $user = await getUser(\"foo\");\nconst userOrUndefined = $user.value;\nconst userOrUndefined = $user.is === \"Ok\" ? $user.value : undefined;\n\nconst $user = await getUser(\"foo\");\nconst userOrDefault = $user.value ?? defaultUser;\nconst userOrDefault = $user.is === \"Ok\" ? $user.value : defaultUser;\n```\n\nBased on Rust's\n[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) enum.\n\n> **Note**\n>\n> You may find it useful to name variables for container-like `Enum`s (like\n> `Result`s and `Future`s) with a `$` prefix (e.g. `$user`) before unwrapping\n> the desired value into non-prefixed value (e.g. `const user = $user.value`).\n\n<br />\n\n### `Future<ValueOrEnum?>`\n\nRepresents an asynchronous `value` that is either loading (`Pending`) or\nresolved (`Ready`). If defined with an `Enum` type, `Future` will omit its\n`Ready` variant in favour of the \"non-pending\" `Enum`'s variants.\n\n`Future` uses `value?: never` to allow for shorthand access to `.value` if you\nwant to safely default to `undefined` if it is not available. If using with an\n`Enum` type, all its _common_ properties will be extended as `?: never`\nproperties on the `Pending` variant to allow for shorthand `undefined` access\nalso. (See [`Enum.Props`](#enumpropsenum-all).)\n\n```ts import \"unenum/global.future\"; // global\nimport type { Future } from \"unenum\"; // imported\n\nFuture\n-> | { is: \"Pending\"; value?: never }\n   | { is: \"Ready\"; value: unknown }\n\nFuture<string>\n-> | { is: \"Pending\"; value?: never }\n   | { is: \"Ready\"; value: string }\n\nFuture<Result<number>>\n-> | { is: \"Pending\"; value?: never; error?: never }\n   | { is: \"Ok\"; value: number; error?: never }\n   | { is: \"Error\"; error: unknown; value?: never }\n```\n\n```tsx\nconst useRemoteUser = (name: string): Future<Result<User, \"NotFound\">> => {\n\treturn { is: \"Pending\" };\n\treturn { is: \"Ok\", value: user };\n\treturn { is: \"Error\", error: \"NotFound\" };\n};\n\nconst $user = useRemoteUser(\"foo\");\nif ($user.is === \"Pending\") { return <Loading />; }\nif ($user.is === \"Error\") { return <Error />; }\nconst user = $user.value;\nreturn <View user={user} />;\n\nconst $user = useRemoteUser(\"foo\");\nconst userOrUndefined = $user.value;\nconst userOrUndefined = $user.is === \"Ok\" ? $user.value : undefined;\n\nconst $user = useRemoteUser(\"foo\");\nconst userOrDefault = $user.value ?? defaultUser;\nconst userOrDefault = $user.is === \"Ok\" ? $user.value : defaultUser;\n```\n\nBased on Rust's\n[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) trait and\n[`Poll`](https://doc.rust-lang.org/std/task/enum.Poll.html) enum.\n\n<br />\n\n## Utils\n\n### `match(value, matcher) -> ...`\n\nUses a given `Enum` `value` to execute its corresponding variants' `matcher`\nfunction and return its result. Use `match.orUndefined(...)` or\n`match.orDefault(...)` if you want to match against only a subset of variants.\n\n```ts\nimport { match } from \"unenum\"; // dependency\n\ntype Foo = Enum<{ A: undefined; B: { b: string }; C: { c: number } }>;\nconst foo: Foo = ...\n\n// all cases\nmatch(foo, {\n\tA: () => null,\n\tB: ({ b }) => b,\n\tC: ({ c }) => c,\n})\n-> null | string | number\n\n// some cases or undefined\nmatch.orUndefined(foo, {\n\tA: () => null,\n\tB: ({ b }) => b,\n})\n-> null | string | undefined\n\n// some cases or default\nmatch.orDefault(\n\tfoo,\n\t{ A: () => null },\n\t($) => $.is === \"B\" ? true : false\n)\n-> null | string | boolean\n```\n\n### `safely(fn) -> Result`\n\nExecutes a given function and returns a `Result` that wraps its normal return\nvalue as `Ok` and any thrown errors as `Error`. Supports async/`Promise`\nreturns.\n\n```ts\nimport { safely } from \"unenum\"; // dependency\n\nsafely(() => JSON.stringify(...))\n-> Result<string>\n\nsafely(() => JSON.parse(...))\n-> Result<unknown>\n\nsafely(() => fetch(\"/endpoint\").then(res => res.json() as Data))\n-> Promise<Result<Data>>\n```\n","readmeFilename":"README.md"}