tuil
ReferencePackages@mwillbanks/tuil-registryAPI

RegistryClient

class exported by @mwillbanks/tuil-registry.

View rawEdit

class

Public class exported by @mwillbanks/tuil-registry.

export class RegistryClient {
  constructor(readonly sources: readonly RegistrySource[]) {
    if (sources.length === 0) {
      throw new Error("Registry client requires at least one source");
    }
  }

  async get(name: string, signal?: AbortSignal): Promise<RegistryItem> {
    const qualified = name.match(/^@([^/]+)\/(.+)$/);
    const selectedSources = qualified
      ? this.sources.filter((source) => source.id === qualified[1])
      : this.sources;
    const itemName = qualified?.[2] ?? name;
    for (const source of selectedSources) {
      const item = await source.get(itemName, signal);
      if (item) {
        return Object.freeze({
          ...item,
          registryName: qualified ? name : item.name,
          sourceId: source.id,
        });
      }
    }
    throw new Error(`Registry item "${name}" was not found`);
  }

  async list(signal?: AbortSignal): Promise<readonly RegistryIndexEntry[]> {
    const results = await Promise.allSettled(
      this.sources.map((source) => source.list(signal)),
    );
    const entries = results
      .filter(
        (
          result,
        ): result is PromiseFulfilledResult<readonly RegistryIndexEntry[]> =>
          result.status === "fulfilled",
      )
      .map((result) => result.value);
    if (entries.length === 0) {
      throw new AggregateError(
        results
          .filter(
            (result): result is PromiseRejectedResult =>
              result.status === "rejected",
          )
          .map((result) => result.reason),
        "Every registry source failed",
      );
    }
    const unique = new Map<string, RegistryIndexEntry>();
    for (const entry of entries.flat()) {
      if (!unique.has(entry.name)) {
        unique.set(entry.name, entry);
      }
    }
    return [...unique.values()];
  }

  async search(
    query: string,
    signal?: AbortSignal,
  ): Promise<readonly RegistryIndexEntry[]> {
    const normalized = query.trim().toLowerCase();
    return (await this.list(signal)).filter((entry) =>
      `${entry.name} ${entry.title} ${entry.description}`
        .toLowerCase()
        .includes(normalized),
    );
  }

  async resolvePlan(
    names: readonly string[],
    signal?: AbortSignal,
  ): Promise<readonly RegistryItem[]> {
    const visiting = new Set<string>();
    const resolved = new Map<string, RegistryItem>();
    const visit = async (
      name: string,
      path: readonly string[],
    ): Promise<void> => {
      if (resolved.has(name)) return;
      if (visiting.has(name)) {
        throw new Error(
          `Registry dependency cycle: ${[...path, name].join(" → ")}`,
        );
      }
      visiting.add(name);
      const item = await this.get(name, signal);
      const source = name.match(/^@([^/]+)\//)?.[1];
      for (const dependency of item.registryDependencies ?? []) {
        const qualified =
          source && !dependency.startsWith("@")
            ? `@${source}/${dependency}`
            : dependency;
        await visit(qualified, [...path, name]);
      }
      visiting.delete(name);
      resolved.set(name, item);
    };
    for (const name of names) {
      await visit(name, []);
    }
    return [...resolved.values()];
  }
}

Members

MemberTypeRequiredDescriptionRelated types
__constructoranyYesThe __constructor member uses the any contract.
get(name: string, signal?: AbortSignal) => Promise<RegistryItem>YesThe get member uses the (name: string, signal?: AbortSignal) => Promise<RegistryItem> contract.RegistryItem
list(signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]>YesThe list member uses the (signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> contract.RegistryIndexEntry
search(query: string, signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]>YesThe search member uses the (query: string, signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> contract.RegistryIndexEntry
resolvePlan(names: readonly string[], signal?: AbortSignal) => Promise<readonly RegistryItem[]>YesThe resolvePlan member uses the (names: readonly string[], signal?: AbortSignal) => Promise<readonly RegistryItem[]> contract.RegistryItem

Parameters

This declaration has no public members.

Returns

This declaration does not return a value.

Throws

No thrown errors are documented for this declaration.

Source

View the secondary source reference

Package

@mwillbanks/tuil-registry

On this page