Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .changeset/issue-5373-retire-crud-schema.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
---
'@object-ui/types': minor
'@object-ui/core': minor
---

Retire `CRUDSchema` and the `type: 'crud'` node spelling (objectui#5373,
maintainer ruling 2026-08-20, route 2) under ADR-0049 enforce-or-remove.

`crud` had four declaration faces and no registered renderer, for the whole
life of the key: the TS interface (`packages/types/src/crud.ts`), the zod
mirror (`packages/types/src/zod/crud.zod.ts`), a dedicated branch in
`validateSchema` that affirmatively PASSED it, and `CRUDBuilder` in
`@object-ui/core`. A node spelling it painted the OBJUI-001 "Unknown component
type" panel, and `content/docs/api/schema-reference.md` published it as
reference material — so a reader (or an AI author) who copied the page got a
red panel.

Removed from `@object-ui/types`: the `CRUDSchema` interface and its zod
mirror, the four shapes that existed only to type its keys — `CRUDOperation`,
`CRUDFilter`, `CRUDToolbar`, `CRUDPagination` and their zod mirrors and
`…SchemaType` aliases — and `CRUDSchema` as a member of `CRUDComponentSchema`,
which is what took it off the node union `AnySchema`. `ActionSchema`,
`DetailSchema` and `CRUDDialogSchema` are unchanged and remain the union's
members.

Removed from `@object-ui/core`: `CRUDBuilder` and the `crud()` factory.

Authoring `crud` is now REFUSED BY NAME rather than passed or silently
ignored. `validateSchema` returns an `error` with `code: 'RETIRED_TYPE'` on
`schema.type` — at any depth, since it is what `validateChildren` recurses
with — so `assertValidSchema` throws and `isValidSchema` answers `false`. The
message names the migration: `object-grid` for the record table with its
toolbar, filters, pagination and row/batch actions, `object-form` for the
create/edit form, and `detail` for the record view. `api/schema-reference.md`
is rewritten around those shapes.

Note on blast radius: the repository itself contains zero authored `crud`
nodes and zero registrations of the key (measured on the merge base against
the doc gate's own 659-key registry derivation, which reads `register` and
`registerLazy` alike). That is an IN-REPO zero, not an npm zero — a published
consumer that imported the `CRUDSchema` type, called `crud()` / `CRUDBuilder`,
or authored `type: 'crud'` will see a compile error or a validation error
respectively. Both are the intended, loud replacement for a shape that has
never rendered.
2 changes: 1 addition & 1 deletion content/docs/api/index.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,7 @@ Complete reference for every ObjectUI schema type with annotated JSON examples c
- **Layout** — `PageSchema`, `DivSchema`, `CardSchema`, `GridSchema`, `TabsSchema`
- **Forms** — `FormSchema`, `InputSchema`, `SelectSchema`, `ButtonSchema`
- **Data Display** — `TableSchema`, `ChartSchema`, `TreeViewSchema`
- **CRUD** — `CRUDSchema`, `ActionSchema`, `DetailSchema`
- **CRUD** — `ActionSchema`, `DetailSchema`, `CRUDDialogSchema`
- **ObjectQL** — `ObjectGridSchema`, `ObjectFormSchema`, `ObjectViewSchema`
- **Complex** — `KanbanSchema`, `DashboardSchema`, `CalendarViewSchema`
- **Views** — `DetailViewSchema`, `ViewSwitcherSchema`
115 changes: 30 additions & 85 deletions content/docs/api/schema-reference.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -424,7 +424,7 @@ did; authoring them is now refused by validation instead of silently ignored).
| `data` | `any[]` | Array of row data objects. |
| `footer` | `SchemaNode \| string` | Footer content below the table. |

