💾 Archived View for iich.space › src › modules › mission-control › router.ts captured on 2022-03-01 at 16:04:57.

View Raw

More Information

⬅️ Previous capture (2022-01-08)

-=-=-=-=-=-=-

import { createLogger } from '@/log';

interface Route<T> {
  regex: RegExp;
  path: string;
  value: T;
}

interface Match<T> {
  value: T;
  params: Record<string, string>;
  regex: RegExp;
}

const log = createLogger();

const pathToRegex = (path: string): RegExp => {
  const substitutions: Array<[RegExp, string]> = [
    [/\/:(\w+)\?/g, '(?:/(?<$1>[\\w-%.=]+))?'],
    [/:(\w+)/g, '(?<$1>[\\w-%.=]+)'],
    [/\*/g, '(?<match>.*?)'],
    [/\/*/, '/'],
    [/\/$/, '/?'],
  ];

  const pattern = substitutions.reduce(
    (acc, [searchValue, replaceValue]) =>
      acc.replace(searchValue, replaceValue),
    path,
  );

  return new RegExp(`^${pattern}/?


gemini - kennedy.gemi.dev




);
};

export class Router<T> {
  routes: Array<Route<T>>;

  constructor() {
    this.routes = [];
  }

  use(path: string, ...values: Array<T | Router<T>>): void {
    const regex = pathToRegex(path);

    values.forEach((value) => {
      if (value instanceof Router) {
        log.debug(`nested ${path}`);

        value.routes.forEach((route) => {
          this.use(path + route.path, route.value);
        });
      } else {
        log.debug(`registering ${path}`);

        this.routes.push({
          path: path.replaceAll(/\/+/g, '/'),
          regex,
          value,
        });
      }
    });
  }

  *match(path: string): Generator<Match<T>> {
    log.debug(`matching ${path}`);

    for (const { regex, value } of this.routes) {
      const exec = regex.exec(path);

      if (exec !== null) {
        log.debug(`matched ${regex}`);

        const match: Match<T> = {
          params: exec.groups || {},
          regex,
          value,
        };

        yield match;
      }
    }
  }
}