import type { ActionConfig } from '../ActionConfig'; import type { PropertyConfig } from '../PropertyConfig'; export class ActionPropertySorter { private readonly readyIndex = new Set(); constructor(private readonly actionName: string, private config: ActionConfig) { } public sortPropertiesInConfig() { const { properties } = this.config; const propEntries = [...properties.entries()]; const sortedProperties: Array<[string, PropertyConfig]> = []; let limit = properties.size * 5; let prop = propEntries.shift(); while (prop) { limit -= 1; if (limit < 0) { throw new Error(`Action ${this.actionName}: Too complex or cyclic parameter dependencies.`); } const [name, conf] = prop; prop = propEntries.shift(); this.checkDependsProperties(name, conf); if (conf.depends.length === 0) { sortedProperties.unshift([name, conf]); this.readyIndex.add(name); } else if (this.readyAll(conf)) { sortedProperties.push([name, conf]); this.readyIndex.add(name); } else { propEntries.push([name, conf]); } } properties.clear(); sortedProperties.forEach(([key, propConf]) => { properties.set(key, propConf); }); } private checkDependsProperties(propName: string, prop: PropertyConfig) { const { depends } = prop; const { properties } = this.config; if (depends.includes(propName)) { throw new Error(`Action ${this.actionName}: Does the ${propName} field depend on itself?`); } depends.forEach((p) => { if (!properties.has(p)) { throw new Error(`Action ${this.actionName}: The ${propName} field depends on the unknown ${p} field.`); } }); } private readyAll(conf: PropertyConfig): boolean { for (let i = 0; i < conf.depends.length; i += 1) { const name = conf.depends[i]; if (!this.readyIndex.has(name)) { return false; } } return true; } }