**Related:** [CRUDSchema](#crudschema), [ObjectGridSchema](#objectgridschema)
**Related:** [ObjectGridSchema](#objectgridschema)

---

Expand DownExpand Up@@ -523,88 +523,33 @@ A hierarchical tree component for nested data with expand/collapse and selection

## CRUD Schemas

### CRUDSchema
### CRUDSchema — retired

A complete CRUD (Create, Read, Update, Delete) interface with table, toolbar, filters, pagination, and batch/row actions.
`CRUDSchema` and the `crud` node type were **removed** in objectui#5373 under
ADR-0049 (enforce-or-remove). The type had four declaration faces — a TypeScript
interface, a zod mirror, a branch in the schema validator and a `CRUDBuilder` —
and no registered renderer, for the whole life of the key. A node that spelled it
painted the OBJUI-001 "Unknown component type" panel, so this page was teaching a
shape that could not render.

```json
{
"type": "crud",
"title": "Products",
"resource": "products",
"api": "/api/products",
"selectable": "multiple",
"defaultSort": "name",
"defaultSortOrder": "asc",
"columns": [
{ "name": "name", "label": "Product Name", "sortable": true },
{ "name": "price", "label": "Price", "align": "right" },
{ "name": "stock", "label": "Stock", "sortable": true },
{ "name": "status", "label": "Status" }
],
"fields": [
{ "name": "name", "label": "Product Name", "type": "text", "required": true },
{ "name": "price", "label": "Price", "type": "number", "required": true },
{ "name": "stock", "label": "Stock", "type": "number" },
{ "name": "status", "label": "Status", "type": "select", "options": [
{ "label": "Active", "value": "active" },
{ "label": "Draft", "value": "draft" }
]}
],
"operations": {
"create": true,
"read": true,
"update": true,
"delete": true,
"export": true
},
"toolbar": {
"showSearch": true,
"showFilters": true,
"showExport": true,
"actions": [
{ "type": "action", "label": "Add Product", "level": "primary", "icon": "Plus" }
]
},
"filters": [
{ "name": "status", "label": "Status", "type": "select", "options": [
{ "label": "Active", "value": "active" },
{ "label": "Draft", "value": "draft" }
]}
],
"pagination": {
"pageSize": 20,
"pageSizeOptions": [10, 20, 50, 100]
},
"rowActions": [
{ "type": "action", "label": "Edit", "icon": "Pencil", "actionType": "dialog" },
{ "type": "action", "label": "Delete", "icon": "Trash2", "level": "danger", "actionType": "confirm" }
],
"batchActions": [
{ "type": "action", "label": "Delete Selected", "level": "danger", "actionType": "confirm" }
]
}
```
There is no drop-in replacement, because a CRUD screen is a composition rather
than one node. Build it from the shapes that do render:

| Property | Type | Description |
|----------|------|-------------|
| `title` | `string` | CRUD view title. |
| `resource` | `string` | Resource identifier for API calls. |
| `api` | `string` | Base API endpoint URL. |
| `columns` | `TableColumn[]` | **Required.** Column definitions for the table view. |
| `fields` | `FormField[]` | Field definitions for create/edit forms. |
| `operations` | `object` | Toggle CRUD operations: `create`, `read`, `update`, `delete`, `export`, `import`. |
| `toolbar` | `CRUDToolbar` | Toolbar configuration with search, filters, and custom actions. |
| `filters` | `CRUDFilter[]` | Filter definitions. |
| `pagination` | `CRUDPagination` | Pagination settings with `pageSize` and `pageSizeOptions`. |
| `selectable` | `boolean \| "single" \| "multiple"` | Row selection mode. |
| `rowActions` | `ActionSchema[]` | Actions available on each row. |
| `batchActions` | `ActionSchema[]` | Actions for selected rows. |
| `defaultSort` / `defaultSortOrder` | `string` / `"asc" \| "desc"` | Default sort field and direction. |
| `mode` | `"table" \| "grid" \| "list" \| "kanban"` | Display mode for the CRUD view. |
| `emptyState` | `SchemaNode` | Custom empty state content. |

**Related:** [ActionSchema](#actionschema), [TableSchema](#tableschema), [ObjectGridSchema](#objectgridschema)
| What `CRUDSchema` promised | What to author instead |
|---|---|
| The record table, with toolbar, filters, pagination and row/batch actions | [ObjectGridSchema](#objectgridschema) |
| The create/edit form | [ObjectFormSchema](#objectformschema) |
| The single-record read view | [DetailSchema](#detailschema) / [DetailViewSchema](#detailviewschema) |
| Whole-object screens that bundle the above | [ObjectViewSchema](#objectviewschema) |

The `defaultSort` and `defaultSortOrder` keys documented here were `CRUDSchema`'s
own — a flat field name plus a separate direction. They are gone with it.
[ObjectGridSchema](#objectgridschema) declares its own, differently shaped
`defaultSort` (an object with `field` and `order`); that key is unaffected.

Authoring `crud` is now refused by name: `validateSchema` from `@object-ui/core`
returns a `RETIRED_TYPE` error on `schema.type` naming the migration above, and
`objectui check` reports the type as unknown.

---

Expand DownExpand Up@@ -661,7 +606,7 @@ A powerful action definition supporting API calls, confirmations, dialogs, chain
| `redirect` | `string` | URL to navigate to after action. |
| `retry` | `object` | Retry config with `maxAttempts` and `delay`. |

**Related:** [CRUDSchema](#crudschema), [ButtonSchema](#buttonschema)
**Related:** [DetailSchema](#detailschema), [ButtonSchema](#buttonschema)

---

Expand DownExpand Up@@ -722,7 +667,7 @@ A single-record detail view with grouped fields, actions, and tabs.
| `showBack` | `boolean` | Show a back navigation button. |
| `loading` | `boolean` | Show loading state. |

**Related:** [DetailViewSchema](#detailviewschema), [CRUDSchema](#crudschema)
**Related:** [DetailViewSchema](#detailviewschema), [ObjectGridSchema](#objectgridschema)

---

Expand DownExpand Up@@ -790,7 +735,7 @@ A data grid that auto-fetches from an ObjectQL object definition. Includes searc
| `frozenColumns` | `number` | Number of columns frozen on scroll. |
| `navigation` | `ViewNavigationConfig` | SPA navigation configuration. |

**Related:** [ObjectViewSchema](#objectviewschema), [CRUDSchema](#crudschema), [TableSchema](#tableschema)
**Related:** [ObjectViewSchema](#objectviewschema), [TableSchema](#tableschema)

---

Expand DownExpand Up@@ -966,7 +911,7 @@ A drag-and-drop Kanban board with columns and cards.
| `onColumnAdd` | `function` | Callback when a new column is added. |
| `onCardAdd` | `function` | Callback when a new card is added to a column. |

**Related:** [ObjectViewSchema](#objectviewschema), [CRUDSchema](#crudschema)
**Related:** [ObjectViewSchema](#objectviewschema), [ObjectGridSchema](#objectgridschema)

---

Expand DownExpand Up@@ -1300,7 +1245,7 @@ import type { FormSchema, InputSchema, SelectSchema, ButtonSchema } from '@objec
import type { TableSchema, ChartSchema, TreeViewSchema } from '@object-ui/types';

// CRUD
import type { CRUDSchema, ActionSchema, DetailSchema } from '@object-ui/types';
import type { ActionSchema, DetailSchema } from '@object-ui/types';

// ObjectQL
import type { ObjectGridSchema, ObjectFormSchema, ObjectViewSchema } from '@object-ui/types';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,8 +24,9 @@
* REJECTED, not ignored), and the binding's own predicate
* `isElementDataSourceConfig` decides on `object` — so a `resource`-only
* binding was never a binding on any other renderer. `resource` IS a real key
* elsewhere (`CRUDSchema.resource`, the `DataSource` adapter's first
* parameter, `LiveExportOptions.resource`); none of those is this surface.
* elsewhere (the `DataSource` adapter's first parameter,
* `LiveExportOptions.resource`); none of those is this surface. (`CRUDSchema.resource`
* was a fourth until objectui#5373 retired `CRUDSchema` under ADR-0049.)
*
* Per AGENTS.md #0.1 the fix belongs at the producer, never as a renderer-side
* alias — and the producer census found no producer to fix: nothing in this
Expand Down
19 changes: 18 additions & 1 deletion packages/cli/src/__tests__/check-known-types.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,9 +81,26 @@ afterEach(() => {
});

describe('objectui check — unknown schema types', () => {
it('warns about `crud`, which four declaration faces describe and no renderer registers', async () => {
it('warns about `crud`, a RETIRED spelling that must never re-enter the key set', async () => {
// The defect objectui#5115 was filed for: this file passed in silence, and
// then rendered the OBJUI-001 "Unknown component type" panel in the browser.
//
// This pin was flipped by objectui#5373, which retired `CRUDSchema` under
// ADR-0049. Its old name said `crud` was a type "four declaration faces
// describe and no renderer registers" — true when it was written, false
// now: the interface, the zod mirror, the validator branch and the builder
// are all gone, and `type: 'crud'` is refused BY NAME by
// `validateSchema` in `@object-ui/core`.
//
// What this pin can and cannot witness, stated so the next reader does not
// over-read it: `KNOWN_SCHEMA_TYPES` is derived from the REGISTRATION calls,
// and `crud` never had one — so this assertion held before the retirement
// and holds after it, and would keep holding if the retirement were
// reverted. It is a regression pin against `crud` being REGISTERED back
// into the key set, not a witness of the declarations being gone. The
// witnesses that do distinguish those two worlds are the refusal test in
// `@object-ui/core`'s `schema-validator.test.ts` and the union/barrel pins
// in `@object-ui/types`' `crud-retirement-5373.test.ts`.
writeSchema('crud-page.json', { type: 'crud', resource: '/api/accounts' });
await check(cwd);
expect(unknownTypeWarnings()).toEqual([
Expand Down
92 changes: 14 additions & 78 deletions packages/core/src/builder/__tests__/schema-builder.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
import { describe, it, expect } from 'vitest';
import type { CRUDOperation, CRUDSchema } from '@object-ui/types';
import { form, crud, button, input, card, grid, flex } from '../../builder/schema-builder';

/**
* `CRUDSchema.operations.<key>` is `boolean | CRUDOperation | undefined`, so an
* `.enabled` read off it does not compile. The `enable*()` builders always write
* the OBJECT form; this narrows to it and fails loudly if that ever stops being
* true, instead of casting the distinction away.
*/
function operationOf(schema: CRUDSchema, key: 'create' | 'update' | 'delete'): CRUDOperation {
const operation = schema.operations?.[key];
if (typeof operation !== 'object' || operation === null) {
throw new Error(`operations.${key} is not the object form: ${String(operation)}`);
}
return operation;
}
import * as builder from '../../builder/schema-builder';
import { form, button, input, card, grid, flex } from '../../builder/schema-builder';

describe('SchemaBuilder', () => {
describe('form()', () => {
Expand DownExpand Up@@ -87,68 +73,6 @@ describe('SchemaBuilder', () => {
});
});

describe('crud()', () => {
it('creates a basic CRUD schema', () => {
const schema = crud().id('test-crud').build();
expect(schema).toBeDefined();
expect(schema.type).toBe('crud');
expect(schema.id).toBe('test-crud');
});

it('supports resource definition', () => {
const schema = crud()
.resource('users')
.build();
expect(schema.resource).toBe('users');
});

// The fixtures below used to be authored as `{ name, label }` — a dialect
// `TableColumn` does not have (its keys are `accessorKey` / `header`). The
// builder stores whatever it is handed, so `columns![0].name` was asserting
// that the builder returns its own input, on a shape no CRUD renderer reads
// (objectui#4040).
it('supports column definitions', () => {
const schema = crud()
.column({ accessorKey: 'name', header: 'Name' })
.build();
expect(schema.columns).toHaveLength(1);
expect(schema.columns![0].accessorKey).toBe('name');
expect(schema.columns![0].header).toBe('Name');
});

it('supports bulk columns', () => {
const schema = crud()
.columns([
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
])
.build();
expect(schema.columns).toHaveLength(2);
});

it('supports CRUD operations', () => {
const schema = crud()
.api('/api/users')
.enableCreate()
.enableUpdate()
.enableDelete()
.build();
expect(schema.operations).toBeDefined();
expect(operationOf(schema, 'create').enabled).toBe(true);
expect(operationOf(schema, 'update').enabled).toBe(true);
expect(operationOf(schema, 'delete').enabled).toBe(true);
});

it('supports pagination', () => {
const schema = crud()
.pagination(25)
.build();
expect(schema.pagination).toBeDefined();
expect(schema.pagination!.enabled).toBe(true);
expect(schema.pagination!.pageSize).toBe(25);
});
});

describe('button()', () => {
it('creates a button schema', () => {
const schema = button().id('btn').build();
Expand DownExpand Up@@ -254,3 +178,15 @@ describe('SchemaBuilder', () => {
});
});
});

describe('the retired `crud` builder (objectui#5373)', () => {
it('exports no `crud` factory — the ADR-0049 retirement removed CRUDBuilder', () => {
// Counter-probe in the SAME assertion pair, so this cannot pass by the
// module failing to load or by the names being read off the wrong object:
// the surviving factories must still be there.
expect(builder).not.toHaveProperty('crud');
expect(builder).not.toHaveProperty('CRUDBuilder');
expect(typeof builder.form).toBe('function');
expect(typeof builder.grid).toBe('function');
});
});
Loading
Loading