{"id":27184222,"url":"https://github.com/snowflyt/megamatch","last_synced_at":"2025-04-13T17:49:49.622Z","repository":{"id":286667734,"uuid":"962123074","full_name":"Snowflyt/megamatch","owner":"Snowflyt","description":"Painless pattern matching in TypeScript with type safety and minimalistic syntax","archived":false,"fork":false,"pushed_at":"2025-04-11T04:32:15.000Z","size":174,"stargazers_count":21,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T16:01:03.316Z","etag":null,"topics":["adt","match","matching","pattern","pattern-matching","switch","type-safe","type-safety","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/megamatch","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Snowflyt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-07T17:24:48.000Z","updated_at":"2025-04-12T11:12:58.000Z","dependencies_parsed_at":"2025-04-11T12:07:46.175Z","dependency_job_id":"b7020d8b-f91f-4255-984d-3a81db7df3d2","html_url":"https://github.com/Snowflyt/megamatch","commit_stats":null,"previous_names":["snowflyt/megamatch"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fmegamatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fmegamatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fmegamatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fmegamatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Snowflyt","download_url":"https://codeload.github.com/Snowflyt/megamatch/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248757925,"owners_count":21156954,"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","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","match","matching","pattern","pattern-matching","switch","type-safe","type-safety","typescript"],"created_at":"2025-04-09T16:38:22.985Z","updated_at":"2025-04-13T17:49:49.595Z","avatar_url":"https://github.com/Snowflyt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003emegamatch\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003ePainless\u003c/i\u003e \u003cstrong\u003epattern matching\u003c/strong\u003e in \u003cstrong\u003eTypeScript\u003c/strong\u003e with \u003cstrong\u003etype-safety\u003c/strong\u003e and \u003cstrong\u003eminimalistic\u003c/strong\u003e syntax.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/megamatch\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/megamatch.svg\" alt=\"downloads\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/megamatch\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/megamatch.svg\" alt=\"npm version\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/megamatch\"\u003e\n    \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/megamatch.svg\" alt=\"minzipped size\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://coveralls.io/github/Snowflyt/megamatch?branch=main\"\u003e\n    \u003cimg src=\"https://img.shields.io/coverallsCoverage/github/Snowflyt/megamatch?branch=main\" alt=\"coverage status\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/Snowflyt/megamatch\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/megamatch.svg\" alt=\"MPL-2.0 license\" height=\"18\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n```typescript\nimport { match } from \"megamatch\";\n\ntype Data = { type: \"text\"; content: string } | { type: \"img\"; src: string };\ntype Result = { type: \"ok\"; data: Data } | { type?: \"error\" | \"fatal\"; message: string };\n\nconst result: Result = /* ... */;\n\n// Pattern matching with exhaustiveness checking in TypeScript\nconst html = match(result, { // [1]\n  \"{ type?: 'error' | 'fatal' }\": (res) =\u003e `\u003cp\u003eOops! Something went wrong: ${res.message}\u003c/p\u003e`,\n  \"{ type: 'ok', data: { type: 'text', content: _ } }\": (content) =\u003e `\u003cp\u003e${content}\u003c/p\u003e`,\n  \"{ type: 'ok', data: { type: 'img', src } as data }\": ({ src, data }) =\u003e `\u003cimg src=\"${src}\" /\u003e`,\n});\n\n// Point-free style API\nconst quickSort: (nums: number[]) =\u003e number[] = match({\n  \"[]\": () =\u003e [],\n  \"[head, ...tail]\": ({ head, tail }) =\u003e {\n    const smaller = tail.filter((n) =\u003e n \u003c= head);\n    const greater = tail.filter((n) =\u003e n \u003e head);\n    return [...quickSort(smaller), head, ...quickSort(greater)];\n  },\n});\n```\n\n\u003csmall\u003e[1]: Example inspired by \u003ca href=\"https://github.com/gvergnaud/ts-pattern\"\u003ets-pattern\u003c/a\u003e.\u003c/small\u003e\n\n\u003e [!WARNING]\n\u003e\n\u003e This library is highly experimental and relies heavily on complex type-level programming. Deeply nested patterns may cause excessive type-level recursion and slow down the TypeScript compiler.\n\n## Features\n\n- An embedded pattern matching language (DSL) with **minimalistic** syntax.\n- Smart **type inference** with parsers on both type-level and runtime.\n- **Exhaustiveness checking** ensures all cases are handled.\n- Dual API to match in both normal and **point-free** style.\n- **Optimization** in point-free style for better performance.\n- **Validate** a value against a [pattern](#patterns) with [`matches`](#matches).\n- Match a value against a pattern without exhaustive checking with [`ifMatch`](#ifmatch).\n\n## Installation\n\nTo install megamatch via npm (or any other package manager you prefer):\n\n```shell\nnpm install megamatch\n```\n\n## Quickstart\n\nmegamatch exports the `match` function to match a value against a pattern, which can be used in **2** different ways:\n\n1. **Normal style**: The first argument is the value to match, and the second argument is an object with patterns as keys and functions as values.\n2. **Point-free style**: The only argument is an object with patterns as keys and functions as values. The function returned by `match` can be called with the value to match.\n\nThe former is more common and is similar to the pattern matching syntax in other languages, while the latter is more functional and is optimized to avoids parsing patterns on each call.\n\nBoth styles support specifying the return type or input type of the function using TypeScript generics with the following syntax:\n\n```typescript\n// Note that the empty parentheses `()` are required for the generic type parameters\nconst result = match\u003cReturnType, InputType\u003e()(value, ...);\n\n// Omit the input type if you only want to specify the return type\nconst result = match\u003cReturnType\u003e()(value, ...);\n```\n\nAside from `match`, [`matches`](#matches) and [`ifMatch`](#ifmatch) also support both styles and allow specifying the return type or input type of the function using the same syntax.\n\nInside the object, each pattern is represented as a string key, and the corresponding function defines the behavior if the pattern matches. The patterns are generally a DSL (Domain-Specific Language) that resembles JavaScript destructuring syntax, supporting rest properties/elements and object shorthand properties, but with some additional features. See the [Patterns](#patterns) section for more details.\n\nThe input arguments of the functions are decided by the following rules:\n\n1. If no “argument” is selected in the pattern, the function will receive the whole matched value as its first argument.\n2. If only unnamed arguments (`_`) are selected, the function will receive each of the matched values as separate arguments by the order they appear in the pattern. For example, `\"[_, '+', _]\": (left, right) =\u003e ...` will receive two arguments, the first and third ones of the matched array; `\"{ foo: _, bar: { baz: _ } as _ }\": (foo, baz, bar) =\u003e ...` will receive three arguments, each `_` representing a matched value.\n3. If only named arguments are selected (e.g., `value` and `rest` in `{ key: value, ...rest }`,`head` and `tail` in `[head, ...tail]`, `alias` and `shorthandProp` in `{ type: 'ok', shorthandProp } as alias`), the function will receive an object with the selected arguments as its first argument.\n\nNote that unnamed and named arguments cannot be mixed in the same pattern, otherwise an error will be thrown at both runtime and type level.\n\n`match` supports exhaustive checking, meaning that if a pattern is not matched, you’ll see TypeScript complains a type error right away in your code editor:\n\n```typescript\nconst result = match(value, {\n  \"{ type?: 'error' | 'fatal' }\": (res) =\u003e \"an error or fatal result\",\n  // ~~~~~~~~~~~~~~~~~~~~~~~~~~\n  // ... 'NonExhaustive\u003c{ type: \"ok\"; data: { type: \"img\"; src: string; }; }\u003e'.\n  \"{ type: 'ok', data: { type: 'text', content: _ } }\": (content) =\u003e \"a text result\",\n});\n```\n\nTo avoid this, provide a “catch-all” pattern with `*` or `_` at the end of the object:\n\n```typescript\nconst result = match(value, {\n  \"{ type?: 'error' | 'fatal' }\": (res) =\u003e \"an error or fatal result\",\n  \"{ type: 'ok', data: { type: 'text', content: _ } }\": (content) =\u003e \"a text result\",\n  _: (v) =\u003e \"any other value\", // catch-all pattern\n});\n```\n\n## API Reference\n\nApart from the `match` function, megamatch also provides some utility functions with patterns.\n\n### `matches`\n\n`matches` is a type guard function that validates a value against a pattern and returns a boolean indicating whether the value matches the pattern or not.\n\n```typescript\nimport { matches } from \"megamatch\";\n\nif (matches(value, \"{ type: 'error' | 'fatal' }\")) {\n  // `value` is either an error or fatal result\n  const { type } = value; // The type of `value` is narrowed to `{ type: 'error' | 'fatal' }`\n}\n```\n\nSimilar to `match`, `matches` can be used in **2** different ways:\n\n1. **Normal style**: The first argument is the value to match, and the second argument is the pattern to match against.\n2. **Point-free style**: The only argument is the pattern to match against. The function returned by `matches` can be called with the value to match.\n\nLike `match`, `matches` also allows specifying the _input type_ of the function using TypeScript generics.\n\n```typescript\nmatches(value, pattern);\nmatches\u003cInputType\u003e()(value, pattern);\n\nconst isError = matches(\"{ type: 'error' }\");\nconst isError = matches\u003cMyType\u003e()(\"{ type: 'error' }\");\n\nif (isError(value)) {\n  // Do something when `value` matches the pattern\n}\n```\n\n### `ifMatch`\n\n`ifMatch` validates a value against a pattern and returns the matched value if it matches, or `null` if it does not match.\n\n```typescript\nimport { ifMatch } from \"megamatch\";\n\n// `result` is either the matched value or null\nconst result = ifMatch(value, \"{ type: 'error', message: _ }\", (message) =\u003e {\n  console.log(`Error: ${message}`);\n});\n```\n\nThis is useful to simplify a common pattern like this:\n\n```typescript\nmatch(value, {\n  \"{ type: 'error', message: _ }\": (message) =\u003e {\n    console.log(`Error: ${message}`);\n  },\n  _: () =\u003e {},\n});\n```\n\nSince `match` forces exhaustive checking, you need to provide a catch-all pattern to avoid TypeScript errors if the patterns are not exhaustive. `ifMatch` does not require this, which is a good alternative if you want to avoid this boilerplate code:\n\n```typescript\nifMatch(value, \"{ type: 'error', message: _ }\", (message) =\u003e {\n  console.log(`Error: ${message}`);\n});\n```\n\nThis function is inspired by [Rust’s `if let` syntax](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html).\n\nSimilar to `match`, `ifMatch` can be in **3** different ways:\n\n1. **Normal style**: The first argument is the value to match, the second argument is the pattern to match against, and the third argument is the function to call if the pattern matches.\n2. **Point-free style 1**: The only argument is the pattern to match against. The function returned by `ifMatch` can be called with the value to match and the function to call if the pattern matches.\n3. **Point-free style 2**: The first argument is the pattern to match against, and the second argument is the function to call if the pattern matches. The function returned by `ifMatch` can be called with the value to match.\n\nLike `match`, `ifMatch` also allows specifying the return type or input type of the function using TypeScript generics.\n\n```typescript\nifMatch(value, pattern, fn);\nifMatch\u003cReturnType\u003e()(value, pattern, fn);\nifMatch\u003cReturnType, InputType\u003e()(value, pattern, fn);\n\nconst ifError = ifMatch(\"{ type: 'error' }\");\nconst ifError = ifMatch\u003cvoid\u003e()(\"{ type: 'error' }\");\nconst ifError = ifMatch\u003cvoid, MyType\u003e()(\"{ type: 'error' }\");\n\nifError(value, (...args) =\u003e {\n  // Do something with the matched value\n});\n\nconst logIfError = ifMatch(\"{ type: 'error' }\", console.log);\nconst logIfError = ifMatch\u003cvoid\u003e()(\"{ type: 'error' }\", console.log);\nconst logIfError = ifMatch\u003cvoid, MyType\u003e()(\"{ type: 'error' }\", console.log);\n\nlogIfError(value);\n```\n\n## Patterns\n\n### Wildcards (`*`/spread wildcard/typed wildcards)\n\n`*` matches any JavaScript value, including `null` and `undefined`:\n\n```typescript\nconst result = match(value, {\n  \"*\": (v) =\u003e \"any value\",\n});\n```\n\n**Don’t confuse `*` with `_`!** The former matches any value but does not turn it into an argument, while the latter matches any value and turns it into an argument. In many other languages, `_` is used as a wildcard, but in megamatch, it is used to destructure the matched value.\n\nIn arrays, `...*` or just `...` matches zero or more elements:\n\n```typescript\nconst result = match(value, {\n  \"[]\": (v) =\u003e \"an empty array\",\n  \"[42, ...*]\": (v) =\u003e \"an array with the first element 42 and any number of elements after it\",\n  \"['a', ..., 'b', 'c']\": (v) =\u003e\n    \"an array with 'a' at the beginning, 'b' and 'c' at the end, and any number of elements in between\",\n});\n```\n\nNote that spread wildcards can only occur _once_ in an array pattern.\n\nSome keywords are reserved as “typed wildcards” and are not recognized as named arguments. They can be used to match specific types:\n\n```typescript\nconst result = match(value, {\n  \"{ key: string }\": (v) =\u003e \"object with `key` property of type string\",\n  \"[number]\": (v) =\u003e \"a tuple with only one number\",\n  boolean: (v) =\u003e \"a boolean value\",\n  symbol: (v) =\u003e \"a symbol value\",\n  bigint: (v) =\u003e \"a bigint value\",\n  function: (v) =\u003e \"a function value\",\n  nonNullable: (v) =\u003e \"any value except null and undefined\",\n  object: (v) =\u003e\n    \"an object value (v !== null \u0026\u0026 (typeof v === 'object' || typeof v === 'function'))\",\n  Date: (v) =\u003e \"a Date object\",\n  RegExp: (v) =\u003e \"a RegExp object\",\n  Error: (v) =\u003e \"an Error object\",\n  Array: (v) =\u003e \"an array\",\n  Map: (v) =\u003e \"a Map object\",\n  Set: (v) =\u003e \"a Set object\",\n  WeakMap: (v) =\u003e \"a WeakMap object\",\n  WeakSet: (v) =\u003e \"a WeakSet object\",\n  Promise: (v) =\u003e \"a Promise object\",\n  TypedArray: (v) =\u003e \"a TypedArray object\",\n  ArrayBuffer: (v) =\u003e \"an ArrayBuffer object\",\n  DataView: (v) =\u003e \"a DataView object\",\n});\n```\n\n### Unnamed arguments (`_`/`..._`)\n\n`_` matches any JavaScript value and turns it into an unnamed argument. It can be used in any pattern except [“Or” pattern](#or-patterns-), including arrays and objects:\n\n```typescript\nconst result = match(value, {\n  \"[_, '+', _]\": (left, right) =\u003e\n    \"select the first and third elements of a 3-element array with '+' in the middle\",\n  \"{ foo: _, bar: _ }\": (foo, bar) =\u003e \"select the `foo` and `bar` properties of an object\",\n});\n```\n\nSimilar to wildcards, `..._` matches zero or more elements in an array _or object_ and turns them into a single unnamed argument:\n\n```typescript\nconst result = match(value, {\n  \"[42, ..._, 'a']\": (middle) =\u003e\n    \"select the middle elements of an array starting with 42 and ending with 'a'\",\n  \"{ key: _, ..._ }\": (key, rest) =\u003e \"select the `key` property and the rest of the object\",\n});\n```\n\nIf you want to select an unnamed argument with some “constraints”, you can use it with the [Alias patterns](#alias-patterns-as) syntax:\n\n```typescript\nconst result = match(value, {\n  \"{ key: string as _, ..._ }\": (key, rest) =\u003e\n    \"select the `key` property that is a string and the rest of the object\",\n  \"['red' | 'green' | 'blue' as _, _]\": (color, second) =\u003e\n    \"select the first element of a 2-element array that is one of 'red', 'green' or 'blue' and the second element\",\n});\n```\n\n### Named arguments\n\nNamed arguments are used to destructure the matched value and collect the results into an object parameter. They can be used in any pattern except in [\"Or\" patterns](#or-patterns-), including arrays and objects.\n\nNamed arguments _must not_ start with an uppercase letter, otherwise parsing will fail. Identifiers starting with an uppercase letter are reserved for future use.\n\n```typescript\nconst result = match(value, {\n  \"{ key: value, ...rest }\": ({ value, rest }) =\u003e\n    \"select the `key` property as `value` and the rest of the object\",\n  \"[head, ...tail]\": ({ head, tail }) =\u003e \"select the first element and the rest of the array\",\n});\n```\n\nSimilar to wildcards and unnamed patterns, `...name` matches zero or more elements in an array or object and turns them into a single named argument, which is shown above.\n\nIn object patterns, you can use shorthand properties to match the value of the property with the same name as the argument:\n\n```typescript\nconst result = match(value, {\n  \"{ foo, bar, baz: * }\": ({ foo, bar }) =\u003e \"select the `foo` and `bar` properties of the object\",\n  \"{ foo: { bar: { baz } } }\": ({ baz }) =\u003e \"select the `baz` property of the nested object\",\n});\n```\n\nSimilar to unnamed arguments, you can use named arguments with the [Alias patterns](#alias-patterns-as) syntax:\n\n```typescript\nconst result = match(value, {\n  \"{ key: string as value, ...rest }\": ({ value, rest }) =\u003e\n    \"select the `key` property that is a string and the rest of the object\",\n  \"['red' | 'green' | 'blue' as color, *]\": ({ color }) =\u003e\n    \"select the first element of a 2-element array that is one of 'red', 'green' or 'blue'\",\n});\n```\n\n### Or patterns (`|`)\n\nAn “or” pattern is an expression separated by `|` that matches if any of the sub-patterns match. You can use any pattern type as sub-patterns except unnamed and named arguments. Or patterns can be nested within other pattern types.\n\n```typescript\nconst result = match(value, {\n  \"'foo' | 'bar'\": (v) =\u003e \"a string that is either 'foo' or 'bar'\",\n  \"[1 | 2 | 3]\": (v) =\u003e \"a 1-element array with only 1, 2 or 3 as the element\",\n  \"{ _tag: 'None' } | { _tag: 'Some', value: string }\": (v) =\u003e\n    \"an object with `_tag` property that is either 'None', or 'Some' with a `value` property of type string\",\n});\n```\n\nThe priority of `|` is higher than `as`, so you can use it to match a value with an [alias](#alias-patterns-as):\n\n```typescript\nconst result = match(value, {\n  \"{ key: string | number as value }\": ({ value }) =\u003e\n    \"select the `key` property that is a string or number as `value`\",\n  \"[1 | 2 | 3 as _]\": (value) =\u003e\n    \"select the first element of a 1-element array that is either 1, 2 or 3\",\n});\n```\n\n### Alias patterns (`as`)\n\nAn alias pattern is an expression that ends with `as _` or `as name` that matches the value and turns it into an argument. It can be used in any pattern except spread wildcards and arguments.\n\n```typescript\nconst matchValue = match\u003cvoid, unknown\u003e()({\n  \"[_, [_, *] as _, *] as _\": (first, nested, second, whole) =\u003e {\n    console.log(first, nested, second, whole);\n  },\n  \"{ foo: { key: string | number as key } as inner } as outer\": ({ key, inner, outer }) =\u003e {\n    console.log(key, inner, outer);\n  },\n  _: () =\u003e {},\n});\n\nmatchValue([1, [2, 3], 4]); // 1 2 [2, 3] [1, [2, 3], 4]\nmatchValue({ foo: { key: 42 } }); // 42 { key: 42 } { foo: { key: 42 } }\n```\n\n### Literal patterns\n\nThe following literal patterns are supported:\n\n- **null:** Matches `null` value. Example: `null`.\n- **undefined:** Matches `undefined` value. Example: `undefined`.\n- **boolean:** Matches `true` or `false`. Example: `true`, `false`.\n- **string:** Matches a string literal with either single or double quotes. Common escape sequences are supported, including `\\n`, `\\r`, `\\t`, `\\\\`, etc. Example: `'foo'`, `\"bar\"`, `\"foo\\\\\\\"bar\"`, `\"hello\\\\nworld\"`.\n- **number:** Matches a decimal or float literal, scientific notation is _not_ supported. Example: `0`, `42`, `3.14`, `-2.50`.\n- **bigint:** Matches a bigint literal with `n` suffix. The same rules as `number` apply. Example: `0n`, `42n`, `-5n`.\n\n```typescript\nconst result = match(value, {\n  null: (v) =\u003e \"null value\",\n  undefined: (v) =\u003e \"undefined value\",\n  \"true | false\": (v) =\u003e \"a boolean value\",\n  '\\'foo\\' | \"bar\" | \"hello\\\\nworld\"': (v) =\u003e\n    \"a string that is either 'foo', 'bar' or 'hello\\nworld'\",\n  \"42 | -5.5 | 0\": (v) =\u003e \"a number that is either 42, -5.5 or 0\",\n  \"42n | -5n | 0n\": (v) =\u003e \"a bigint that is either 42n, -5n or 0n\",\n});\n```\n\n### Array patterns\n\nArray patterns are used to match arrays and can be nested within other pattern types.\n\n```typescript\ntype Expr =\n  | [number, \"+\", number]\n  | [number, \"-\", number]\n  | [number, \"*\", number]\n  | [\"-\", number];\n\nconst result = match(expr, {\n  \"[_, '+', _]\": (left, right) =\u003e left + right,\n  \"[_, '-', _]\": (left, right) =\u003e left - right,\n  \"[_, '*', _]\": (left, right) =\u003e left * right,\n  \"['-', _]\": (value) =\u003e -value,\n});\n```\n\nAs already mentioned in the [Wildcards](#wildcards-spread-wildcardtyped-wildcards), [Unnamed arguments](#unnamed-arguments-__) and [Named arguments](#named-arguments) sections, you can use spread patterns to match variadic elements in an array. Note that only _1_ spread pattern is allowed in an array pattern.\n\n```typescript\nconst result = match(value, {\n  \"[42, ...]\": () =\u003e \"an array with the first element 42 and any number of elements after it\",\n  \"[1, ..._, 'a']\": (middle) =\u003e \"an array starting with 1 and ending with 'a'\",\n  \"[head, ...tail]\": ({ head, tail }) =\u003e \"select the first element and the rest of the array\",\n});\n```\n\n### Object patterns\n\nObject patterns are used to match objects and can be nested within other pattern types.\n\nObject keys can be either a valid JavaScript identifier or a string literal. If the key is a string literal, it must be enclosed in single or double quotes.\n\n```typescript\ntype Expr =\n  | { type: \"add\"; left: number; right: number }\n  | { type: \"sub\"; left: number; right: number }\n  | { type: \"neg\"; value: number }\n  | { type: \"error\"; \"~source\": string };\n\nconst result = match\u003cnumber | string\u003e()(expr, {\n  \"{ type: 'add', left: _, right: _ }\": (left, right) =\u003e left + right,\n  \"{ type: 'sub', left: left, right: right }\": ({ left, right }) =\u003e left - right,\n  \"{ type: 'neg', value: _ }\": (value) =\u003e -value,\n  \"{ type: 'error', '~source': _ }\": (source) =\u003e `Error: ${source}`,\n});\n```\n\nAs already mentioned in the [Named arguments](#named-arguments) section, a shorthand property can be used to match the value of the property with the same name as the argument:\n\n```typescript\nconst result = match(expr, {\n  \"{ type: 'add', left, right }\": ({ left, right }) =\u003e left + right,\n  \"{ type: 'sub', left, right }\": ({ left, right }) =\u003e left - right,\n  \"{ type: 'neg', value }\": ({ value }) =\u003e -value,\n});\n```\n\nOptional keys can be matched with `?`. If the key exists, it will be matched against the pattern, otherwise it will be ignored. If an argument is selected, it will be `undefined` if the key does not exist.\n\n```typescript\nmatch(value, {\n  \"{ key?: string }\": () =\u003e \"object with an optional `key` property of type string\",\n  \"{ key?: number as _ }\": (key) =\u003e\n    \"object with an optional `key` property of type number and select it as an unnamed argument\",\n});\n```\n\nYou can also use the spread syntax mentioned in the [Unnamed arguments](#unnamed-arguments-__) and [Named arguments](#named-arguments) sections to match the rest of the object:\n\n```typescript\nconst result = match(value, {\n  \"{ key: value, ...rest }\": ({ value, rest }) =\u003e\n    \"select the `key` property as `value` and the rest of the object\",\n  \"{ foo: 'bar', ..._ }\": (rest) =\u003e \"select the rest of the object with `foo` property as 'bar'\",\n});\n```\n\n## Types\n\n### `Infer\u003cPattern\u003e`\n\n`Infer\u003cPattern\u003e` infers the type of value represented by a pattern.\n\n```typescript\nimport type { Infer } from \"megamatch\";\n\nconst userPattern = \"{ id: number, username: string, role: 'admin' | 'user' }\";\n\ntype Post = Infer\u003ctypeof userPattern\u003e;\n//   ^?: { readonly id: number; readonly username: string; readonly role: \"admin\" | \"user\"; }\n```\n\nThe utility accepts an optional second argument `Readonly` to control whether to infer the type as readonly or writable. By default, the inferred type is readonly (i.e., it defaults to `true`), but you can set it to `false` to infer the type as writable.\n\n```typescript\ntype PostWritable = Infer\u003ctypeof userPattern, false\u003e;\n//   ^?: { id: number; username: string; role: \"admin\" | \"user\"; }\n```\n\n### `Narrow\u003cT, Pattern\u003e`\n\n`Narrow\u003cT, Pattern\u003e` narrows the type of `T` to the type represented by the pattern.\n\n```typescript\nimport type { Narrow } from \"megamatch\";\n\ntype Data = { type: \"text\"; content: string } | { type: \"img\"; src: string };\ntype Result = { type: \"ok\"; data: Data } | { type?: \"error\" | \"fatal\"; message: string };\n\ntype Narrowed = Narrow\u003cResult, \"{ type: 'error' }\"\u003e;\n//   ^?: { type: \"error\"; message: string }\n```\n\nmegamatch already narrows the type of the input value in `match` and `ifMatch`, so you don’t need to use `Narrow` in most cases.\n\n### About type narrowing of the input value\n\nSome pattern matching libraries like [ts-pattern](https://github.com/gvergnaud/ts-pattern) perform type narrowing on each pattern one by one, which means that the type of the input value of the function is not only narrowed to the matched pattern, but also narrowed by previous patterns that occur \"before\" the matched pattern.\n\n```typescript\nimport { match, P } from \"ts-pattern\";\n\nconst color = \"blue\" as \"red\" | \"green\" | \"blue\" | number;\n\nmatch(color)\n  .with(\"red\", () =\u003e \"red\")\n  .with(P.string, (v) =\u003e {\n    //             ^?: \"green\" | \"blue\"\n    // ts-pattern knows that `v` cannot be `\"red\"`,\n    // so it is narrowed to `\"green\" | \"blue\"`\n    return v;\n  })\n  .otherwise((v) =\u003e {\n    //        ^?: number\n    return \"unknown\";\n  });\n```\n\nHowever, this is not the case in megamatch. TypeScript does not preserve the order of object keys on the type level, so we cannot find a reliable way to narrow the type by the order of the patterns. Instead, megamatch performs type narrowing on each pattern independently.\n\nHowever, we do some special treatment to `*` and `_` patterns, assuming they occur at the end of the object. In such cases, we narrow the type of the input value of the function based on all other patterns that occur in the object. Such narrowing behavior only applies to exactly `*` and `_` patterns, not even `[*]` or `{ ..._ }`.\n\n```typescript\nimport { match } from \"megamatch\";\n\nconst color = \"blue\" as \"red\" | \"green\" | \"blue\" | number;\n\nmatch(color, {\n  \"'red'\": () =\u003e \"red\",\n  string: (v) =\u003e {\n    //     ^?: \"red\" | \"green\" | \"blue\"\n    return v;\n  },\n  _: (v) =\u003e {\n    // ^?: number\n    return \"unknown\";\n  },\n});\n```\n\nThis can be seen as a trade-off for much cleaner pattern matching syntax, and should not be a problem in most cases.\n\n## FAQ\n\n### What’s the magic behind the scenes?\n\nInternally, megamatch implements a tiny [parser combinator](https://en.wikipedia.org/wiki/Parser_combinator) system that parses the patterns at both runtime and type-level, where [hkt-core](https://github.com/Snowflyt/hkt-core) is used to provide type-level functions. We then implement the pattern DSL using these parser combinators at both levels to generate AST with the same structure, aligning the implementation as closely as possible.\n\nAfter parsers generate AST, a checker at both levels checks the validity of the patterns, which captures common mistakes like mixing unnamed and named arguments.\n\nIf the pattern is valid, we match the value against the pattern. At runtime, we match the value against the pattern using a recursive function that traverses the AST and checks if the value matches the pattern. If it does, we call the corresponding function with the matched values. At type-level, we use a recursive type that traverses the AST and narrows the type of the matched value and infers the type of the arguments.\n\n## Inspirations\n\nThis library is inspired by the following libraries:\n\n- [ts-pattern](https://github.com/gvergnaud/ts-pattern), which made me aware that it is possible to implement pattern matching in TypeScript. The exhaustiveness check implementation in megamatch heavily refers to the implementation in ts-pattern.\n- [Arktype](https://github.com/arktypeio/arktype), which is a runtime type validator that resembles TypeScript's type syntax in defining types. This made me realize that crazy type-level programming can be practical and even performant in TypeScript.\n- [ParseBox](https://github.com/sinclairzx81/parsebox) which powers the `Syntax` API in [TypeBox](https://github.com/sinclairzx81/typebox#syntax), another runtime type validator. This library provides parser combinators on both runtime and type-level. Though megamatch does not refer to its implementation, it somehow encourages me to implement this library.\n- [Megaparsec](https://github.com/mrkkrp/megaparsec), a Haskell library for parsing combinators. The API design of the parser combinators used internally in megamatch is heavily inspired by this library. As you can see here, it also inspired the name of this library. :)\n\nThe pattern matching DSL is inspired by the pattern matching syntax in many other languages, including [Haskell](https://www.haskell.org/), [Scala](https://scala-lang.org/), [Rust](https://www.rust-lang.org/) and [MoonBit](https://www.moonbitlang.com/).\n\n## License\n\nThis project is licensed under the Mozilla Public License Version 2.0 (MPL 2.0).\nFor details, please refer to the `LICENSE` file.\n\nIn addition to the open-source license, a commercial license is available for proprietary use.\nIf you modify this library and do not wish to open-source your modifications, or if you wish to use the modified library as part of a closed-source or proprietary project, you must obtain a commercial license.\n\nFor details, see `COMMERCIAL_LICENSE.md`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowflyt%2Fmegamatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnowflyt%2Fmegamatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowflyt%2Fmegamatch/lists"}