```ts
export function matchRoute(pattern: string, path: string): Record<string, string> | null {
  const segments = splitSegments(path);
  if (!segments) return null;

  const patternSegments = splitSegments(pattern);
  if (!patternSegments) return null;

  const captureNames = new Set<string>();
  const result: Record<string, string> = {};

  function matchPattern(index: number): boolean {
    if (index >= patternSegments.length) return segments.length === index;
    const segment = patternSegments[index];
    if (segment.startsWith(':')) {
      const name = segment.slice(1);
      if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error('Invalid capture name');
      if (captureNames.has(name)) throw new Error('Duplicate capture name');
      captureNames.add(name);
      result[name] = segments[index];
      return matchPattern(index + 1);
    } else if (segment === '*') {
      if (index !== patternSegments.length - 1) throw new Error('Invalid pattern');
      result['*'] = segments.slice(index).join('/');
      return true;
    } else if (segment === '**') {
      if (patternSegments.indexOf(segment) !== patternSegments.lastIndexOf(segment)) throw new Error('Duplicate ** in pattern');
      const captures: string[] = [];
      while (index < patternSegments.length && segment === '**') {
        captures.push(...segments.slice(index));
        index++;
      }
      result['**'] = captures.join('/');
      return matchPattern(index);
    } else if (segment !== segments[index]) throw new Error('Mismatched segment');
    return matchPattern(index + 1);
  }

  return matchPattern(0) ? result : null;
}

export function splitSegments(path: string): string[] {
  if (!path || typeof path !== 'string' || !/^\//.test(path)) throw new Error('Invalid path');

  const segments = path.split('/').filter(segment => segment.length > 0);
  if (segments[segments.length - 1] === '') throw new Error('Trailing slash in path');
  return segments;
}

export function firstRoute(patterns: string[], path: string): number {
  for (let i = 0; i < patterns.length; i++) {
    const pattern = patterns[i];
    if (!splitSegments(pattern)) continue;
    if (matchRoute(pattern, path) !== null) return i;
  }
  return -1;
}
```