RegistryClient
class exported by @mwillbanks/tuil-registry.
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
| Member | Type | Required | Description | Related types |
|---|---|---|---|---|
__constructor | any | Yes | The __constructor member uses the any contract. | — |
get | (name: string, signal?: AbortSignal) => Promise<RegistryItem> | Yes | The get member uses the (name: string, signal?: AbortSignal) => Promise<RegistryItem> contract. | RegistryItem |
list | (signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> | Yes | The list member uses the (signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> contract. | RegistryIndexEntry |
search | (query: string, signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> | Yes | The search member uses the (query: string, signal?: AbortSignal) => Promise<readonly RegistryIndexEntry[]> contract. | RegistryIndexEntry |
resolvePlan | (names: readonly string[], signal?: AbortSignal) => Promise<readonly RegistryItem[]> | Yes | The 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.
Related types
Source
View the secondary source reference