{"version":3,"file":"isIncludedIn.cjs","names":[],"sources":["../src/isIncludedIn.ts"],"sourcesContent":["import type { IsLiteral, IsUnion } from \"type-fest\";\nimport type { IterableContainer } from \"./internal/types/IterableContainer\";\n\n/**\n * A \"constant\" tuple is a type that has a single runtime value that can fulfil\n * it. This means that it doesn't have any variadic/rest/spread/array parts, and\n * that all it's values are singular (non-union) literals.\n *\n * We use this type to allow narrowing when checking against a set of values\n * defined as a const.\n *\n * @example\n *   type T1 = IsConstantTuple<[\"cat\", \"dog\", 3, true]>; // => true;\n *   type T2 = IsConstantTuple<[\"cat\" | \"dog\"]>; // false;\n *   type T2 = IsConstantTuple<[\"cat\", ...\"cat\"[]]>; // false;\n */\ntype IsConstantTuple<T extends IterableContainer> = T extends readonly []\n  ? true\n  : T extends readonly [infer Head, ...infer Rest]\n    ? IsUnion<Head> extends true\n      ? false\n      : IsConstantTuple<Rest>\n    : false;\n\n/**\n * There is no way to tell Typescript to only narrow the \"accepted\" side of a\n * type-predicate and so in many cases the negated side is also affected, this\n * results in over-narrowing in many cases, breaking typing. For this reason we\n * only want to use the type-predicate variant of `isIncludedIn` when we can\n * assume the result represents the expected types (closely enough). This is not\n * and ideal solution and we will still generate wrong types in some cases (see\n * tests), but it reduces the surface of this problem significantly, while still\n * keeping the utility of `isIncludedIn` for the common cases.\n *\n * TL;DR - The types are narrowable when: T is literal and S is a pure tuple, or\n * when T isn't a literal, but S is.\n *\n * @example\n *   const data = 1 as 1 | 2 | 3;\n *   const container = [] as (1 | 2)[];\n *   if (isIncludedIn(data, container)) {\n *     ... it makes sense to narrow data to `1 | 2` as the value `3` is not part\n *     ... of the typing of container, so will never result in being true.\n *   } else {\n *     ... but it doesn't make sense to narrow the value to 3 here, because 1\n *     ... and 2 are still valid values for data, when container doesn't include\n *     ... them **at runtime**.\n *     ... Typescript narrows the _rejected_ branch based on how it narrowed the\n *     ... _accepted_ clause, and we can't control that; because our input type\n *     ... is `1 | 2 | 3` and the accepted side is `1 | 2`, the rejected side is\n *     ... typed `Exclude<1 | 2 | 3, 1 | 2>`, which is `3`.\n *   }\n * }\n */\ntype IsNarrowable<T, S extends IterableContainer<T>> =\n  IsLiteral<T> extends true\n    ? // When T is literal (i.g. it isn't a primitive type like `string` or\n      // `number`) then the criteria for narrowing is that the container is a\n      // \"pure\" tuple because we *assume* that S represents a constant set of\n      // values, and that it's typing also represents it's runtime content 1-\n      // for-1. If S isn't a pure tuple it means we can't tell from the typing\n      // which of it's values are actually present in runtime so can't use them\n      // to narrow correctly.\n      IsConstantTuple<S>\n    : // When T isn't a literal type but the items in S are we can narrow the\n      // type because it won't affect the negated side (`Exclude<number, 3>`\n      // is still `number`).\n      IsLiteral<S[number]>;\n\n/**\n * Checks if the item is included in the container. This is a wrapper around\n * `Array.prototype.includes` and `Set.prototype.has` and thus relies on the\n * same equality checks that those functions do (which is reference equality,\n * e.g. `===`). In some cases the input's type is also narrowed to the\n * container's item types.\n *\n * Notice that unlike most functions, this function takes a generic item as it's\n * data and **an array** as it's parameter.\n *\n * @param data - The item that is checked.\n * @param container - The items that are checked against.\n * @returns `true` if the item is in the container, or `false` otherwise. In\n * cases the type of `data` is also narrowed down.\n * @signature\n *   isIncludedIn(data, container);\n * @example\n *   isIncludedIn(2, [1, 2, 3]); // => true\n *   isIncludedIn(4, [1, 2, 3]); // => false\n *\n *   const data = \"cat\" as \"cat\" | \"dog\" | \"mouse\";\n *   isIncludedIn(data, [\"cat\", \"dog\"] as const); // true (typed \"cat\" | \"dog\");\n * @dataFirst\n * @category Guard\n */\nexport function isIncludedIn<T, S extends IterableContainer<T>>(\n  data: T,\n  container: IsNarrowable<T, S> extends true ? S : never,\n): data is S[number];\nexport function isIncludedIn<T, S extends T>(\n  data: T,\n  container: IterableContainer<S>,\n): boolean;\n\n/**\n * Checks if the item is included in the container. This is a wrapper around\n * `Array.prototype.includes` and `Set.prototype.has` and thus relies on the\n * same equality checks that those functions do (which is reference equality,\n * e.g. `===`). In some cases the input's type is also narrowed to the\n * container's item types.\n *\n * Notice that unlike most functions, this function takes a generic item as it's\n * data and **an array** as it's parameter.\n *\n * @param container - The items that are checked against.\n * @returns `true` if the item is in the container, or `false` otherwise. In\n * cases the type of `data` is also narrowed down.\n * @signature\n *   isIncludedIn(container)(data);\n * @example\n *   pipe(2, isIncludedIn([1, 2, 3])); // => true\n *   pipe(4, isIncludedIn([1, 2, 3])); // => false\n *\n *   const data = \"cat\" as \"cat\" | \"dog\" | \"mouse\";\n *   pipe(\n *     data,\n *     isIncludedIn([\"cat\", \"dog\"] as const),\n *   ); // => true (typed \"cat\" | \"dog\");\n * @dataLast\n * @category Guard\n */\nexport function isIncludedIn<T, S extends IterableContainer<T>>(\n  container: IsNarrowable<T, S> extends true ? S : never,\n): (data: T) => data is S[number];\nexport function isIncludedIn<T, S extends T>(\n  container: IterableContainer<S>,\n): (data: T) => boolean;\n\nexport function isIncludedIn(\n  dataOrContainer: unknown,\n  container?: readonly unknown[],\n): unknown {\n  if (container === undefined) {\n    // === dataLast ===\n    // We don't use purry here because we can optimize the dataLast case by\n    // memoizing a set and accessing it in O(1) time instead of scanning the\n    // array **each time** (O(n)) each time.\n    const asSet = new Set(dataOrContainer as readonly unknown[]);\n    return (data: unknown) => asSet.has(data);\n  }\n\n  // === dataFirst ===\n  return container.includes(dataOrContainer);\n}\n"],"mappings":"mEAyIA,SAAgB,EACd,EACA,EACS,CACT,GAAI,IAAc,IAAA,GAAW,CAK3B,IAAM,EAAQ,IAAI,IAAI,EAAsC,CAC5D,MAAQ,IAAkB,EAAM,IAAI,EAAK,CAI3C,OAAO,EAAU,SAAS,EAAgB"}