# Schema Format Reference

The schema registry is a JSON file with a well-defined structure. Below are the complete TypeScript type definitions used by both the compiler (to generate) and the CMS (to consume).

## SchemaRegistry

The top-level type:

```typescript
interface SchemaRegistry {
  generatedBy: string;
  version: string;
  features: Record<string, string[]>;
  tables: Record<string, TableMeta>;
  tablesByFeature: Record<string, string[]>;
  featureActions: Record<string, ActionMeta[]>;
  pages: Record<string, PageMeta>;
  pagesByFeature: Record<string, string[]>;
}
```

| Field | Description |
|-------|-------------|
| `generatedBy` | Always `"quickback-compiler"` |
| `version` | Compiler version string |
| `features` | Map of feature name to array of source file names |
| `tables` | Map of camelCase table name to full table metadata |
| `tablesByFeature` | Map of feature name to array of table names in that feature |
| `featureActions` | Map of feature name to standalone actions not attached to one table |
| `pages` | Map of page slug to full page metadata |
| `pagesByFeature` | Map of feature name to array of page slugs in that feature |

The registry deliberately omits wall-clock generation metadata. Every field is
derived from compiler inputs, so identical logical input produces
byte-identical `schema-registry.json` output across consecutive compiles.

## TableMeta

Full metadata for a single table:

```typescript
interface TableMeta {
  name: string;
  dbName: string;
  feature: string;
  columns: ColumnMeta[];
  firewall: Record<string, unknown>;
  crud: Record<string, CrudConfig>;
  create?: WriteOperationConfig | false;
  update?: WriteOperationConfig | false;
  delete?: WriteOperationConfig | false;
  upsert?: WriteOperationConfig | false;
  guards: GuardsConfig;
  masking: Record<string, MaskingRule>;
  views: Record<string, ViewConfig>;
  validation: Record<string, ValidationRule>;
  actions: ActionMeta[];
  displayColumn?: string;
  defaultSort?: { field: string; order: 'asc' | 'desc' };
  inputHints?: Record<string, string>;
  layouts?: Record<string, CmsLayout>;
  internal?: boolean;
}
```

| Field | Description |
|-------|-------------|
| `name` | camelCase table name (e.g., `"accountCode"`) |
| `dbName` | Snake_case SQL table name (e.g., `"account_code"`) |
| `feature` | Parent feature name (e.g., `"accounting"`) |
| `columns` | Ordered array of column metadata |
| `firewall` | Tenant isolation config (organization, owner, softDelete, exception) |
| `crud` | Legacy normalized write config kept for backwards compatibility |
| `create` / `update` / `delete` / `upsert` | Flat write-operation aliases mirroring the authored `defineTable(...)` DSL |
| `guards` | Field-level create/update/immutable/protected rules |
| `masking` | Per-field masking rules keyed by column name |
| `views` | Named column projections keyed by view name |
| `validation` | Per-field validation rules keyed by column name |
| `actions` | Array of action definitions scoped to this table |
| `displayColumn` | Column used as human-readable label (auto-detected or explicit) |
| `defaultSort` | Default sort for CMS table list view (e.g., `{ field: "createdAt", order: "desc" }`) |
| `inputHints` | Map of column name to preferred CMS input type (e.g., `"richtext"`, `"select"`, `"textarea"`, `"checkbox"`) |
| `layouts` | Named record page layouts keyed by layout name (see CmsLayout below) |
| `internal` | When `true`, table is hidden from CMS sidebar |

## ColumnMeta

Metadata for a single column:

```typescript
interface ColumnMeta {
  name: string;
  dbName: string;
  type: "text" | "integer" | "real" | "blob";
  mode?: "boolean";
  primaryKey: boolean;
  notNull: boolean;
  defaultValue?: string | number | boolean;
  fkTarget?: string;
}
```

| Field | Description |
|-------|-------------|
| `name` | camelCase property name |
| `dbName` | Snake_case SQL column name |
| `type` | SQLite storage type |
| `mode` | When `"boolean"`, an integer column represents true/false |
| `primaryKey` | Whether this column is the primary key |
| `notNull` | Whether the column has a NOT NULL constraint |
| `defaultValue` | Static default value (strings, numbers, or booleans) |
| `fkTarget` | Target table name for FK columns (e.g., `"contact"` for a `vendorId` column) |

## CRUDConfig

Per-operation access control:

