import { authorizeHousekeeping } from "../../../foundation/authorization"; import { fail, type HousekeepingQuery, ok, } from "../../../foundation/contracts"; import { isSafeHousekeepingHref } from "../../../foundation/housekeeping-href"; import { type EconomyRouteGroup, type EconomyRouteId, economyRouteById, economyRouteGroup, } from "../routes"; export interface EconomyQueryInput { readonly routeId: EconomyRouteId; readonly params?: Readonly>; readonly list?: Readonly<{ search?: string; pageSize?: number; offset?: number; }>; } export interface EconomyQueryItem { readonly id: string; readonly title: string; readonly status?: string; readonly amount?: string; readonly href?: string; readonly description?: string; readonly updatedAt?: string | null; } export interface EconomyQueryData { readonly kind: EconomyRouteGroup; readonly items: readonly EconomyQueryItem[]; readonly total: number; readonly partialDependencies: readonly string[]; } export interface EconomyQueryAdapters { load(input: NormalizedEconomyQueryInput): Promise; } export interface NormalizedEconomyQueryInput { readonly routeId: EconomyRouteId; readonly params: Readonly>; readonly list: Readonly<{ search: string; pageSize: number; offset: number }>; } function boundedInteger( value: unknown, fallback: number, minimum: number, maximum: number, ): number { const parsed = Number(value); if (!Number.isSafeInteger(parsed)) return fallback; return Math.min(maximum, Math.max(minimum, parsed)); } function normalizeInput(input: EconomyQueryInput): NormalizedEconomyQueryInput { return { routeId: input.routeId, params: Object.fromEntries( Object.entries(input.params ?? {}).map(([key, value]) => [ key.normalize("NFC").trim().slice(0, 128), value.normalize("NFC").trim().slice(0, 128), ]), ), list: { search: String(input.list?.search ?? "") .normalize("NFC") .trim() .slice(0, 128), pageSize: boundedInteger(input.list?.pageSize, 25, 1, 100), offset: boundedInteger(input.list?.offset, 0, 0, 100_000), }, }; } function isValidData( data: EconomyQueryData, input: NormalizedEconomyQueryInput, ): boolean { if ( data.kind !== economyRouteGroup(input.routeId) || !Array.isArray(data.items) || !Number.isSafeInteger(data.total) || data.total < 0 || !Array.isArray(data.partialDependencies) || data.partialDependencies.length !== 0 ) { return false; } return data.items.every( (item) => typeof item.id === "string" && item.id.length > 0 && typeof item.title === "string" && item.title.length > 0 && (item.href === undefined || isSafeHousekeepingHref(item.href)), ); } export function createEconomyQuery( adapters: EconomyQueryAdapters, ): HousekeepingQuery { return { id: "economy.query", owner: "economy", capability: economyRouteById("economy.catalog.overview").capability, async run(context, rawInput) { const route = economyRouteById(rawInput.routeId); const authorization = authorizeHousekeeping(context, route.capability); if (!authorization.ok) return authorization; const input = normalizeInput(rawInput); try { const data = await adapters.load(input); if (!isValidData(data, input)) throw new Error("invalid Economy query"); return ok(data, authorization.correlationId); } catch { return fail( "DEPENDENCY_UNAVAILABLE", "errors.housekeeping.dependencyUnavailable", authorization.correlationId, ); } }, }; } export async function loadEconomyCatalogQuery( input: NormalizedEconomyQueryInput, ): Promise { const { loadEconomyDatabaseQuery } = await import("./economy-production"); return loadEconomyDatabaseQuery(input); } export const economyQuery = createEconomyQuery({ async load(input) { const group = economyRouteGroup(input.routeId); if (group === "catalog" || group === "items") { return loadEconomyCatalogQuery(input); } if (group === "commerce" || group === "history") { const { loadEconomyCommerceQuery } = await import("./commerce"); return loadEconomyCommerceQuery(input); } const { loadEconomyValueQuery } = await import("./value"); return loadEconomyValueQuery(input); }, });