{"version":3,"file":"startsWith.cjs","names":["purry"],"sources":["../src/startsWith.ts"],"sourcesContent":["/* eslint-disable unicorn/consistent-boolean-name --\n * When we mirror a built-in function we use the same name for it.\n */\n\nimport type {\n  IsEqual,\n  IsNever,\n  IsStringLiteral,\n  UnionToIntersection,\n} from \"type-fest\";\nimport type { Boxed } from \"./internal/types/Boxed\";\nimport type { RemedaTypeError } from \"./internal/types/RemedaTypeError\";\nimport { purry } from \"./purry\";\n\n// By intersecting with a prefix template we force all types that satisfy this\n// type to also be of this shape. For a raw primitive string this narrows\n// exactly to the prefix template, for a literal TypeScript checks if it\n// satisfies the condition and narrow to `never` if not (and distribute the\n// check for unions). The only limitation is for unbounded template literals, as\n// TypeScript leaves the intersection as-is, even when they are disjoint.\ntype StartsWith<T, Prefix extends string> = T & `${Prefix}${string}`;\n\ntype IsDisjointPrefix<T extends string, Prefix extends string> =\n  // The tuple wrapping keeps the checks decidable while `T` or `Prefix` is an\n  // unresolved type parameter (a generic wrapper around the function):\n  // TypeScript probes a deferred conditional with a wildcard type, which bare\n  // `string extends T` resolves to, leaving the rejection branch a live\n  // candidate that no argument satisfies; `[string] extends [T]` resolves to\n  // `false` and rules it out.\n  [string] extends [Prefix]\n    ? // A primitive prefix could hold any value at runtime, so a check with it\n      // is never provably dead.\n      false\n    : [string] extends [T]\n      ? // A primitive string could hold any value at runtime, so a prefix is\n        // never provably dead for it.\n        false\n      : IsNever<StartsWith<T, Prefix>>;\n\n// The mirror image of `IsDisjointPrefix`: every value `T` could hold starts\n// with every value `Prefix` could hold, so the check is provably always `true`.\ntype IsGuaranteedPrefix<T extends string, Prefix extends string> = [T] extends [\n  StartsWithEvery<T, Prefix>,\n]\n  ? true\n  : false;\n\ntype DisjointPrefixError<Prefix extends string> = RemedaTypeError<\n  \"startsWith\",\n  \"This prefix doesn't match any of the inputs, the function will always return `false`\",\n  {\n    // A `string` base is already satisfied by any prefix argument, so the\n    // assignability failure is reported on the tag, which carries the\n    // message.\n    type: string;\n    metadata: Prefix;\n  }\n>;\n\n// The same intersection, but requiring *every* possible runtime value of the\n// prefix instead of any of them. Only one of them is the prefix at runtime, and\n// which one is unknowable, so a failed check can only rule out values that\n// would have matched no matter which one it was.\ntype StartsWithEvery<T, Prefix extends string> = T &\n  // 4. And then we intersect the prefixes instead of adding them to a union to\n  // flip the semantics from \"OR\" to \"AND\", so that the resulting prefix\n  // limitation is the tightest possible combination of all prefixes, and not\n  // the widest one, before unwrapping the box.\n  Boxed.Extract<\n    UnionToIntersection<\n      // 1. We first distribute the union to compute the prefix for each member\n      // of the union separately (otherwise the prefix itself would contain\n      // the union).\n      Prefix extends unknown\n        ? // 3. Each prefix is boxed so that it survives as a distinct union\n          // member until the intersection. Unboxed, a `never` would vanish from\n          // the union instead of emptying the intersection, and an empty\n          // prefix's `string` would absorb its siblings via subtype reduction.\n          Boxed<\n            // 2. Unbounded template strings represent infinite possible\n            // prefixes, which is exactly the kind of uncertainty that we are\n            // working to resolve here, only literals are workable here.\n            IsStringLiteral<Prefix> extends true ? `${Prefix}${string}` : never\n          >\n        : never\n    >\n  >;\n\n// TypeScript treats type-guards as complementary (e.g., everything either\n// fully satisfies the type, or fully doesn't, typing the falsy branch similar\n// to the result of `Exclude<T, Condition>`). `startsWith` doesn't have this\n// relationship when `Prefix` is a union because we don't **know** which of the\n// union members match, so we can't narrow the falsy branch at all. The only way\n// to prevent this is to prevent TypeScript from using the narrowing overload\n// in cases where we know the narrowing wouldn't be sound.\ntype IsNarrowingUnsound<T, Prefix extends string> = IsEqual<\n  // We simulate the falsy branch using the actual narrowing type we use and\n  // the type created by narrowing via *all* union members together.\n  IsEqual<\n    Exclude<T, StartsWith<T, Prefix>>,\n    Exclude<T, StartsWithEvery<T, Prefix>>\n  >,\n  // We want to find the cases where they don't agree, this means that narrowing\n  // would result in an unsound overly-narrow falsy branch.\n  false\n>;\n\n/**\n * **NOTE**: every possible value of `data` starts with every possible value of\n * `prefix` meaning the check can't fail; so the result is typed as a\n * **literal `true`**.\n *\n * @param data - The input string.\n * @param prefix - The string to check for at the beginning.\n * @hidden\n */\nexport function startsWith<T extends string, Prefix extends string>(\n  data: T,\n  // This signature has to come first because the narrowing overload accepts\n  // these inputs too, it would just narrow `data` to itself.\n  prefix: IsDisjointPrefix<T, Prefix> extends true\n    ? // Every data-first overload rejects a dead prefix so that no overload\n      // matches the call at all, which puts the error on the argument itself.\n      DisjointPrefixError<Prefix>\n    : IsGuaranteedPrefix<T, Prefix> extends true\n      ? Prefix\n      : never,\n): true;\n\n/**\n * Determines whether a string begins with the provided prefix, and refines the\n * output type if possible.\n *\n * This function is a wrapper around the built-in [`String.prototype.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\n * method, but doesn't expose the `position` parameter. To check from a specific\n * position, use `startsWith(sliceString(data, position), prefix)`.\n *\n * @param data - The input string.\n * @param prefix - The string to check for at the beginning.\n * @signature\n *   startsWith(data, prefix);\n * @example\n *   startsWith(\"hello world\", \"hello\"); //=> true\n *   startsWith(\"hello world\" as string, \"world\"); //=> false\n * @dataFirst\n * @category String\n */\nexport function startsWith<T extends string, Prefix extends string>(\n  data: T,\n  prefix: string extends Prefix\n    ? // Reject primitive strings, they can't be used to narrow T. They would\n      // match the non-narrowing overload.\n      never\n    : IsDisjointPrefix<T, Prefix> extends true\n      ? DisjointPrefixError<Prefix>\n      : IsNarrowingUnsound<T, Prefix> extends true\n        ? // Union prefixes are rejected too when the guard they'd produce isn't\n          // sound.\n          never\n        : Prefix,\n): data is StartsWith<T, Prefix>;\n\nexport function startsWith<T extends string, Prefix extends string>(\n  data: T,\n  prefix: IsDisjointPrefix<T, Prefix> extends true\n    ? // Without the disjoint check here too, a dead prefix rejected by the\n      // previous overload would fall through to this one and be accepted.\n      DisjointPrefixError<Prefix>\n    : Prefix,\n): boolean;\n\n/**\n * Determines whether a string begins with the provided prefix, and refines the\n * output type if possible.\n *\n * This function is a wrapper around the built-in [`String.prototype.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\n * method, but doesn't expose the `position` parameter. To check from a specific\n * position, use `startsWith(sliceString(data, position), prefix)`.\n *\n * @param prefix - The string to check for at the beginning.\n * @signature\n *   startsWith(prefix)(data);\n * @example\n *   pipe(\"hello world\", startsWith(\"hello\")); //=> true\n *   pipe(\"hello world\", startsWith(\"world\")); //=> false\n * @dataLast\n * @category String\n */\nexport function startsWith<T extends string, Prefix extends string>(\n  // This signature has to come first because the generic guard overload\n  // below accepts every literal prefix. Unlike the data-first overloads we\n  // can't fail the call on the prefix itself: an overload that rejects it\n  // just doesn't match, and the call falls through to the generic guard,\n  // which has no `T` to check it against. So the rejection is carried by the\n  // returned predicate instead, which rejects `data`.\n  prefix: IsDisjointPrefix<T, Prefix> extends true ? Prefix : never,\n): (data: T & DisjointPrefixError<Prefix>) => boolean;\n\nexport function startsWith<T extends string, Prefix extends string>(\n  // Like the rejection overload, `T` is inferred from the contextual type of\n  // the returned predicate; without one it falls back to `string`, which only\n  // an empty prefix is guaranteed for.\n  prefix: IsGuaranteedPrefix<T, Prefix> extends true ? Prefix : never,\n): (data: T) => true;\n\nexport function startsWith<T extends string, Prefix extends string>(\n  // In the narrowing data-last overload we move the type of `data` to the\n  // returned callback so that it could defer the inference to the wrapper,\n  // allowing it to support complex compositions (e.g., `isNot`); but our\n  // soundness check requires the `data` type so it could compare against it.\n  // To work around this we need an additional overload that would only match\n  // the unsound cases. If the inputs are sound, it wouldn't match and allow us\n  // to fall through to the next overload.\n  prefix: IsNarrowingUnsound<T, Prefix> extends true ? Prefix : never,\n): (data: T) => boolean;\n\nexport function startsWith<Prefix extends string>(\n  // Reject primitive strings, they can't be used to narrow T. They would match\n  // the non-narrowing overload.\n  prefix: string extends Prefix ? never : Prefix,\n): <T extends string>(data: T) => data is StartsWith<T, Prefix>;\n\nexport function startsWith(prefix: string): (data: string) => boolean;\n\nexport function startsWith(...args: readonly unknown[]): unknown {\n  return purry(startsWithImplementation, args);\n}\n\nconst startsWithImplementation = (data: string, prefix: string): boolean =>\n  data.startsWith(prefix);\n"],"mappings":"kGAgOA,SAAgB,EAAW,GAAG,EAAmC,CAC/D,OAAOA,EAAAA,MAAM,EAA0B,CAAI,CAC7C,CAEA,MAAM,GAA4B,EAAc,IAC9C,EAAK,WAAW,CAAM"}