```typescript
interface CrudConfig {
  access?: AccessRule;
  mode?: string;
}

interface WriteOperationConfig extends CrudConfig {
  defaults?: Record<string, unknown>;
  computed?: Record<string, unknown>;
  fields?: string[];
  maxBatchSize?: number;
  allowFailFast?: boolean;
  batch?: false | Record<string, unknown>;
}

interface AccessRule {
  roles?: string[];
  or?: Array<{
    roles?: string[];
    record?: Record<string, unknown>;
  }>;
  record?: Record<string, unknown>;
}
```

| Field | Description |
|-------|-------------|
| `access.roles` | Array of roles allowed for this operation |
| `access.or` | Alternative access conditions (any must match) |
| `access.record` | Record-level conditions for access |
| `mode` | Operation mode (e.g., `"batch"` for bulk create) |
| `defaults` / `computed` | Create-time defaults and computed field metadata |
| `maxBatchSize` / `allowFailFast` | Batch-write controls when the config describes a batch operation |
| `batch` | Nested batch config for the flat `create` / `update` / `delete` / `upsert` aliases |

## GuardsConfig

Field-level control for create and update operations:

```typescript
interface GuardsConfig {
  createable: string[];
  updatable: string[];
  immutable: string[];
  protected: Record<string, string[]>;
}
```

| Field | Description |
|-------|-------------|
| `createable` | Fields that can be set during record creation |
| `updatable` | Fields that can be modified on existing records |
| `immutable` | Fields that can be set on create but never changed |
| `protected` | Fields only modifiable via named actions (field name to action names) |

## ActionMeta

Metadata for a custom action:

```typescript
interface ActionMeta {
  name: string;
  description: string;
  inputFields: ActionInputField[];
  access?: {
    roles: string[];
    record?: Record<string, unknown>;
  };
  standalone?: boolean;
  path?: string;
  method?: string;
  responseType?: string;
  sideEffects?: string;
  cms?: CmsConfig;
}

interface CmsConfig {
  label?: string;
  icon?: string;
  confirm?: string | boolean;
  destructive?: boolean;
  category?: string;
  hidden?: boolean;
  placement?: 'feature' | 'tables';
  tables?: string[];
  successMessage?: string;
  onSuccess?: 'refresh' | 'redirect:list' | 'close';
  order?: number;
}

interface ActionInputField {
  name: string;
  type: string;
  required: boolean;
  default?: unknown;
}
```

| Field | Description |
|-------|-------------|
| `name` | Action identifier (e.g., `"approve"`, `"applyPayment"`) |
| `description` | Human-readable description shown in dialog |
| `inputFields` | Array of input field definitions |
| `access.roles` | Roles allowed to execute this action |
| `access.record` | Record conditions (e.g., `{ status: { equals: "pending" } }`) |
| `standalone` | When `true`, action is not tied to a specific record |
| `path` | Custom API path (overrides default) |
| `method` | HTTP method (defaults to POST) |
| `responseType` | `"file"` for download responses |
| `sideEffects` | `"sync"` for actions with synchronous side effects |
| `cms` | Optional CMS rendering metadata (label, icon, confirm, destructive, category, hidden, placement, tables, successMessage, onSuccess, order) |

### CmsConfig placement rules

`placement` is only relevant for standalone actions in multi-table features:

- `"feature"` shows the action in the toolbar for every table in the feature.
- `"tables"` shows the action only for the tables listed in `tables`.

If the action is already unambiguously table-scoped, the compiler can infer placement and `placement` is optional.

### ActionInputField

| Field | Description |
|-------|-------------|
| `name` | Field identifier |
| `type` | Zod type string: `"string"`, `"number"`, `"boolean"`, `"array<string>"` |
| `required` | Whether the field must be provided |
| `default` | Default value pre-filled in the form |

## ViewConfig

Named column projection with access control:

```typescript
interface ViewConfig {
  fields: string[];
  access: AccessRule;
}
```

| Field | Description |
|-------|-------------|
| `fields` | Array of column names to include in this view |
| `access` | Role-based access rules (same shape as CrudConfig access) |

## CmsLayout

Named record page layout with ordered sections:

```typescript
interface CmsLayout {
  sections: CmsLayoutSection[];
}

interface CmsLayoutSection {
  label: string;
  fields: string[];
  columns?: 1 | 2;
  collapsed?: boolean;
}
```

| Field | Description |
|-------|-------------|
| `sections` | Ordered array of field sections |

