```ts
/**
 * Determines if the needle occurs inside the haystack as an in-order match of all of its characters,
 * where between one matched character and the next at most gap unmatched characters may be skipped.
 * Any number of characters may come before the first matched character or after the last.
 * Returns true when such a match exists and false otherwise.
 * Rejects with an error if the needle is empty, the gap is negative, or not a whole number.
 *
 * @param needle - The substring to search for.
 * @param haystack - The string in which to search.
 * @param gap - The maximum number of unmatched characters between matched characters.
 * @returns true if a match exists, false otherwise.
 */
export function gappedMatch(needle: string, haystack: string, gap: number): boolean {
  if (needle === "") throw new Error("Needle cannot be empty");
  if (!Number.isInteger(gap) || gap < 0) throw new Error("Gap must be a non-negative integer");

  const needleLength = needle.length;
  let i = 0;

  for (let j = 0; j < haystack.length && i < needleLength; j++) {
    if (haystack[j] === needle[i]) {
      i++;
    } else if (j - i >= gap) {
      return false;
    }
  }

  return i === needleLength;
}
```