(
tableId: string,
column: {
id?: string
name: string
type: string
required?: boolean
unique?: boolean
position?: number
},
requestId: string
)
| 42 | * @throws Error if table not found or column name already exists |
| 43 | */ |
| 44 | export async function addTableColumn( |
| 45 | tableId: string, |
| 46 | column: { |
| 47 | id?: string |
| 48 | name: string |
| 49 | type: string |
| 50 | required?: boolean |
| 51 | unique?: boolean |
| 52 | position?: number |
| 53 | }, |
| 54 | requestId: string |
| 55 | ): Promise<TableDefinition> { |
| 56 | return withLockedTable(tableId, async (table, trx) => { |
| 57 | if (!NAME_PATTERN.test(column.name)) { |
| 58 | throw new Error( |
| 59 | `Invalid column name "${column.name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.` |
| 60 | ) |
| 61 | } |
| 62 | |
| 63 | if (column.name.length > TABLE_LIMITS.MAX_COLUMN_NAME_LENGTH) { |
| 64 | throw new Error( |
| 65 | `Column name exceeds maximum length (${TABLE_LIMITS.MAX_COLUMN_NAME_LENGTH} characters)` |
| 66 | ) |
| 67 | } |
| 68 | |
| 69 | if (!COLUMN_TYPES.includes(column.type as (typeof COLUMN_TYPES)[number])) { |
| 70 | throw new Error( |
| 71 | `Invalid column type "${column.type}". Must be one of: ${COLUMN_TYPES.join(', ')}` |
| 72 | ) |
| 73 | } |
| 74 | |
| 75 | const schema = table.schema |
| 76 | if (schema.columns.some((c) => c.name.toLowerCase() === column.name.toLowerCase())) { |
| 77 | throw new Error(`Column "${column.name}" already exists`) |
| 78 | } |
| 79 | |
| 80 | if (schema.columns.length >= TABLE_LIMITS.MAX_COLUMNS_PER_TABLE) { |
| 81 | throw new Error( |
| 82 | `Table has reached maximum column limit (${TABLE_LIMITS.MAX_COLUMNS_PER_TABLE})` |
| 83 | ) |
| 84 | } |
| 85 | |
| 86 | const newColumn: TableSchema['columns'][number] = { |
| 87 | // Honor a caller-provided id (undo of a delete reuses the original id); |
| 88 | // otherwise mint a fresh one. |
| 89 | id: column.id ?? generateColumnId(), |
| 90 | name: column.name, |
| 91 | type: column.type as TableSchema['columns'][number]['type'], |
| 92 | required: column.required ?? false, |
| 93 | unique: column.unique ?? false, |
| 94 | } |
| 95 | const newColumnId = getColumnId(newColumn) |
| 96 | |
| 97 | const columns = [...schema.columns] |
| 98 | if (column.position !== undefined && column.position >= 0 && column.position < columns.length) { |
| 99 | columns.splice(column.position, 0, newColumn) |
| 100 | } else { |
| 101 | columns.push(newColumn) |
no test coverage detected