Skip to content

Latest commit

 

History

History
260 lines (206 loc) · 7.63 KB

File metadata and controls

260 lines (206 loc) · 7.63 KB
title Data Model
description Objects, fields, relationships, validation, indexes — described to AI or written in TypeScript.

Data Model

The data model is the single source of truth for your app. Once an object exists, ObjectOS gives you REST APIs, a Console view, RBAC checkpoints, audit log entries, and AI tool exposure — for free.

Most customers never write the schema by hand. They describe what they need in the AI Builder and the platform creates the objects, fields, indexes, and translations. This page describes the underlying shape — so you understand what the AI is generating and can edit it directly when you want to.

Authoring paths

Path Looks like
AI Builder (primary) "Create a support_ticket object with subject, description, priority, status, assignee."
Console click-build Console → Objects → New Object → forms
TypeScript (*.object.ts) The TS shown below — typically inside a forked template

All three produce the same schema. The schema is canonical; everything else is derived.

Anatomy of an object

// src/objects/task.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Task = ObjectSchema.create({
  name: 'todo_task',
  label: 'Task',
  pluralLabel: 'Tasks',
  icon: 'check-square',
  description: 'A single unit of work.',

  fields: {
    subject:     Field.text({ label: 'Subject', required: true, maxLength: 200 }),
    description: Field.markdown({ label: 'Description' }),
    status:      Field.select({
      label: 'Status',
      options: [
        { label: 'To Do',       value: 'todo', default: true },
        { label: 'In Progress', value: 'in_progress' },
        { label: 'Done',        value: 'done' },
      ],
    }),
    due:         Field.date({ label: 'Due' }),
    assignee:    Field.lookup('sys_user', { label: 'Assignee' }),
  },

  enable: {
    trackHistory: true,    // record field changes in audit log
    apiEnabled: true,      // expose REST endpoints (default true)
    feeds: true,           // chatter / comments / @mentions
  },
});

Register it in your stack:

// objectstack.config.ts
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects';

export default defineStack({
  manifest: { id: 'my.app', namespace: 'myapp', version: '0.1.0', type: 'app', name: 'My App' },
  objects: Object.values(objects),
});

That's all you need. os dev recompiles, and /api/v1/data/todo_task, the Console Task view, and the Console permission row all appear.

Field types

ObjectStack ships ~25 field types. The most-used ones:

Scalars

Type What it stores Helper
text Short string Field.text({ maxLength, required })
textarea Long string Field.textarea(...)
markdown Rich text with markdown Field.markdown(...)
number Integer Field.number({ min, max })
decimal Exact decimal (money, etc.) Field.decimal({ precision, scale })
boolean True/false Field.boolean({ defaultValue })
date Calendar date Field.date(...)
datetime Timestamp Field.datetime(...)
email Validated email Field.email(...)
url Validated URL Field.url(...)
phone Validated phone Field.phone(...)
json Arbitrary JSON Field.json(...)

Choices

Type Use for
select Single choice (enum)
multiselect Multiple choices

Relationships

Type Cardinality Helper
lookup One-to-many (FK) Field.lookup({ reference: 'sys_user' })
masterDetail One-to-many with cascade delete Field.masterDetail({ reference: 'order' })

Files & media

Type What it stores
file One file via the storage service
image Image file with preview

Computed / derived

Type Behavior
formula Computed at read time from a CEL expression
summary Aggregate of related records (sum/count/avg)
autonumber Sequence (INV-{000001})
created, lastModified System-maintained timestamps
createdBy, lastModifiedBy System-maintained user refs

Required / unique / default

Common modifiers on every scalar field:

Field.text({
  label: 'Code',
  required: true,
  unique: true,          // unique constraint enforced at DB level
  defaultValue: '',
  helpText: 'Internal short code',
})

Validation

Inline:

Field.number({ label: 'Quantity', min: 1, max: 9999 })
Field.text({ label: 'SKU', pattern: '^[A-Z]{3}-[0-9]{4}$' })

Object-level rules (cross-field):

ObjectSchema.create({
  name: 'order',
  fields: { /* ... */ },
  validations: [
    {
      name: 'discount_lt_total',
      message: 'Discount cannot exceed total',
      condition: 'discount < total',
    },
  ],
});

Validation runs on every write — REST, Console, ObjectQL — so there's no "back door."

Indexes & performance

ObjectSchema.create({
  name: 'order',
  fields: { /* ... */ },
  indexes: [
    { fields: ['status', 'created_at'] },
    { fields: ['account', 'created_at'], unique: false },
  ],
});

The driver creates real DB indexes on schema sync.

Field groups

For long forms, group fields in Console:

ObjectSchema.create({
  name: 'task',
  fieldGroups: [
    { key: 'core',     label: 'Task',     icon: 'check-square' },
    { key: 'planning', label: 'Planning', icon: 'calendar' },
    { key: 'meta',     label: 'Metadata', icon: 'info', defaultExpanded: false },
  ],
  fields: {
    subject: Field.text({ label: 'Subject', group: 'core' }),
    due:     Field.date({ label: 'Due',     group: 'planning' }),
  },
});

Lifecycle & ownership

ObjectSchema.create({
  name: 'task',
  ownership: 'own',          // 'own' | 'shared' | 'system'
  enable: {
    apiEnabled: true,         // generated REST endpoints
    trackHistory: true,       // audit log of field changes
    feeds: true,              // sys_comment / sys_activity / @mentions
    softDelete: true,         // tombstone instead of hard delete
  },
});

System objects (free with every project)

You don't have to declare these — they're always there:

Object What
sys_user User accounts
sys_org Organizations / tenants
sys_member Org membership
sys_role, sys_permission_set RBAC primitives
sys_audit_log Audit trail (when audit capability loaded)
sys_file, sys_attachment File metadata (when storage loaded)
sys_comment, sys_activity Feed / chatter (when feed loaded)
sys_session, sys_api_key Auth artifacts
sys_webhook, sys_webhook_delivery Webhook subs (when enabled)

Reference them in lookup fields by name — e.g. Field.lookup({ reference: 'sys_user' }).

Polymorphic platform features

When you enable feeds: true and trackHistory: true, your object automatically participates in:

  • sys_comment (thread_id = <object>:<id>)
  • sys_attachment (parent_object = <object>, parent_id = <id>)
  • sys_activity (timeline)
  • sys_audit_log (field-level diffs)

You don't wire these per object — they're polymorphic on the platform.

Where to go next