### CmsLayoutSection

| Field | Description |
|-------|-------------|
| `label` | Section header text |
| `fields` | Array of column names to display in this section |
| `columns` | `1` (default) or `2` for two-column field layout |
| `collapsed` | When `true`, section starts collapsed with a toggle to expand |

Fields not assigned to any section in the active layout are collected into an "Other Fields" section.

## MaskingRule

Per-field data masking:

```typescript
interface MaskingRule {
  type: "email" | "phone" | "ssn" | "redact";
  show: {
    roles: string[];
    or?: string;
  };
}
```

| Field | Description |
|-------|-------------|
| `type` | Masking pattern to apply |
| `show.roles` | Roles that see the unmasked value |
| `show.or` | Alternative condition for showing unmasked value |

### Masking Patterns

| Type | Input | Output |
|------|-------|--------|
| `email` | `john@acme.com` | `j***@acme.com` |
| `phone` | `(555) 123-4567` | `***-***-4567` |
| `ssn` | `123-45-6789` | `***-**-6789` |
| `redact` | Any string | `------` |

## ValidationRule

Per-field validation constraints:

```typescript
interface ValidationRule {
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
  enum?: string[];
  email?: boolean;
}
```

| Field | Description |
|-------|-------------|
| `minLength` | Minimum string length |
| `maxLength` | Maximum string length |
| `min` | Minimum numeric value |
| `max` | Maximum numeric value |
| `enum` | Array of allowed string values |
| `email` | When `true`, validates email format |

## PageMeta

Full metadata for a custom page:

```typescript
interface PageMeta {
  slug: string;
  title: string;
  description?: string;
  icon?: string;
  feature: string;
  access?: { roles: string[] };
  dataSources: Record<string, PageDataSource>;
  layout: PageLayout;
  matching?: PageMatching;
  pageActions?: Record<string, PageAction>;
}
```

| Field | Description |
|-------|-------------|
| `slug` | URL-safe page identifier |
| `title` | Display title |
| `description` | Human-readable description |
| `icon` | Icon name for sidebar |
| `feature` | Parent feature name |
| `access` | Role-based access control |
| `dataSources` | Named data source bindings |
| `layout` | Layout configuration |
| `matching` | Matching/reconciliation rules |
| `pageActions` | Actions triggered from the page |

### PageDataSource

```typescript
interface PageDataSource {
  table: string;
  defaultFilters?: Record<string, unknown>;
  defaultSort?: { field: string; order: 'asc' | 'desc' };
  displayColumns?: string[];
}
```

### PageLayout

```typescript
interface PageLayout {
  type: 'split-panel';
  panels: PagePanel[];
}

interface PagePanel {
  id: string;
  title: string;
  dataSource: string;
  position: 'left' | 'right';
  features?: string[];
}
```

Panel `features` can include `"drag-source"` and `"drop-target"` for drag-and-drop interactions.

### PageMatching

```typescript
interface PageMatching {
  enabled: boolean;
  rules: MatchingRule[];
  confidenceThreshold?: number;
}

interface MatchingRule {
  name: string;
  weight: number;
  condition: {
    left: string;
    right: string;
    operator: 'abs-equals' | 'within-days' | 'fuzzy-match';
    value?: number;
  };
}
```

| Field | Description |
|-------|-------------|
| `enabled` | Whether matching is active |
| `rules` | Array of matching rules with weights (0-1) |
| `confidenceThreshold` | Minimum score to display a match suggestion (0-1, default 0.5) |
| `condition.left` | Left field reference (`dataSourceName.fieldName`) |
| `condition.right` | Right field reference |
| `condition.operator` | Comparison operator |
| `condition.value` | Operator parameter (e.g., max days for `within-days`) |

### PageAction

```typescript
interface PageAction {
  table: string;
  action: string;
  inputMapping: Record<string, string>;
  label?: string;
  icon?: string;
  confirm?: string;
}
```

| Field | Description |
|-------|-------------|
| `table` | Target table for the action |
| `action` | Action name on that table |
| `inputMapping` | Maps action input fields to data source field references (`"dataSourceName.$fieldName"`) |
| `label` | Button label (defaults to action name) |
| `icon` | Button icon name |
| `confirm` | Confirmation prompt shown before execution |

## Next Steps

- **[Schema Registry](/ui/admin/schema-registry)** — How the registry is generated and used
- **[Components Reference](/ui/admin/components)** — All CMS React components
