import { assertColumnName, assertTableName, slugifyColumnName, collectPaths, formatChoices, isComputed, isFormatAllowed, parseCached, q, validateSequence, type ColumnFormat, type ColumnMeta, type ColumnType, type Locale, } from '@wellformed/shared'; import { badRequest, conflict, notFound } from '../errors.js'; import { invalidateMeta, transact, type Db } from '../db/driver.js'; import { findColumn, getTable, incomingLinks, loadMeta, storedColumns, type MetaSnapshot, type TableSchema, } from '../db/meta.js'; import { recordAudit, type Actor } from './audit.js'; import { validateExpression } from './compile.js'; import { newId } from './ids.js'; import { dropSqlViews } from './sql-views.js'; import { uniqueIndexName } from './tighten.js'; import { sqlTypeFor } from './values.js'; /** * The derived table name, stepped past whatever already exists. * * People choose the label; the table name is derived from it (see * `slugifyTableName` in shared) and two labels can derive the same name. * "orders" being taken makes the next one "orders_2" rather than an error * about an identifier the person never typed. */ export function availableTableName(meta: MetaSnapshot, wanted: string): string { if (!meta.tables[wanted]) return wanted; for (let n = 2; ; n++) { const suffix = `_${n}`; const candidate = `${wanted.slice(0, 48 - suffix.length)}${suffix}`; if (!meta.tables[candidate]) return candidate; } } /** * The derived column name, stepped past whatever the table already has. * * The column-side twin of `availableTableName`: people choose the label, the * identifier is derived from it, and "price" being taken makes the next one * "price_2" rather than an error about a name the person never typed. */ export function availableColumnName(table: TableSchema, wanted: string): string { const taken = new Set(table.columns.map((c) => c.name)); if (!taken.has(wanted)) return wanted; for (let n = 2; ; n++) { const suffix = `_${n}`; const candidate = `${wanted.slice(0, 48 - suffix.length)}${suffix}`; if (!taken.has(candidate)) return candidate; } } /** * The built-in everything view, named in the workspace's language. * * The engine's own fallback is English; routes pass this instead, so a Dutch * workspace does not grow an "All klanten" among its "Alle orders". Two words * inlined rather than a message catalog: the server has none, and one word * per language does not justify starting one. */ export function defaultViewName(label: string, locale: Locale): string { return `${locale === 'nl' ? 'Alle' : 'All'} ${label.toLowerCase()}`; } /** What the row-label column is called when nobody says: the word for a name. */ export function defaultDisplayLabel(locale: Locale): string { return locale === 'nl' ? 'Naam' : 'Name'; } export interface CreateTableInput { name: string; label: string; /** What to call the text column that labels a row. Defaults to "Name". */ displayLabel?: string; /** Its identifier, when the caller must know it up front (import, demo). */ display?: string; /** The sidebar heading to list the table under. */ group?: string; /** What to call the built-in everything view. Defaults to "All