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
2 changes: 2 additions & 0 deletions content/docs/concepts/architecture.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Customer = ObjectSchema.create({
name: 'customer',
sharingModel: 'private',
label: 'Customer',
icon: 'building',

Expand DownExpand Up@@ -401,6 +402,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Opportunity = ObjectSchema.create({
name: 'opportunity',
sharingModel: 'private',
label: 'Opportunity',
icon: 'target',

Expand Down
8 changes: 7 additions & 1 deletion content/docs/concepts/metadata-driven.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const User = ObjectSchema.create({
name: 'user',
sharingModel: 'private',
label: 'User',
icon: 'user',

Expand DownExpand Up@@ -243,6 +244,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Task = ObjectSchema.create({
name: 'task',
sharingModel: 'private',
label: 'Task',
icon: 'check-square',

Expand DownExpand Up@@ -341,6 +343,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Account = ObjectSchema.create({
name: 'account',
sharingModel: 'private',
label: 'Account',
fields: {
name: Field.text({ required: true }),
Expand DownExpand Up@@ -387,6 +390,7 @@ Follow these strict naming conventions for consistency:
// ✅ Correct naming
export const TodoTask = ObjectSchema.create({
name: 'todo_task', // snake_case machine name
sharingModel: 'private', // OWD — required on custom objects
label: 'Todo Task',

fields: {
Expand DownExpand Up@@ -463,14 +467,15 @@ annual_revenue: Field.currency({
```typescript
export const Account = ObjectSchema.create({
name: 'account',
sharingModel: 'private',
label: 'Account',

fields: { /* ... */ },

enable: {
trackHistory: true, // Enable field history tracking
searchable: true, // Include in global search
apiEnabled: true, // Expose via REST/GraphQL
apiEnabled: true, // Expose object via automatic APIs (REST)
files: true, // Enable file attachments
feeds: true, // Enable activity feed
activities: true, // Enable tasks and events
Expand All@@ -486,6 +491,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const ExampleObject = ObjectSchema.create({
name: 'example_object', // Required: snake_case
sharingModel: 'private', // Required on custom objects: the OWD baseline
label: 'Example Object', // Required: Human-readable
pluralLabel: 'Example Objects', // Optional
icon: 'box', // Optional: Lucide icon name
Expand Down
1 change: 1 addition & 0 deletions content/docs/data-modeling/drivers.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -905,6 +905,7 @@ Then in your object definition:
```typescript
export const AuditLog = ObjectSchema.create({
name: 'audit_log',
sharingModel: 'private',
datasource: 'analytics', // Routes to the analytics database
fields: { /* ... */ },
});
Expand Down
5 changes: 5 additions & 0 deletions content/docs/data-modeling/external-datasources.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -72,6 +72,7 @@ and `external.columnMap`.
```typescript
export const Customer = ObjectSchema.create({
name: 'ext_customer',
sharingModel: 'private',
datasource: 'warehouse',
external: {
remoteName: 'customers', // remote TABLE name (object name may differ)
Expand DownExpand Up@@ -255,6 +256,10 @@ ObjectSchema.create({ /* ... */ external: { remoteName: 'orders', writable: true

With either gate off, insert/update/delete on the federated object is rejected.

The `/* ... */` above still has to carry a `sharingModel` — a custom (non-`sys_`) object
that declares no OWD is an **`os validate` error** (`security-owd-unset`), federated or
not. See the full object above.

<Callout type="warn">
**This gate is federation-only — it does nothing on a managed datasource.**
`allowWrites` answers *who owns this external database*, not *is this connection
Expand Down
6 changes: 3 additions & 3 deletions content/docs/data-modeling/field-types.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -314,7 +314,7 @@ Reference to a record in another object (foreign key).
| Property | Type | Default | Description |
|:---|:---|:---|:---|
| `reference` | `string` | **required** | Target object name (snake_case) |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property (unknown keys are stripped by the schema). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property. `FieldSchema` is a strict object, so an unknown key is **rejected with guidance**, not silently stripped (ADR-0078): the error echoes the offending key and prescribes the replacement. Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted. On a *required* lookup `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared — **whether the `set_null` was defaulted or written out explicitly**. On a `multiple: true` required lookup the escalation is judged per referencing row: only a row the member removal would leave EMPTY is refused. `cascade` and `restrict` are the values honored as written. Where `set_null` does run, a `multiple: true` lookup loses only the deleted **member** — the other members are kept, and a set emptied that way is stored as `[]`, never `null` |

```typescript
Expand All@@ -336,7 +336,7 @@ Parent-child relationship (cascading delete by default).
| Property | Type | Default | Description |
|:---|:---|:---|:---|
| `reference` | `string` | **required** | Target (master) object name |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property (unknown keys are stripped by the schema). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property. `FieldSchema` is a strict object, so an unknown key is **rejected with guidance**, not silently stripped (ADR-0078): the error echoes the offending key and prescribes the replacement. Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'cascade'` | Behavior when parent is deleted. `restrict` is the only value that deviates: master-detail cascades on everything else, so an explicit `set_null` here is **not** honored — the child is deleted with the parent |
| `inlineEdit` | `boolean \| 'grid' \| 'form'` | — | Edit child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) |
| `inlineColumns` | `array` | — | Optional explicit inline grid columns |
Expand DownExpand Up@@ -530,7 +530,7 @@ Name-keyed map of embedded sub-objects (`Record<string, SubObject>`). Insertion
## Enhanced Types

### `location`
Geographic coordinates. Stored as `{ latitude, longitude, altitude?, accuracy? }` (latitude −90..90, longitude −180..180). No per-type config properties.
Geographic coordinates. Stored as `{ lat, lng, altitude?, accuracy? }` (`lat` −90..90, `lng` −180..180). No per-type config properties. The key names are `lat`/`lng`, not `latitude`/`longitude` — see `LocationValueSchema` in `field-value.zod.ts` (ADR-0104 D1).

```typescript
{ name: 'headquarters', label: 'Location', type: 'location' }
Expand Down
1 change: 1 addition & 0 deletions content/docs/data-modeling/fields.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Contact = ObjectSchema.create({
name: 'contact',
sharingModel: 'private',
label: 'Contact',
fields: {
first_name: Field.text({ label: 'First Name', required: true }),
Expand Down
1 change: 1 addition & 0 deletions content/docs/data-modeling/formulas.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Invoice = ObjectSchema.create({
name: 'invoice',
sharingModel: 'private',
nameField: 'display_title', // ADR-0079 — the record title is a designated field; composite titles migrate off the deprecated `titleFormat` to a text formula
fields: {
// Composite record title as a text formula, surfaced via `nameField` above.
Expand Down
5 changes: 3 additions & 2 deletions content/docs/data-modeling/index.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Lead = ObjectSchema.create({
name: 'crm_lead',
sharingModel: 'public_read_write', // OWD — required on every custom object
label: 'Lead',
pluralLabel: 'Leads',
icon: 'funnel',
Expand All@@ -35,8 +36,8 @@ That one definition is enough to get a persisted table, CRUD + query endpoints,
- **A full spectrum of field types** — from `text`, `currency`, and `lookup`/`master_detail` relationships to `formula`, `summary`, `signature`, and `vector` (with `dimensions` config for AI embeddings). See the [Field Types gallery](/docs/data-modeling/field-types).
- **Validation as metadata** — required/format rules, CEL script validation with access to `previous.<field>`, uniqueness via indexes, and severity levels — enforced identically in API, UI, and automation.
- **CEL expressions** — formula fields and computed defaults share one expression language ([Expressions](/docs/data-modeling/formulas)).
- **A compiled query AST** — queries are JSON documents validated against the protocol, then compiled by a driver into native queries with joins, aggregations, window functions, HAVING, and subqueries. The [query cheat sheet](/docs/data-modeling/queries) covers the syntax; the [spec](/docs/protocol/objectql/query-syntax) is normative.
- **Four database drivers in this repo** — `driver-sql` (PostgreSQL / MySQL / SQLite via Knex), `driver-mongodb`, `driver-memory` (in-memory, for tests and demos), and `driver-sqlite-wasm` (SQLite in the browser / WebContainers). The same model runs unchanged on any of them.
- **A compiled query AST** — queries are JSON documents validated against the protocol, then compiled by a driver into native queries with aggregations, `groupBy`, `HAVING`, and subqueries. `joins` and request-surface `windowFunctions` were **retired in protocol 17** (#4286, ADR-0049) and are now rejected at parse; window functions survive only as a SQL-driver door (`SqlDriver.findWithWindowFunctions()`), not on the `IDataDriver` contract. The [query cheat sheet](/docs/data-modeling/queries) covers the syntax; the [spec](/docs/protocol/objectql/query-syntax) is normative.
- **Five database drivers in this repo** — `driver-sql` (PostgreSQL / MySQL / SQLite via Knex), `driver-mongodb`, `driver-memory` (in-memory, for tests and demos), `driver-sqlite-wasm` (SQLite in the browser / WebContainers), and `driver-turso` (Turso / libSQL — an **optional** install, because it pulls `@libsql/client` plus native bindings; see [Database Drivers](/docs/data-modeling/drivers)). The same model runs unchanged on any of them.
- **External datasource federation** — introspect an existing external database, import selected tables into the catalog, and query them alongside native objects ([External Datasources](/docs/data-modeling/external-datasources)).
- **Composable ownership** — one package owns an object, and any other package can merge fields, validation rules, and indexes into it without forking the definition ([Object Extensions](/docs/data-modeling/object-extensions)).
- **Repeatable bulk import** — a named mapping projects someone else's column headers and codes onto your fields, and the import endpoint applies it by name ([Import Mappings](/docs/data-modeling/import-mappings)).
Expand Down
4 changes: 3 additions & 1 deletion content/docs/data-modeling/objects.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Account = ObjectSchema.create({
name: 'account',
sharingModel: 'private',
label: 'Account',
pluralLabel: 'Accounts',
icon: 'building',
Expand DownExpand Up@@ -79,7 +80,7 @@ Control which platform features are active for this object:
enable: {
trackHistory: true, // Field history tracking for audit
searchable: true, // Include in global search index
apiEnabled: true, // Expose via REST/GraphQL APIs
apiEnabled: true, // Expose object via automatic APIs (REST)
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
files: true, // File attachments
feeds: true, // Activity feed and comments
Expand DownExpand Up@@ -373,6 +374,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';

export const ProjectTask = ObjectSchema.create({
name: 'project_task',
sharingModel: 'private',
label: 'Project Task',
pluralLabel: 'Project Tasks',
icon: 'check-square',
Expand Down
8 changes: 6 additions & 2 deletions content/docs/data-modeling/schema-design.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
export const MyObject = ObjectSchema.create({
// Metadata
name: 'my_object', // Machine name (snake_case)
sharingModel: 'private', // OWD baseline — REQUIRED on custom objects
label: 'My Object', // Display name
pluralLabel: 'My Objects', // Plural form
icon: 'briefcase', // Icon identifier
Expand DownExpand Up@@ -67,13 +68,14 @@ Control which features are available for an object:
enable: {
trackHistory: true, // Track field changes over time
searchable: true, // Include in global search
apiEnabled: true, // Expose via REST/GraphQL
apiEnabled: true, // Expose object via automatic APIs (REST)
apiMethods: [ // Whitelist API operations — six primitives only;
'get', // search/export/… DERIVE from them (list grants
'list', // aggregate/search/export; create+update grants
'create', // upsert/import). undefined = all, [] = none.
'update',
'delete'
'delete',
'bulk' // omitting `bulk` DENIES batch operations
],
files: true, // Allow file attachments
feeds: true, // Enable activity feed (Chatter-like)
Expand DownExpand Up@@ -284,6 +286,7 @@ import { ObjectSchema } from '@objectstack/spec/data';

export const Account = ObjectSchema.create({
name: 'account',
sharingModel: 'private',
label: 'Account',

fieldGroups: [
Expand DownExpand Up@@ -403,6 +406,7 @@ here (ADR-0085).
```typescript
export const Account = ObjectSchema.create({
name: 'account',
sharingModel: 'private',
label: 'Account',
pluralLabel: 'Accounts',
icon: 'building',
Expand Down
1 change: 1 addition & 0 deletions content/docs/data-modeling/seed-data.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -246,6 +246,7 @@ seed value is an **array of natural keys**. Each element resolves independently.
```typescript
const Book = ObjectSchema.create({
name: 'book',
sharingModel: 'private',
fields: {
name: Field.text({ label: 'Title', required: true }),
authors: Field.lookup('author', { label: 'Authors', multiple: true }),
Expand Down
Loading
Loading