{"id":16711486,"url":"https://github.com/twop/ts-union","last_synced_at":"2025-10-24T22:34:33.447Z","repository":{"id":31817984,"uuid":"128577908","full_name":"twop/ts-union","owner":"twop","description":"ADT sum type in typescript","archived":false,"fork":false,"pushed_at":"2023-01-06T01:37:14.000Z","size":1411,"stargazers_count":70,"open_issues_count":15,"forks_count":2,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-12T03:15:32.345Z","etag":null,"topics":["adt","sum-types","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-07T23:44:45.000Z","updated_at":"2024-03-01T17:31:41.000Z","dependencies_parsed_at":"2023-01-14T19:50:35.136Z","dependency_job_id":null,"html_url":"https://github.com/twop/ts-union","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/twop/ts-union","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twop%2Fts-union","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twop%2Fts-union/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twop%2Fts-union/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twop%2Fts-union/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twop","download_url":"https://codeload.github.com/twop/ts-union/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twop%2Fts-union/sbom","scorecard":{"id":904375,"data":{"date":"2025-08-18","repo":{"name":"github.com/twop/ts-union","commit":"4b6a2be67f4a1d026f04ebef07f3f226f02a7a98"},"scorecard":{"version":"v5.2.1-41-g40576783","commit":"40576783fda6698350fcbbeaea760ff827433034"},"score":1.7,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 1/21 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 11 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"29 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-v4rh-8p82-6h5w","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T16:48:16.941Z","repository_id":31817984,"created_at":"2025-08-24T16:48:16.941Z","updated_at":"2025-08-24T16:48:16.941Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280878346,"owners_count":26406641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adt","sum-types","typescript"],"created_at":"2024-10-12T20:12:10.744Z","updated_at":"2025-10-24T22:34:33.404Z","avatar_url":"https://github.com/twop.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ts-union\n\nA tiny library for algebraic sum types in typescript. Inspired by [unionize](https://github.com/pelotom/unionize) and [F# discriminated-unions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions) (and other ML languages)\n\n## Installation\n\n```\nnpm add ts-union\n```\n\nNOTE: Distrubuted as modern javascript (es2018) library.\n\n## Usage\n\n### Define\n\n```typescript\nimport { Union, of } from 'ts-union';\n\nconst PaymentMethod = Union({\n  Check: of\u003cCheckNumber\u003e(),\n  CreditCard: of\u003cCardType, CardNumber\u003e(),\n  Cash: of(null), // means that this variant has no payload\n});\n\ntype CheckNumber = number;\ntype CardType = 'MasterCard' | 'Visa';\ntype CardNumber = string;\n```\n\n### Construct a union value\n\n```typescript\n// Check is a function that accepts a check number\nconst check = PaymentMethod.Check(15566909);\n\n// CreditCard is a function that accepts two arguments (CardType, CardNumber)\nconst card = PaymentMethod.CreditCard('Visa', '1111-566-...');\n\n// Cash is just a value\nconst cash = PaymentMethod.Cash;\n\n// or destructure it to simplify construction :)\nconst { Cash, Check, CreditCard } = PaymentMethod;\nconst anotherCheck = Check(566541123);\n```\n\n### `match`\n\n```typescript\nconst str = PaymentMethod.match(cash, {\n  Cash: () =\u003e 'cash',\n  Check: (n) =\u003e `check num: ${n.toString()}`,\n  CreditCard: (type, n) =\u003e `${type} ${n}`,\n});\n```\n\nAlso supports deferred (curried) matching and `default` case.\n\n```typescript\nconst toStr = PaymentMethod.match({\n  Cash: () =\u003e 'cash',\n  default: (_v) =\u003e 'not cash', // _v is the union obj\n});\n\nconst str = toStr(card); // \"not cash\"\n```\n\n### `if` (aka simplified match)\n\n```typescript\nconst str = PaymentMethod.if.Cash(cash, () =\u003e 'yep'); // \"yep\"\n// typeof str === string | undefined\n```\n\nYou can provide else case as well, in that case 'undefined' type will be removed from the result.\n\n```typescript\n// typeof str === string\nconst str = PaymentMethod.if.Check(\n  cash,\n  (n) =\u003e `check num: ${n.toString()}`,\n  (_v) =\u003e 'not check' // _v is the union obj that is passed in\n); // str === 'not check'\n```\n\n### **EXPERIMENTAL** `matchWith`\n\nWARNING: This API is experimental and currently more of an MVP.\n\nOften we want to match a union with another union. A good example of this if we try to model a state transition in `useReducer` in React or model a state machine.\n\nThis is what you have to do currently:\n\n```ts\nconst State = Union({\n  Loading: of(null),\n  Loaded: of\u003cnumber\u003e(),\n  Err: of\u003cstring\u003e(),\n});\n\nconst Ev = Union({\n  ErrorHappened: of\u003cstring\u003e(),\n  DataFetched: of\u003cnumber\u003e(),\n});\n\nconst { Loaded, Err, Loading } = State;\n\nconst transition = (prev: typeof State.T, ev: typeof Ev.T) =\u003e\n  State.match(prev, {\n    Loading: () =\u003e\n      Ev.match(ev, {\n        ErrorHappened: (err) =\u003e Err(err),\n        DataFetched: (data) =\u003e Loaded(data),\n      }),\n\n    Loaded: (loadedData) =\u003e\n      // just add to the current loaded value as an example\n      Ev.if.DataFetched(\n        ev,\n        (data) =\u003e Loaded(loadedData + data),\n        () =\u003e prev\n      ),\n\n    default: (s) =\u003e s,\n  });\n```\n\nIt gets worse and more verbose when complexity grows, also you have to match the `Ev` in each variant of `State`.\n\nIn my experience this comes up often enough to justify a dedicated API for matching a pair:\n\n```ts\nimport { Union, of } from 'ts-union';\n\nconst State = Union({\n  Loading: of(null),\n  Loaded: of\u003cnumber\u003e(),\n  Err: of\u003cstring\u003e(),\n});\n\nconst Ev = Union({\n  ErrorHappened: of\u003cstring\u003e(),\n  DataFetched: of\u003cnumber\u003e(),\n});\n\nconst { Loaded, Err, Loading } = State;\n\nconst transition = State.matchWith(Ev, {\n  Loading: {\n    ErrorHappened: (_, err) =\u003e Err(err),\n    DataFetched: (_, data) =\u003e Loaded(data),\n  },\n\n  Loaded: {\n    DataFetched: (loaded, data) =\u003e Loaded(loaded + data),\n  },\n\n  default: (prevState, ev) =\u003e prevState,\n});\n\n// usage\nconst newState = transition(Loading, Ev.ErrorHappened('oops')); // \u003c-- State.Err('oops')\n```\n\n`transition` is a function with type signature: (prev: State, ev: Ev) =\u003e State.\nNote that the return type is **inferred**, meaning that you can return whatever type you want :)\n\n```ts\nconst logLoadingTransition = State.matchWith(Ev, {\n  Loading: {\n    ErrorHappened: (_, err) =\u003e 'Oops, error happened: ' + err,\n    DataFetched: (_, data) =\u003e 'Data loaded with: ' + data.toString(),\n  },\n  default: () =\u003e '',\n});\n```\n\n#### Caveats\n\n1. Doesn't support generic version (yet?)\n2. Doesn't work with unions that have more than 1 arguments in variants. E.g. `of\u003cstring, number\u003e()` will give an incomprehensible type error.\n3. You cannot pass additional data to the update function. I'm tinkering about something like this for the future releases:\n\n```ts\nconst transition = State.matchWith(Ev, {...}, of\u003cSomeContext\u003e());\ntransition = (prev, ev, someContextValue);\n```\n\n### Two ways to specify variants with no payload\n\nYou can define variants with no payload with either `of(null)` or `of\u003cvoid\u003e()`;\n\n```ts\nconst Nope = Union({\n  Old: of\u003cvoid\u003e(), // only option in 2.0\n  New: of(null), // new syntax in 2.1\n});\n\n// Note that New is a value not a function\nconst nope = Nope.New;\n\n// here Old is a function\nconst oldNope = Nope.Old();\n```\n\nNote that `Old` will always allocate a new value while `New` **is** a value (thus more efficient).\n\nFor generics the syntax differs a little bit:\n\n```ts\n// generic version\nconst Option = Union((t) =\u003e ({\n  None: of(null),\n  Some: of(t),\n}));\n\n// we need to provide a type for the Option to \"remember\" it.\nconst maybeNumber = Option.None\u003cnumber\u003e();\n```\n\nEven though `None` is a function, but it **always** returns the same value. It is just a syntax to \"remember\" the type it was constructed with;\n\nSpeaking of generics...\n\n### Generic version\n\n```typescript\n// Pass a function that accepts a type token and returns a record\nconst Maybe = Union((val) =\u003e ({\n  Nothing: of(null), // type is Of\u003c[Unit]\u003e\n  Just: of(val), // type is Of\u003c[Generic]\u003e\n}));\n```\n\nNote that `val` is a **value** of the special type `Generic` that will be substituted with an actual type later on. It is just a variable name, pls feel free to name it whatever you feel like :) Maybe `a`, `T` or `TPayload`?\n\nThis feature can be handy to model network requests (like in `Redux`):\n\n```typescript\nconst ReqResult = Union((data) =\u003e ({\n  Pending: of(null),\n  Ok: of(data),\n  Err: of\u003cstring | Error\u003e(),\n}));\n\n// res is inferred as UnionValG\u003cstring, ...\u003e\nconst res = ReqResult.Ok('this is awesome!');\n\nconst status = ReqResult.match(res, {\n  Pending: () =\u003e 'Thinking...',\n  Err: (err) =\u003e\n    typeof err === 'string' ? `Oops ${err}` : `Exception ${err.message}`,\n  Ok: (str) =\u003e `Ok, ${str}`,\n}); // 'Ok, this is awesome!'\n```\n\nLet's try to build `map` and `bind` functions for `Maybe`:\n\n```typescript\nconst { Nothing, Just } = Maybe;\n\n// GenericValType is a helper that allows you to substitute Generic token type.\ntype MaybeVal\u003cT\u003e = GenericValType\u003cT, typeof Maybe.T\u003e;\n\nconst map = \u003cA, B\u003e(val: MaybeVal\u003cA\u003e, f: (a: A) =\u003e B) =\u003e\n  Maybe.match(val, {\n    Just: (v) =\u003e Just(f(v)),\n    Nothing: () =\u003e Nothing\u003cB\u003e(), // note that we have to explicitly provide B type here\n  });\n\nconst bind = \u003cA, B\u003e(val: MaybeVal\u003cA\u003e, f: (a: A) =\u003e MaybeVal\u003cB\u003e) =\u003e\n  Maybe.if.Just(\n    val,\n    (a) =\u003e f(a),\n    (n) =\u003e (n as unknown) as MaybeVal\u003cB\u003e\n  );\n\nmap(Just('a'), (s) =\u003e s.length); // -\u003e Just(1)\nbind(Just(100), (n) =\u003e Just(n.toString())); // -\u003e Just('100')\n\nmap(Nothing\u003cstring\u003e(), (s) =\u003e s.length); // -\u003e Nothing\n```\n\nAnd if you want to **extend** `Maybe` with these functions:\n\n```typescript\nconst TempMaybe = Union(val =\u003e ({\n  Nothing: of(),\n  Just: of(val)\n}));\n\nconst map = .....\nconst bind = .....\n\n// TempMaybe is just an object, so this is perfectly legit\nexport const Maybe = {...TempMaybe, map, bind};\n```\n\n### Type of resulted objects\n\nTypes of union values are opaque. That makes it possible to experiment with different underlying data structures.\n\n```typescript\ntype CashType = typeof cash;\n// UnionVal\u003c{Cash:..., Check:..., CreditCard:...}\u003e\n// and it is the same for card and check\n```\n\nThe `UnionVal\u003c...\u003e` type for `PaymentMethod` is accessible via phantom property `T`\n\n```typescript\ntype PaymentMethodType = typeof PaymentMethod.T;\n// UnionVal\u003c{Cash:..., Check:..., CreditCard:...}\u003e\n```\n\n## API and implementation details\n\nIf you log a union value to console you will see a plain object.\n\n```typescript\nconsole.log(PaymentMethod.Check(15566909));\n// {k:'Check', p0:15566909, p1: undefined, p2: undefined, a: 1}\n```\n\nThis is because union values are objects under the hood. The `k` element is the key, `p0` - `p1` are passed in parameters and `a` is the number of parameters. I decided not to expose that through typings but I might reconsider that in the future. You **cannot** use it for redux actions, however you can **safely use it for redux state**.\n\nNote that in version 2.0 it was a tuple. But [benchmarks](https://github.com/twop/ts-union/tree/master/benchmarks) showed that object are more efficient (I have no idea why arrays cannot be jitted efficiently). You can find more details below\n\n### API\n\nUse `Union` constructor to define the type\n\n```typescript\nimport { Union, of } from 'ts-union';\n\nconst U = Union({\n  Simple: of(), // or of\u003cvoid\u003e(). no payload.\n  SuperSimple: of(null), // static union value with no payload\n  One: of\u003cstring\u003e(), // one argument\n  Const: of(3), // one constant argument that is baked in\n  Two: of\u003cstring, number\u003e(), // two arguments\n  Three: of\u003cstring, number, boolean\u003e(), // three\n});\n\n// generic version\nconst Option = Union((t) =\u003e ({\n  None: of(null),\n  Some: of(t), // Note: t is a value of the special type Generic\n}));\n\n// for static variant values you still have to provide a type\n// because it needs to \"remember\" the type.\n// Thus a function call, but it will always return the same object\nconst opt = Option.None\u003cstring\u003e();\n\n// But here type is inferred as number\nconst opt2 = Option.Some(5);\n```\n\nLet's take a closer look at `of` function\n\n```typescript\nexport interface Types {\n  (unit: null): Of\u003c[Unit]\u003e;\n  \u003cT = void\u003e(): Of\u003c[T]\u003e;\n  (g: Generic): Of\u003c[Generic]\u003e;\n  \u003cT\u003e(val: T): Const\u003cT\u003e;\n  \u003cT1, T2\u003e(): Of\u003c[T1, T2]\u003e;\n  \u003cT1, T2, T3\u003e(): Of\u003c[T1, T2, T3]\u003e;\n}\ndeclare const of: Types;\n```\n\nthe actual implementation is pretty simple:\n\n```typescript\nexport const of: Types = ((val: any) =\u003e val) as any;\n```\n\nWe just capture the constant and don't really care about the rest. Typescript will guide us to provide proper number of args for each case.\n\n`match` accepts either a full set of props or a subset with a default case.\n\n```typescript\n// typedef for match function. Note there is a curried version\nexport type MatchFunc\u003cRecord\u003e = {\n  \u003cResult\u003e(cases: MatchCases\u003cRecord, Result\u003e): (\n    val: UnionVal\u003cRecord\u003e\n  ) =\u003e Result;\n  \u003cResult\u003e(val: UnionVal\u003cRecord\u003e, cases: MatchCases\u003cRecord, Result\u003e): Result;\n};\n```\n\n`if` either accepts a function that will be invoked (with a match) and/or else case.\n\n```typescript\n// typedef for if case for one argument.\n// Note it doesn't throw but can return undefined\n{\n    \u003cR\u003e(val: UnionVal\u003cRec\u003e, f: (a: A) =\u003e R): R | undefined;\n    \u003cR\u003e(val: UnionVal\u003cRec\u003e, f: (a: A) =\u003e R, els: (v: UnionVal\u003cRec\u003e) =\u003e R): R;\n}\n```\n\n`GenericValType` is a type that helps with generic union values. It just replaces `Generic` token type with provided `Type`.\n\n```typescript\ntype GenericValType\u003cType, Val\u003e = Val extends UnionValG\u003cinfer _Type, infer Rec\u003e\n  ? UnionValG\u003cType, Rec\u003e\n  : never;\n\n// Example\nimport { Union, of, GenericValType } from 'ts-union';\nconst Maybe = Union((t) =\u003e ({ Nothing: of(), Just: of(t) }));\ntype MaybeVal\u003cT\u003e = GenericValType\u003cT, typeof Maybe.T\u003e;\n```\n\nThat's the whole API.\n\n### Benchmarks\n\nYou can find a more details [here](https://github.com/twop/ts-union/tree/master/benchmarks). Both `unionize` and `ts-union` are 1.2x -2x (ish?) times slower than handwritten discriminated unions: aka `{tag: 'num', n: number} | {tag: 'str', s: string}`. But the good news is that you don't have to write the boilerplate yourself, _and_ it is still blazing fast!\n\n### Breaking changes from 2.1.1 -\u003e 2.2.0\n\nThere should be no public breaking changes, but I changed the underlying data structure (again!? and again!?) to be `{k: string, p0: any, p1: any, p2: any, a: number}`, where k is a case name like `\"CreditCard\"`, `p0`-`p2` passed in parameters and `a` is how many parameters were passed in. So if you stored the values somewhere (localStorage?) then please migrate accordingly.\n\n```ts\nconst oldShape = { k: 'CreditCard', p: ['Visa', '1111-566-...'] };\nconst newShape = {\n  k: 'CreditCard',\n  p0: 'Visa',\n  p1: '1111-566-...',\n  p2: undefined,\n  a: 2,\n};\n```\n\nmotivation for this is potential perf wins avoiding dealing with `(...args) =\u003e {...}`. The current approach should be more friendly for JIT compilers (arguments and ...args are hard to optimize). That kinda aligns with my local perf results:\n\nold shape\n\n```\nCreation\n    baseline: 8.39 ms\n    unionize: 17.32 ms\n    ts-union: 11.10 ms\n\nMatching with inline object\n    baseline: 1.97 ms\n    unionize: 5.96 ms\n    ts-union: 7.32 ms\n\nMatching with preallocated function\n    baseline: 2.20 ms\n    unionize: 4.21 ms\n    ts-union: 4.52 ms\n\nMapping\n    baseline: 2.02 ms\n    unionize: 2.98 ms\n    ts-union: 1.69 ms\n```\n\nnew shape\n\n```\nCreation\n    baseline: 6.90 ms\n    unionize: 15.62 ms\n    ts-union: 6.38 ms\n\nMatching with inline object\n    baseline: 2.33 ms\n    unionize: 6.26 ms\n    ts-union: 5.19 ms\n\nMatching with preallocated function\n    baseline: 1.67 ms\n    unionize: 4.44 ms\n    ts-union: 3.88 ms\n\nMapping\n    baseline: 1.96 ms\n    unionize: 2.93 ms\n    ts-union: 1.39 ms\n```\n\n### Breaking changes from 2.0.1 -\u003e 2.1\n\nThere should be no public breaking changes, but I changed the underlying data structure (again!?) to be `{k: string, p: any[]}`, where k is a case name like `\"CreditCard\"` and p is a payload array. So if you stored the values somewhere (localStorage?) then please migrate accordingly.\n\nThe motivation for it that I finally tried to benchmark the performance of the library. Arrays were 1.5x - 2x slower than plain objects :(\n\n```ts\nconst oldShape = ['CreditCard', ['Visa', '1111-566-...']];\n\n// and yes this is faster. Blame V8.\nconst newShape = { k: 'CreditCard', p: ['Visa', '1111-566-...'] };\n```\n\n### Breaking changes from 1.2 -\u003e 2.0\n\nThere should be no breaking changes, but I completely rewrote the types that drive public api. So if you for some reasons used them pls look into d.ts file for a replacement.\n\n### Breaking changes from 1.1 -\u003e 1.2\n\n- `t` function to define shapes is renamed to `of`.\n- There is a different underlying data structure. So if you persisted the values somewhere it wouldn't be compatible with the new version.\n\nThe actual change is pretty simple:\n\n```typescript\ntype OldShape = [string, ...payload[any]];\n// Note: no nesting\nconst oldShape = ['CreditCard', 'Visa', '1111-566-...'];\n\ntype NewShape = [string, payload[any]];\n// Note: captured payload is nested\nconst newShape = ['CreditCard', ['Visa', '1111-566-...']];\n```\n\nThat reduces allocations and opens up possibility for future API extensions. Such as:\n\n```typescript\n// namespaces to avoid collisions.\nconst withNamespace = ['CreditCard', ['Visa', '1111-566-...'], 'MyNamespace'];\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwop%2Fts-union","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwop%2Fts-union","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwop%2Fts-union/lists"}