Generated record surfaces and form helpers
View as MarkdownIdentity
Section titled “Identity”Generated record routes, form components, and form helpers are exported from
@sapporta/frontend. Presentation-neutral validation values are exported from
@sapporta/shared/validation.
Generated create screens use TanStack Form directly. The previous
RecordFormProvider, record-form store, and record-form hooks are not public
integration points.
Generated routes and components
Section titled “Generated routes and components”/tables/:tableNamerendersTableRoute, the table-aware list, inline record editing, row expansion, and declared child collections./tables/:tableName/newrendersNewRecordRoute.- There is no generated
/tables/:tableName/:idbrowser route. The separate HTTP operationGET /api/tables/<table>/<id>returns one row but does not create a frontend page. Use the table workflow/expanded row or add an application detail route. NewRecordPagerenders a metadata-derived create form for oneTableSchema.FormFieldrenders oneRecordFormFieldModel. It acceptsfield,value,issue, andonChangevalues and supplies the matching label, input, combobox, checkbox, or scoped lookup.
NewRecordPage creates a TanStack Form instance. TanStack Form owns draft
values, submit validation, field errors, form errors, and pending state. A
successful create calls createTableRow(), reloads mounted TGrid sessions for
the table with reloadTGridRows(), invalidates the table’s public TanStack
Query prefix, and replaces the route with the table page.
Field metadata
Section titled “Field metadata”import { buildRecordFormFields, fieldModelForColumn, foreignKeyFieldModelForColumn,} from "@sapporta/frontend";
const fields = buildRecordFormFields({ table, lookups });const title = fieldModelForColumn(fields, "title");const project = foreignKeyFieldModelForColumn(fields, "project_id");buildRecordFormFields({ table, lookups }) returns one RecordFormFieldModel
for each editable column. The union contains text, number, currency,
percentage, date, timestamp, checkbox, select, and foreignKey
variants. Select models include their declared options. Foreign-key models
include scoped lookup capabilities.
Columns do not produce editable field models when they are visuallyHidden, are
primary keys with a generated default, have apiWritable: false, or use a
system-managed scope name. Client-assigned primary keys remain editable when the
metadata permits them.
created_at and updated_at are visually hidden by default, and an application
may override that presentation hint. A timestamp default does not make a column
API-owned: use apiWritable: false when direct generated-API callers must not
set it.
fieldModelForColumn() returns the model for one SQL column name and throws
when the field is absent. foreignKeyFieldModelForColumn() also verifies that
the model is a foreign key. These failures identify a mismatch between the
application form and the current table metadata; they are not user-validation
results.
The matching exported types are RecordFormFieldModel and
ForeignKeyRecordFormFieldModel.
Create-draft parsing
Section titled “Create-draft parsing”const parsed = parseCreateDraft(table, formValues);if (!parsed.ok) { return Object.fromEntries( parsed.issues.map((issue) => [issue.field, issue.message]), );}
await createTableRow(table.name, parsed.value);parseCreateDraft(table, draft) performs a non-mutating submit-time decode. Its
result is either { ok: true, value } or { ok: false, issues }.
ParseCreateDraftResult names this union. CreateDraftIssue remains as a
deprecated alias; use FieldIssue from @sapporta/shared/validation.
- Numeric, currency, percentage, date, and timestamp controls may retain raw text while the user edits. The parser converts valid complete drafts at submit time.
- Optional empty non-text values are omitted. Omission preserves database defaults and optional insert behavior.
- Empty text remains
"". It is never converted tonull. - Required empty values and invalid drafts produce
FieldIssuevalues keyed by public SQL column name. - A searchable select may expose a clear button. Clearing a required select still produces a required-field issue; the editor affordance does not change schema nullability.
- Values for non-editable columns are ignored. Client metadata does not grant write authority.
The parser implements create presence and wire-decoding rules. The server still owns API write policy, trusted scope values, reference visibility, authorization, structural validation, application validation, and database constraints. Patch parsing is a separate contract because omission means “leave unchanged” during update.
Submission errors
Section titled “Submission errors”import { FormSubmissionError, fieldIssuesForSubmissionError, firstFormErrorMessage,} from "@sapporta/frontend/form";FormSubmissionError(issues)carries localFieldIssue[]values. Itsissuesarray is copied at construction.fieldIssuesForSubmissionError(error)returns copied issues from aFormSubmissionErroror recognized field details from a SapportaApiError. Other errors produce[].firstFormErrorMessage(errors)converts the first TanStack Form field error to display text. It supports strings,Errorinstances, objects with a stringmessage, and other printable values. An empty list returnsundefined.
Generated create forms map recognized API validation details into TanStack
Form’s field error map and keep the API summary as the form-level error. A field
issue may use a direct field name or a nested path such as lines.0.quantity.
A successful generated create sends POST /api/tables/<table>, reloads mounted
TGrid sessions for that table, invalidates its public TanStack Query prefix, and
replace-navigates to /tables/<table>.
Shared validation values
Section titled “Shared validation values”@sapporta/shared/validation exports:
interface FieldIssue { field: string; message: string;}
interface ApiProblem { summary: string; code?: string; fieldIssues: readonly FieldIssue[];}fieldIssuesFromZodError(error)preserves nested Zod paths with dot notation. A pathless issue uses the field nameform.apiProblemFromBody(body)validates a Sapporta error body and returns its summary, optional code, and recognized field details. Invalid bodies returnundefined. Unrecognized detail entries are ignored.
These helpers normalize transport and validation data. Application code still decides how server field names map to a domain form and which summary the user can act on.