import { apiFetch, apiPath, type ApiQueryParams } from "../api/client"; import { fetchPlatformModules } from "../api/platform"; import { filterSearchableSelectOptions } from "../components/SearchableSelect"; import { unavailableReferenceOption, type ReferenceOption, type ReferenceOptionProvider } from "../components/ReferenceSelect"; import type { ApiSettings } from "../types"; type ApiReferenceOptionPayload = { value: string; label: string; description?: string | null; kind?: string | null; availability?: ReferenceOption["availability"]; disabled?: boolean; source_module?: string | null; provenance?: Record; }; export function apiReferenceOptionProvider( settings: ApiSettings, path: string, params: ApiQueryParams = {} ): ReferenceOptionProvider { async function load( query: string, selectedValues: readonly string[], limit: number, signal: AbortSignal ): Promise { const response = await apiFetch<{ options: ApiReferenceOptionPayload[]; provider_available: boolean; }>( settings, apiPath(path, { ...params, q: query, selected: selectedValues, limit }), { signal } ); return response.options.map(referenceOptionFromPayload); } return { search: (query, context) => load(query, context.selectedValues, context.limit, context.signal), async resolve(values, context) { const uniqueValues = [...new Set(values.map((value) => value.trim()).filter(Boolean))]; const chunks: string[][] = []; for (let index = 0; index < uniqueValues.length; index += 200) { chunks.push(uniqueValues.slice(index, index + 200)); } const pages = await Promise.all( chunks.map((chunk) => load("", chunk, Math.min(200, Math.max(1, chunk.length)), context.signal) ) ); const byValue = new Map( pages.flat().map((option) => [option.value, option]) ); return uniqueValues.map( (value) => byValue.get(value) ?? unavailableReferenceOption(value) ); } }; } export function platformModuleReferenceProvider( settings: ApiSettings, presentedOptions: readonly ReferenceOption[] = [] ): ReferenceOptionProvider { const presentedByValue = new Map( presentedOptions.map((option) => [option.value, option]) ); let cataloguePromise: Promise | null = null; async function catalogue(signal: AbortSignal): Promise { cataloguePromise ??= loadPlatformModuleOptions( settings, presentedOptions, presentedByValue ).catch((error: unknown) => { cataloguePromise = null; throw error; }); const options = await cataloguePromise; if (signal.aborted) throw abortError(); return options; } return { async search(query, context) { const options = await catalogue(context.signal); return filterSearchableSelectOptions( options, query, context.limit ) as ReferenceOption[]; }, async resolve(values, context) { const options = await catalogue(context.signal); const byValue = new Map( [...presentedOptions, ...options].map((option) => [ option.value, option ]) ); return values.map( (value) => byValue.get(value) ?? unavailableReferenceOption(value, "Unavailable module") ); } }; } async function loadPlatformModuleOptions( settings: ApiSettings, presentedOptions: readonly ReferenceOption[], presentedByValue: ReadonlyMap ): Promise { let modules; try { modules = (await fetchPlatformModules(settings)).modules; } catch (error) { if (presentedOptions.length) return [...presentedOptions]; throw error; } return modules .filter((module) => module.enabled) .map((module) => { const presented = presentedByValue.get(module.id); return { value: module.id, label: presented?.label ?? module.name ?? module.id, description: presented?.description ?? `${module.id} ยท version ${module.version}`, searchText: presented?.searchText ?? `${module.id} ${module.name} ${module.version}`, kind: "module", availability: presented?.availability ?? "available", disabled: presented?.disabled ?? false, sourceModule: "core", provenance: { version: module.version, activeRegistry: true, ...(presented?.provenance ?? {}) } } satisfies ReferenceOption; }); } function referenceOptionFromPayload( option: ApiReferenceOptionPayload ): ReferenceOption { return { value: option.value, label: option.label, description: option.description, kind: option.kind, availability: option.availability, disabled: option.disabled, sourceModule: option.source_module, provenance: option.provenance }; } function abortError(): DOMException { return new DOMException("The operation was aborted.", "AbortError"); }