/** * Capability Lookup * * Zone-aware capability provider lookup. Supports both zone-scoped * capabilities (like firewall) and zone-agnostic ones (like dns_registrar). */ import { eq } from 'drizzle-orm'; import type { DbClient } from '../db/client'; import { capabilities } from '../db/schema'; export interface CapabilityProviderInfo { id: number; moduleId: string; capabilityName: string; version: string; data: Record; zones: string[] | null; } type CapabilityProviderRow = { id: number; moduleId: string; capabilityName: string; version: string; data: Record; zones: string[] | null; }; /** * Select a provider from an already-loaded candidate set. * * Keeping this policy separate from the database query lets runtime consumers * that already loaded every provider (notably the hook capability loader) use * exactly the same explicit-zone-first semantics as direct lookups. */ export function selectCapabilityProvider( all: CapabilityProviderRow[], zone?: string, ): CapabilityProviderInfo | null { if (all.length === 0) return null; if (zone) { const zoneMatch = all.find((candidate) => candidate.zones?.includes(zone)); if (zoneMatch) return toInfo(zoneMatch); const agnostic = all.find( (candidate) => candidate.zones === null || candidate.zones === undefined, ); if (agnostic) return toInfo(agnostic); return null; } return toInfo(all[0]); } /** * Find a capability provider, optionally filtered by zone. * * - If zone is provided: prefer providers whose zones include that zone. * Falls back to zone-agnostic providers (zones is null). * - If zone is not provided: return the first matching provider. * * @param name - Capability name (e.g., "firewall", "dns_registrar") * @param zone - Optional zone filter (e.g., "dmz", "internal") * @param db - Database client */ export function findCapabilityProvider( name: string, db: DbClient, zone?: string, ): CapabilityProviderInfo | null { const all = db.select().from(capabilities).where(eq(capabilities.capabilityName, name)).all(); return selectCapabilityProvider(all, zone); } /** * Find all providers of a capability (across all zones) */ export function findAllCapabilityProviders(name: string, db: DbClient): CapabilityProviderInfo[] { return db .select() .from(capabilities) .where(eq(capabilities.capabilityName, name)) .all() .map(toInfo); } function toInfo(row: CapabilityProviderRow): CapabilityProviderInfo { return { id: row.id, moduleId: row.moduleId, capabilityName: row.capabilityName, version: row.version, data: row.data as Record, zones: row.zones as string[] | null, }; }