import { PERMS } from "@/lib/permission-slugs"; import { authorizeHousekeeping } from "../../foundation/authorization"; import { satisfiesCapability } from "../../foundation/capability-context"; import { anyCapability, type CapabilityRequirement, fail, type HousekeepingCapabilityContext, type HousekeepingSearchProvider, ok, } from "../../foundation/contracts"; import { isSafeHousekeepingHref } from "../../foundation/housekeeping-href"; export const ECONOMY_SEARCH_PROVIDER_IDS = [ "economy.catalog-pages", "economy.items", "economy.transactions", ] as const; export interface EconomySearchCandidate { readonly id: string; readonly title: string; readonly description?: string; readonly href: string; readonly capability: CapabilityRequirement; } type EconomySearchLoader = ( context: HousekeepingCapabilityContext, term: string, limit: number, ) => Promise; export interface EconomySearchAdapters { readonly catalogPages: EconomySearchLoader; readonly items: EconomySearchLoader; readonly transactions: EconomySearchLoader; } function createProvider( id: (typeof ECONOMY_SEARCH_PROVIDER_IDS)[number], capability: CapabilityRequirement, load: EconomySearchLoader, ): HousekeepingSearchProvider { return { id, owner: "economy", capability, async search(context, input) { const authorization = authorizeHousekeeping(context, capability); if (!authorization.ok) return authorization; const limit = Number.isFinite(input.limit) ? Math.min(25, Math.max(1, Math.trunc(input.limit))) : 25; const term = input.term.normalize("NFC").trim().slice(0, 128); try { const candidates = await load(context, term, limit); return ok( candidates .filter( (candidate) => isSafeHousekeepingHref(candidate.href) && satisfiesCapability(context, candidate.capability), ) .slice(0, limit) .map((candidate) => ({ ...candidate, domain: "economy" as const, type: "entity" as const, href: candidate.href as `/ase/${string}`, })), authorization.correlationId, ); } catch { return fail( "DEPENDENCY_UNAVAILABLE", "errors.housekeeping.dependencyUnavailable", authorization.correlationId, ); } }, }; } export function createEconomySearchProviders( adapters: EconomySearchAdapters, ): readonly HousekeepingSearchProvider[] { return [ createProvider( "economy.catalog-pages", anyCapability(PERMS.CATALOG_VIEW), adapters.catalogPages, ), createProvider( "economy.items", anyCapability(PERMS.CATALOG_VIEW), adapters.items, ), createProvider( "economy.transactions", anyCapability(PERMS.SHOP_VIEW), adapters.transactions, ), ]; } export const ECONOMY_SEARCH_PROVIDERS = createEconomySearchProviders({ async catalogPages(context, term, limit) { const { loadEconomySearchCandidates } = await import("./search-production"); return loadEconomySearchCandidates("catalog-pages", context, term, limit); }, async items(context, term, limit) { const { loadEconomySearchCandidates } = await import("./search-production"); return loadEconomySearchCandidates("items", context, term, limit); }, async transactions(context, term, limit) { const { loadEconomySearchCandidates } = await import("./search-production"); return loadEconomySearchCandidates("transactions", context, term, limit); }, });