( tableId: string, metadata: TableMetadata, existingMetadata?: TableMetadata | null )
| 537 | * @returns Updated metadata |
| 538 | */ |
| 539 | export async function updateTableMetadata( |
| 540 | tableId: string, |
| 541 | metadata: TableMetadata, |
| 542 | existingMetadata?: TableMetadata | null |
| 543 | ): Promise<TableMetadata> { |
| 544 | const merged: TableMetadata = { ...(existingMetadata ?? {}), ...metadata } |
| 545 | |
| 546 | // When `columnOrder` is in the patch, scrub any workflow-group dependency |
| 547 | // that now sits to the right of (or at the same index as) its group's |
| 548 | // leftmost column. Without this, reordering a column could leave a group |
| 549 | // depending on a column it can no longer reach in the dag — the group |
| 550 | // would never fire. |
| 551 | const newOrder = metadata.columnOrder |
| 552 | let nextSchema: TableSchema | null = null |
| 553 | if (Array.isArray(newOrder) && newOrder.length > 0) { |
| 554 | const [tableRow] = await db |
| 555 | .select({ schema: userTableDefinitions.schema }) |
| 556 | .from(userTableDefinitions) |
| 557 | .where(eq(userTableDefinitions.id, tableId)) |
| 558 | .limit(1) |
| 559 | if (tableRow) { |
| 560 | const schema = tableRow.schema as TableSchema |
| 561 | const groups = schema.workflowGroups ?? [] |
| 562 | if (groups.length > 0) { |
| 563 | // `columnOrder` and group dep refs are both keyed by stable column id. |
| 564 | const positionOf = new Map<string, number>() |
| 565 | newOrder.forEach((id, i) => positionOf.set(id, i)) |
| 566 | let mutated = false |
| 567 | const nextGroups = groups.map((group) => { |
| 568 | const ownCols = schema.columns.filter((c) => c.workflowGroupId === group.id) |
| 569 | let leftmost = Number.POSITIVE_INFINITY |
| 570 | for (const c of ownCols) { |
| 571 | const idx = positionOf.get(getColumnId(c)) ?? Number.POSITIVE_INFINITY |
| 572 | if (idx < leftmost) leftmost = idx |
| 573 | } |
| 574 | if (!Number.isFinite(leftmost)) return group |
| 575 | const deps = group.dependencies?.columns ?? [] |
| 576 | const removed = new Set(deps.filter((dep) => (positionOf.get(dep) ?? -1) >= leftmost)) |
| 577 | if (removed.size === 0) return group |
| 578 | const stripped = stripGroupDeps(group, removed) |
| 579 | if (stripped !== group) mutated = true |
| 580 | return stripped |
| 581 | }) |
| 582 | if (mutated) nextSchema = { ...schema, workflowGroups: nextGroups } |
| 583 | } |
| 584 | } |
| 585 | } |
| 586 | |
| 587 | await db |
| 588 | .update(userTableDefinitions) |
| 589 | .set(nextSchema ? { metadata: merged, schema: nextSchema } : { metadata: merged }) |
| 590 | .where(eq(userTableDefinitions.id, tableId)) |
| 591 | |
| 592 | return merged |
| 593 | } |
| 594 | |
| 595 | /** |
| 596 | * Archives a table. |
no test coverage detected