(schema: TableSchema, columnOrder: string[] | undefined)
| 823 | * etc. Returns a list of human-readable errors (empty if valid). |
| 824 | */ |
| 825 | export function validateSchema(schema: TableSchema, columnOrder: string[] | undefined): string[] { |
| 826 | const errors: string[] = [] |
| 827 | // Group refs and columnOrder hold stable column ids (not display names). |
| 828 | const columnsById = new Map(schema.columns.map((c) => [getColumnId(c), c])) |
| 829 | const groups = schema.workflowGroups ?? [] |
| 830 | const groupsById = new Map(groups.map((g) => [g.id, g])) |
| 831 | |
| 832 | // Reference integrity for group outputs. |
| 833 | const claimedColumns = new Map<string, string>() // columnId → groupId |
| 834 | for (const group of groups) { |
| 835 | if (group.outputs.length === 0) { |
| 836 | errors.push(`Workflow group "${group.name ?? group.id}" has no outputs.`) |
| 837 | } |
| 838 | for (const out of group.outputs) { |
| 839 | const col = columnsById.get(out.columnName) |
| 840 | if (!col) { |
| 841 | errors.push( |
| 842 | `Workflow group "${group.name ?? group.id}" references missing column "${out.columnName}".` |
| 843 | ) |
| 844 | continue |
| 845 | } |
| 846 | if (col.workflowGroupId !== group.id) { |
| 847 | errors.push( |
| 848 | `Column "${col.name}" is referenced by group "${group.id}" but its workflowGroupId is "${col.workflowGroupId ?? '(unset)'}".` |
| 849 | ) |
| 850 | } |
| 851 | const claimer = claimedColumns.get(out.columnName) |
| 852 | if (claimer && claimer !== group.id) { |
| 853 | errors.push( |
| 854 | `Column "${out.columnName}" is claimed by both groups "${claimer}" and "${group.id}".` |
| 855 | ) |
| 856 | } else { |
| 857 | claimedColumns.set(out.columnName, group.id) |
| 858 | } |
| 859 | } |
| 860 | } |
| 861 | |
| 862 | // Every column flagged with a workflowGroupId must appear in exactly one group's outputs. |
| 863 | for (const col of schema.columns) { |
| 864 | if (!col.workflowGroupId) continue |
| 865 | if (!groupsById.has(col.workflowGroupId)) { |
| 866 | errors.push( |
| 867 | `Column "${col.name}" references missing workflow group "${col.workflowGroupId}".` |
| 868 | ) |
| 869 | continue |
| 870 | } |
| 871 | if (claimedColumns.get(getColumnId(col)) !== col.workflowGroupId) { |
| 872 | errors.push( |
| 873 | `Column "${col.name}" has workflowGroupId "${col.workflowGroupId}" but isn't in that group's outputs.` |
| 874 | ) |
| 875 | } |
| 876 | if (col.required) { |
| 877 | errors.push(`Workflow-output column "${col.name}" cannot be required.`) |
| 878 | } |
| 879 | if (col.unique) { |
| 880 | errors.push(`Workflow-output column "${col.name}" cannot be unique.`) |
| 881 | } |
| 882 | } |
no test coverage detected