Skip to content

Commit 38c4559

Browse files
committed
docs: expand reference guides and architecture overview
1 parent 7605aba commit 38c4559

9 files changed

Lines changed: 1024 additions & 5 deletions

File tree

content/docs/architecture.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ description: What you're actually running — for the engineer evaluating whethe
88
A practical view of what runs on your machines when you deploy
99
ObjectOS, what data leaves your network, and what doesn't.
1010

11+
The mental model is two thin layers:
12+
13+
1. **Metadata** — packages of objects / views / actions / flows /
14+
agents. Mostly written by the [AI Builder](/docs/build/ai-builder)
15+
against a sandboxed tool API; sometimes hand-edited, always
16+
version-controlled and audited.
17+
2. **A single Node.js runtime** that interprets that metadata into a
18+
working application — REST API, Studio UI, permissions, jobs, AI
19+
tools — all in one process, talking to your database.
20+
21+
No code generation step, no deploy pipeline between "user described
22+
what they want" and "it's live". The runtime hot-loads the new
23+
metadata after a HITL approval.
24+
1125
## What you deploy
1226

1327
One Node.js process per ObjectOS instance. That's it.

content/docs/reference/cel.mdx

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
title: CEL Expressions
3+
description: The expression language used for formulas, predicates, schedules, and templated strings — surfaced via five tagged templates.
4+
---
5+
6+
# CEL Expressions
7+
8+
ObjectOS uses [CEL](https://github.com/google/cel-spec) (Common
9+
Expression Language) for every place where you need a small, safe,
10+
sandboxed expression: formula fields, validation rules, visibility
11+
predicates, sharing conditions, flow guards, schedules, and templated
12+
strings.
13+
14+
Authoring is done through **five tagged templates** imported from
15+
`@objectstack/spec`. They all produce a small JSON object the runtime
16+
parses:
17+
18+
```ts
19+
{ dialect: 'cel' | 'template' | 'cron', source: string }
20+
```
21+
22+
Schema source:
23+
[`packages/spec/src/shared/expression.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/shared/expression.zod.ts).
24+
25+
## The five tagged templates
26+
27+
| Template | Dialect | Use it for | Example |
28+
|:--|:--|:--|:--|
29+
| `` F`...` `` | `cel` | **Formula fields** — derived values stored alongside the record | `` F`record.amount * 0.1` `` |
30+
| `` P`...` `` | `cel` | **Predicates** — booleans for validation / sharing / visibility / conditions | `` P`record.status == "open"` `` |
31+
| `` cel`...` `` | `cel` | **General CEL** — when neither F nor P fits (e.g. a parameter value) | `` cel`now() + duration("P30D")` `` |
32+
| `` tmpl`...` `` | `template` | **String templates** with `{{var}}` interpolation | `` tmpl`Order from {{record.customer.name}}` `` |
33+
| `` cron`...` `` | `cron` | **Schedules** — standard 5-field cron syntax | `` cron`0 9 * * 1-5` `` |
34+
35+
There is no functional difference between `F`, `P`, and `cel` at
36+
evaluation time — they all run CEL. The split exists so that schemas
37+
(and AI agents) know what role the expression plays and the editor can
38+
type-check (formulas must return a value, predicates must return a
39+
bool).
40+
41+
### Import
42+
43+
```ts
44+
import { F, P, cel, tmpl, cron } from '@objectstack/spec'
45+
```
46+
47+
## Where each one is used
48+
49+
| Field on a spec | Tag | Example location |
50+
|:--|:--|:--|
51+
| `Field.expression` (formula type) | `F` | `*.object.ts` formula fields |
52+
| `Field.conditionalRequired` | `P` | object fields |
53+
| `Validation.predicate` | `P` | object validations |
54+
| `SharingRule.condition` | `P` | sharing rules |
55+
| `View.conditionalFormatting[].condition` | `P` | views |
56+
| `Flow.step.when` / `Flow.transition.when` | `P` | flows |
57+
| `Action.guard` | `P` | actions |
58+
| Notification subjects / message bodies | `tmpl` | notifications |
59+
| `Schedule.cron` | `cron` | scheduled flows / reports |
60+
| Any value parameter | `cel` | flow step inputs |
61+
62+
## Variable scope
63+
64+
CEL expressions evaluate in a context with these top-level variables:
65+
66+
| Variable | Available when | Contents |
67+
|:--|:--|:--|
68+
| `record` | almost always | The current record being evaluated |
69+
| `previous` | on update hooks / change-detection | The pre-change state of the record (or `null`) |
70+
| `input` | actions, flow steps | The user-supplied input payload |
71+
| `os.user` | always | `{ id, roles: string[], permissions: string[] }` |
72+
| `os.org` | always | Organisation / tenant context |
73+
| `os.env` | always | Environment variables exposed to expressions |
74+
75+
> Legacy `OLD` / `NEW` variables were removed in M9.5. Use `previous`
76+
> and `record`.
77+
78+
## Standard library
79+
80+
Registered in
81+
[`packages/formula/src/stdlib.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/formula/src/stdlib.ts).
82+
The most-used built-ins:
83+
84+
### Time
85+
86+
| Function | Returns | Notes |
87+
|:--|:--|:--|
88+
| `now()` | `Timestamp` | Pinned to the evaluation context — stable within one query |
89+
| `today()` | `Timestamp` | Start of UTC day |
90+
| `daysFromNow(int)` | `Timestamp` | Future date |
91+
| `daysAgo(int)` | `Timestamp` | Past date |
92+
93+
CEL also includes the native `timestamp(...)`, `duration(...)`,
94+
`date.getDayOfWeek()`, etc. — see the
95+
[CEL spec](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions).
96+
97+
### Utility
98+
99+
| Function | Purpose |
100+
|:--|:--|
101+
| `isBlank(x)` | `true` if `null`, `undefined`, `""`, or empty list |
102+
| `coalesce(a, b)` | First non-null |
103+
| `trim(s)` | Strip whitespace |
104+
| `joinNonEmpty(list, sep)` | Concatenate non-empty entries |
105+
106+
Native CEL string helpers (`.contains(...)`, `.startsWith(...)`,
107+
`.matches(...)`, `.size()`) are always available.
108+
109+
## Examples
110+
111+
**Formula field** — line-item total:
112+
113+
```ts
114+
{ name: 'subtotal', type: 'formula', expression: F`record.quantity * record.unit_price` }
115+
```
116+
117+
**Validation** — close date must be after today:
118+
119+
```ts
120+
{ message: 'Close date must be in the future', predicate: P`record.close_date > today()` }
121+
```
122+
123+
**Visibility** — show field only to managers:
124+
125+
```ts
126+
{ visibleIf: P`'manager' in os.user.roles` }
127+
```
128+
129+
**Flow guard** — skip step when amount is small:
130+
131+
```ts
132+
{ when: P`record.amount >= 1000` }
133+
```
134+
135+
**Schedule** — weekday 9am:
136+
137+
```ts
138+
{ schedule: cron`0 9 * * 1-5` }
139+
```
140+
141+
**Template** — notification subject:
142+
143+
```ts
144+
{ subject: tmpl`[{{record.priority}}] {{record.subject}}` }
145+
```
146+
147+
## Errors
148+
149+
Expressions are compiled at load time. Failures show up as
150+
`VALIDATION_ERROR` with the source location:
151+
152+
```json
153+
{ "code": "VALIDATION_ERROR", "message": "CEL: unknown field 'amout' on Record", "details": { "field": "subtotal", "expression": "record.amout * 0.1" } }
154+
```
155+
156+
A failed evaluation at runtime is treated as `null` for formula fields
157+
and `false` for predicates, plus an entry in the audit log.
158+
159+
## See also
160+
161+
- [Field types](./field-types) — formula and conditional-required fields
162+
- [Build → Data model](/docs/build/data-model) — validations and predicates
163+
- [Build → Flows](/docs/build/flows) — guards and schedules
164+
- [`@objectstack/spec/shared/expression.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/shared/expression.zod.ts) — schema
165+
- [`@objectstack/formula/stdlib.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/formula/src/stdlib.ts) — built-ins
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
title: Field Types
3+
description: Every field type you can declare on an object — what it stores, what options it accepts, how it surfaces in REST, Studio, and the AI Builder.
4+
---
5+
6+
# Field Types
7+
8+
53 built-in field types, grouped by family. The full Zod schema is in
9+
[`packages/spec/src/data/field.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/data/field.zod.ts) — this page is a working summary.
10+
11+
## Core properties (every field)
12+
13+
| Property | Type | Default | Purpose |
14+
|:--|:--|:--|:--|
15+
| `name` | string (snake_case) || Machine identifier — REST path segment, SQL column |
16+
| `label` | string || Display label in Studio |
17+
| `type` | FieldType || See type tables below |
18+
| `required` | boolean | `false` | NOT NULL constraint |
19+
| `unique` | boolean | `false` | Unique index |
20+
| `searchable` | boolean | `false` | Indexed for `/api/v1/search` |
21+
| `multiple` | boolean | `false` | Store an array of values |
22+
| `defaultValue` | unknown || Initial value (literal or CEL) |
23+
| `columnName` | string | = `name` | Override physical DB column |
24+
| `hidden` | boolean | `false` | Hide from default Studio views |
25+
| `readonly` | boolean | `false` | Disable in forms |
26+
| `system` | boolean | `false` | Auto-injected (id, created_at, …) |
27+
| `index` | boolean | `false` | Create a DB index |
28+
| `externalId` | boolean | `false` | Eligible for upsert via external key |
29+
| `inlineHelpText` | string || Tooltip / helper text |
30+
| `conditionalRequired` | `P` predicate || Required when CEL is true |
31+
| `auditTrail` | boolean | `false` | Track every change in audit log |
32+
| `encryptionConfig` | object || Field-level encryption (see [Security](./security)) |
33+
| `maskingRule` | string || PII masking pattern |
34+
35+
## Text family
36+
37+
| Type | Use for | Key options |
38+
|:--|:--|:--|
39+
| `text` | short strings | `maxLength`, `minLength` |
40+
| `textarea` | multi-line | `maxLength` |
41+
| `email` | email | format-validated; lowercased |
42+
| `url` | URL | format-validated |
43+
| `phone` | phone | E.164 |
44+
| `password` | secrets | hashed; never returned by GET |
45+
| `markdown` | markdown body | rendered in Studio preview |
46+
| `html` | sanitised HTML | DOMPurify on write |
47+
| `richtext` | WYSIWYG | Studio editor + serialised JSON |
48+
49+
## Numbers
50+
51+
| Type | Use for | Key options |
52+
|:--|:--|:--|
53+
| `number` | floats | `min`, `max`, `precision`, `scale` |
54+
| `currency` | money | `currencyConfig: { precision, currencyMode: 'fixed' \| 'dynamic', defaultCurrency }` |
55+
| `percent` | 0–100 % | `min`, `max`, `scale` |
56+
57+
> `integer` and `decimal` aren't separate types — use `number` with
58+
> `scale: 0` for integer, `precision`+`scale` for fixed decimal.
59+
60+
## Date / time
61+
62+
| Type | Stores | Notes |
63+
|:--|:--|:--|
64+
| `date` | calendar date | no timezone |
65+
| `datetime` | instant | UTC |
66+
| `time` | wall-clock time | no date |
67+
68+
## Logic
69+
70+
| Type | Notes |
71+
|:--|:--|
72+
| `boolean` | Checkbox |
73+
| `toggle` | Same as boolean, switch UI |
74+
75+
## Selection
76+
77+
| Type | Notes |
78+
|:--|:--|
79+
| `select` | Single choice — options declared inline or referenced from a picklist |
80+
| `multiselect` | Many choices, stored as an array |
81+
| `radio` | UI alias for `select` with radio rendering |
82+
| `checkboxes` | UI alias for `multiselect` with checkbox rendering |
83+
84+
Options shape:
85+
86+
```ts
87+
options: [
88+
{ value: 'low', label: 'Low' },
89+
{ value: 'high', label: 'High', color: '#e02' },
90+
{ value: 'urgent', label: 'Urgent', color: '#c00' }
91+
]
92+
```
93+
94+
## Relations
95+
96+
| Type | Cardinality | Semantics |
97+
|:--|:--|:--|
98+
| `lookup` | many-to-one | Loose reference; deleting the parent doesn't delete the child by default |
99+
| `master_detail` | many-to-one, cascading | Child cannot exist without parent; permissions inherit from parent |
100+
| `tree` | self-reference | Hierarchical (parent_id on the same object) |
101+
102+
Common options:
103+
104+
```ts
105+
{
106+
type: 'lookup',
107+
reference: 'account', // target object name
108+
referenceFilters: ['status:active'], // narrow the lookup picker
109+
deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict'
110+
}
111+
```
112+
113+
## Computed
114+
115+
| Type | What it does | Key option |
116+
|:--|:--|:--|
117+
| `formula` | Derived value, evaluated on read or recalc | `expression: F\`record.qty * record.unit_price\`` — see [CEL](./cel) |
118+
| `summary` | Roll-up over a child relation | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) |
119+
| `autonumber` | Auto-incrementing display number | `format` (e.g. `TKT-{0000}`), `startAt` |
120+
121+
Formula example:
122+
123+
```ts
124+
{
125+
name: 'profit_margin',
126+
type: 'formula',
127+
expression: F`(record.revenue - record.cost) / record.revenue * 100`
128+
}
129+
```
130+
131+
## Media
132+
133+
| Type | Use for | Key option |
134+
|:--|:--|:--|
135+
| `image` | image attachments | `fileAttachmentConfig` (see below) |
136+
| `file` | any file | same |
137+
| `avatar` | profile pictures | square crop, sensible defaults |
138+
| `video` | video uploads | duration + thumbnail capture |
139+
| `audio` | audio | waveform preview |
140+
141+
```ts
142+
fileAttachmentConfig: {
143+
maxSize: 10_000_000, // bytes
144+
allowedTypes: ['image/png','image/jpeg'],
145+
virusScan: true,
146+
storageProvider: 's3', // see Configure → Storage
147+
imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] }
148+
}
149+
```
150+
151+
## Structured
152+
153+
| Type | Stores | Notes |
154+
|:--|:--|:--|
155+
| `json` | arbitrary JSON | Stored as JSONB on Postgres |
156+
| `composite` | sub-record with named fields | Inline struct, not a separate table |
157+
| `repeater` | array of composite values | One-to-many without a child object |
158+
159+
## Enhanced UI
160+
161+
| Type | Notes |
162+
|:--|:--|
163+
| `location` | lat/long + accuracy |
164+
| `address` | street / city / region / postal / country |
165+
| `code` | source-code field — `language`, `theme`, `lineNumbers` |
166+
| `color` | `colorFormat: 'hex' \| 'rgb' \| 'rgba' \| 'hsl'`, `presetColors[]` |
167+
| `rating` | 1–N stars — `max`, `icon` |
168+
| `slider` | bounded number with slider UI |
169+
| `signature` | drawn signature, stored as image |
170+
| `qrcode` | renders the value as a QR — `qrErrorCorrection: 'L' \| 'M' \| 'Q' \| 'H'` |
171+
| `barcode` | EAN/UPC/Code128 — `barcodeFormat` |
172+
| `progress` | derived percent rendered as a bar |
173+
| `tags` | free-form tag array with autocomplete |
174+
| `vector` | embedding column — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` |
175+
176+
## System fields (auto-injected on every object)
177+
178+
| Field | Type | Notes |
179+
|:--|:--|:--|
180+
| `id` | text (ULID) | primary key |
181+
| `created_at` | datetime | UTC insert time |
182+
| `updated_at` | datetime | UTC last-write time |
183+
| `created_by` | lookup → `user` | who inserted |
184+
| `updated_by` | lookup → `user` | who last wrote |
185+
| `version` | integer | optimistic-concurrency token |
186+
187+
You don't declare these — opt out per object with
188+
`ObjectSpec.systemFields: false` (rarely a good idea).
189+
190+
## How fields flow through the stack
191+
192+
```
193+
*.object.ts (field spec)
194+
195+
├─► Postgres / MySQL / SQLite column + index + constraint
196+
├─► REST: validated on POST/PATCH, exposed on GET
197+
├─► Studio: form widget + list column
198+
├─► AI Builder: tool argument schema (so the AI knows what to ask)
199+
└─► Audit: change-tracked if `auditTrail: true`
200+
```
201+
202+
## See also
203+
204+
- [Build → Data model](/docs/build/data-model) — composing fields into objects
205+
- [CEL](./cel)`expression`, `conditionalRequired`, validations
206+
- [ObjectQL](./objectql) — querying these fields
207+
- [REST API](./rest-api) — endpoints that produce / consume them
208+
- [`@objectstack/spec/data/field.zod.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/spec/src/data/field.zod.ts) — authoritative schema

0 commit comments

Comments
 (0)