// Controller IDs map one to one onto SPA paths (D-10): // vendor.plugin.controller <-> /vendor/plugin/controller. export interface ControllerParams { vendor: string plugin: string controller: string } const SEGMENT = /^[A-Za-z0-9_-]+$/ export function parseControllerId(id: string): ControllerParams | null { const parts = id.split('.') if (parts.length !== 3 || !parts.every((part) => SEGMENT.test(part))) { return null } const [vendor, plugin, controller] = parts as [string, string, string] return { vendor, plugin, controller } } export function controllerPath(id: string): string | null { const params = parseControllerId(id) return params ? `/${params.vendor}/${params.plugin}/${params.controller}` : null } export function controllerIdFromParams(params: ControllerParams): string { return `${params.vendor}.${params.plugin}.${params.controller}` } export function controllerIdFromPath(path: string): string | null { const parts = path.replace(/^\/+|\/+$/g, '').split('/') if (parts.length < 3) { return null } const id = parts.slice(0, 3).join('.') return parseControllerId(id) ? id : null } /** vendor.plugin prefix of a controller ID, used to find the owning plugin. */ export function pluginKey(id: string): string | null { const params = parseControllerId(id) return params ? `${params.vendor}.${params.plugin}` : null }