Skip to content

Commit 04236f6

Browse files
authored
Merge pull request #536 from objectstack-ai/copilot/update-all-docs-with-typescript
2 parents 264ff22 + b498e54 commit 04236f6

18 files changed

Lines changed: 936 additions & 334 deletions

File tree

.github/prompts/example-creator.prompt.md

Lines changed: 161 additions & 184 deletions
Large diffs are not rendered by default.

content/docs/introduction/architecture.mdx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,37 @@ ObjectStack enforces **Separation of Concerns** through protocol boundaries:
8484

8585
```typescript
8686
// packages/crm/src/objects/customer.object.ts
87-
import { Object, Field } from '@objectstack/spec';
87+
import { ObjectSchema, Field } from '@objectstack/spec/data';
8888

89-
export const Customer = Object({
89+
export const Customer = ObjectSchema.create({
9090
name: 'customer',
9191
label: 'Customer',
92+
icon: 'building',
93+
9294
fields: {
9395
name: Field.text({
9496
label: 'Company Name',
9597
required: true,
9698
maxLength: 120,
9799
}),
100+
98101
industry: Field.select({
99102
label: 'Industry',
100-
options: ['technology', 'finance', 'healthcare', 'retail'],
103+
options: [
104+
{ label: 'Technology', value: 'technology' },
105+
{ label: 'Finance', value: 'finance' },
106+
{ label: 'Healthcare', value: 'healthcare' },
107+
{ label: 'Retail', value: 'retail' },
108+
],
101109
}),
110+
102111
annual_revenue: Field.currency({
103112
label: 'Annual Revenue',
113+
scale: 2,
104114
}),
105-
primary_contact: Field.lookup({
115+
116+
primary_contact: Field.lookup('contact', {
106117
label: 'Primary Contact',
107-
object: 'contact',
108118
}),
109119
},
110120
});
@@ -383,15 +393,37 @@ Here's how all three protocols collaborate for a **Kanban Board** feature:
383393
### 1. ObjectQL: Define the Data
384394

385395
```typescript
386-
export const Opportunity = Object({
396+
import { ObjectSchema, Field } from '@objectstack/spec/data';
397+
398+
export const Opportunity = ObjectSchema.create({
387399
name: 'opportunity',
400+
label: 'Opportunity',
401+
icon: 'target',
402+
388403
fields: {
389-
title: Field.text({ required: true }),
404+
title: Field.text({
405+
label: 'Title',
406+
required: true,
407+
}),
408+
390409
stage: Field.select({
391-
options: ['prospecting', 'qualification', 'proposal', 'closed_won'],
410+
label: 'Stage',
411+
options: [
412+
{ label: 'Prospecting', value: 'prospecting', default: true },
413+
{ label: 'Qualification', value: 'qualification' },
414+
{ label: 'Proposal', value: 'proposal' },
415+
{ label: 'Closed Won', value: 'closed_won' },
416+
],
417+
}),
418+
419+
amount: Field.currency({
420+
label: 'Amount',
421+
scale: 2,
422+
}),
423+
424+
customer: Field.lookup('customer', {
425+
label: 'Customer',
392426
}),
393-
amount: Field.currency(),
394-
customer: Field.lookup({ object: 'customer' }),
395427
},
396428
});
397429
```

content/docs/introduction/metadata-driven.mdx

Lines changed: 246 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,13 @@ ObjectStack centralizes the "Intent" into a **single Protocol Definition**. The
6666
6767
```typescript
6868
// ONE definition (in objectstack.config.ts)
69-
export const User = Object({
69+
import { ObjectSchema, Field } from '@objectstack/spec/data';
70+
71+
export const User = ObjectSchema.create({
7072
name: 'user',
73+
label: 'User',
74+
icon: 'user',
75+
7176
fields: {
7277
phone: Field.phone({
7378
label: 'Phone Number',
@@ -77,6 +82,8 @@ export const User = Object({
7782
});
7883
```
7984

85+
> **📘 Syntax Rules**: Always use `ObjectSchema.create()` with `Field.*` helpers for strict TypeScript validation and runtime checking. See [Object Definition Rules](#object-definition-rules) below.
86+
8087
From this single definition, ObjectStack automatically:
8188

8289
✅ Generates database schema
@@ -188,14 +195,31 @@ React Flutter
188195
189196
```typescript
190197
// All you need:
191-
export const Task = Object({
198+
import { ObjectSchema, Field } from '@objectstack/spec/data';
199+
200+
export const Task = ObjectSchema.create({
192201
name: 'task',
202+
label: 'Task',
203+
icon: 'check-square',
204+
193205
fields: {
194-
title: Field.text({ required: true }),
206+
title: Field.text({
207+
label: 'Title',
208+
required: true,
209+
}),
210+
195211
status: Field.select({
196-
options: ['todo', 'in_progress', 'done'],
212+
label: 'Status',
213+
options: [
214+
{ label: 'To Do', value: 'todo', default: true },
215+
{ label: 'In Progress', value: 'in_progress' },
216+
{ label: 'Done', value: 'done' },
217+
],
218+
}),
219+
220+
assignee: Field.lookup('user', {
221+
label: 'Assignee',
197222
}),
198-
assignee: Field.lookup({ object: 'user' }),
199223
},
200224
});
201225
@@ -246,6 +270,223 @@ You specify **exactly how** to draw each pixel.
246270
| **Flexibility** | Locked to tech stack | Technology agnostic |
247271
| **Boilerplate** | High (300+ lines) | Low (30 lines) |
248272

273+
## Object Definition Rules
274+
275+
When defining objects and metadata in ObjectStack, follow these strict rules and principles:
276+
277+
### 1. Always Use `ObjectSchema.create()` with `Field.*` Helpers
278+
279+
**✅ Correct:**
280+
```typescript
281+
import { ObjectSchema, Field } from '@objectstack/spec/data';
282+
283+
export const Account = ObjectSchema.create({
284+
name: 'account',
285+
label: 'Account',
286+
fields: {
287+
name: Field.text({ required: true }),
288+
industry: Field.select({
289+
options: [
290+
{ label: 'Technology', value: 'technology' },
291+
{ label: 'Finance', value: 'finance' },
292+
],
293+
}),
294+
},
295+
});
296+
```
297+
298+
**❌ Deprecated:**
299+
```typescript
300+
// Old pattern - no runtime validation
301+
const Account: ServiceObject = {
302+
name: 'account',
303+
fields: {
304+
name: { type: 'text', required: true }
305+
}
306+
};
307+
```
308+
309+
**Why?**
310+
-**Type Safety**: Compile-time type checking via `z.input<typeof ObjectSchemaBase>`
311+
-**Runtime Validation**: Zod validates structure at runtime
312+
-**IDE Autocomplete**: Field helpers provide intelligent code completion
313+
-**Error Prevention**: Catches typos and invalid configurations immediately
314+
315+
### 2. Naming Conventions
316+
317+
Follow these strict naming conventions for consistency:
318+
319+
| Element | Convention | Examples |
320+
|---------|-----------|----------|
321+
| **Object Names** (machine names) | `snake_case` | `todo_task`, `project_milestone`, `user_profile` |
322+
| **Field Names** (machine names) | `snake_case` | `first_name`, `annual_revenue`, `is_active` |
323+
| **Constant Names** (exports) | `PascalCase` | `TodoTask`, `ProjectMilestone`, `UserProfile` |
324+
| **Configuration Keys** (props) | `camelCase` | `maxLength`, `defaultValue`, `referenceFilters` |
325+
326+
**Example:**
327+
```typescript
328+
// ✅ Correct naming
329+
export const TodoTask = ObjectSchema.create({
330+
name: 'todo_task', // snake_case machine name
331+
label: 'Todo Task',
332+
333+
fields: {
334+
due_date: Field.date({ // snake_case field name
335+
label: 'Due Date',
336+
defaultValue: null, // camelCase config key
337+
}),
338+
},
339+
});
340+
```
341+
342+
### 3. Select Field Options Must Use Label/Value Objects
343+
344+
**✅ Correct:**
345+
```typescript
346+
status: Field.select({
347+
label: 'Status',
348+
options: [
349+
{ label: 'Open', value: 'open', default: true },
350+
{ label: 'In Progress', value: 'in_progress' },
351+
{ label: 'Closed', value: 'closed' },
352+
],
353+
}),
354+
```
355+
356+
**❌ Incorrect:**
357+
```typescript
358+
status: Field.select({
359+
options: ['open', 'in_progress', 'closed'], // Wrong!
360+
}),
361+
```
362+
363+
**Why?** Option values are machine identifiers stored in the database and must be lowercase to avoid case-sensitivity issues in queries.
364+
365+
### 4. Lookup Fields Must Specify Target Object
366+
367+
**✅ Correct:**
368+
```typescript
369+
owner: Field.lookup('user', {
370+
label: 'Owner',
371+
required: true,
372+
}),
373+
```
374+
375+
**❌ Incorrect:**
376+
```typescript
377+
owner: Field.lookup({
378+
object: 'user', // Wrong property name
379+
}),
380+
```
381+
382+
### 5. Always Include Descriptive Labels
383+
384+
**✅ Correct:**
385+
```typescript
386+
annual_revenue: Field.currency({
387+
label: 'Annual Revenue',
388+
scale: 2,
389+
min: 0,
390+
}),
391+
```
392+
393+
**❌ Avoid:**
394+
```typescript
395+
annual_revenue: Field.currency({
396+
// Missing label - field name will be used as fallback
397+
}),
398+
```
399+
400+
### 6. Use Enable Flags for Object Capabilities
401+
402+
```typescript
403+
export const Account = ObjectSchema.create({
404+
name: 'account',
405+
label: 'Account',
406+
407+
fields: { /* ... */ },
408+
409+
enable: {
410+
trackHistory: true, // Enable field history tracking
411+
searchable: true, // Include in global search
412+
apiEnabled: true, // Expose via REST/GraphQL
413+
files: true, // Enable file attachments
414+
feeds: true, // Enable activity feed
415+
activities: true, // Enable tasks and events
416+
trash: true, // Enable soft delete
417+
mru: true, // Track Most Recently Used
418+
},
419+
});
420+
```
421+
422+
### Quick Reference
423+
424+
```typescript
425+
import { ObjectSchema, Field } from '@objectstack/spec/data';
426+
427+
export const ExampleObject = ObjectSchema.create({
428+
name: 'example_object', // Required: snake_case
429+
label: 'Example Object', // Required: Human-readable
430+
pluralLabel: 'Example Objects', // Optional
431+
icon: 'box', // Optional: Lucide icon name
432+
description: 'Description text', // Optional
433+
434+
fields: {
435+
// Text field
436+
text_field: Field.text({
437+
label: 'Text Field',
438+
required: true,
439+
maxLength: 255,
440+
}),
441+
442+
// Number field
443+
number_field: Field.number({
444+
label: 'Number',
445+
min: 0,
446+
max: 100,
447+
}),
448+
449+
// Currency field
450+
price: Field.currency({
451+
label: 'Price',
452+
scale: 2,
453+
min: 0,
454+
}),
455+
456+
// Select field
457+
status: Field.select({
458+
label: 'Status',
459+
options: [
460+
{ label: 'Active', value: 'active', default: true },
461+
{ label: 'Inactive', value: 'inactive' },
462+
],
463+
}),
464+
465+
// Lookup field
466+
owner: Field.lookup('user', {
467+
label: 'Owner',
468+
required: true,
469+
}),
470+
471+
// Boolean field
472+
is_active: Field.boolean({
473+
label: 'Active',
474+
defaultValue: true,
475+
}),
476+
477+
// Date field
478+
due_date: Field.date({
479+
label: 'Due Date',
480+
}),
481+
},
482+
483+
enable: {
484+
trackHistory: true,
485+
apiEnabled: true,
486+
},
487+
});
488+
```
489+
249490
## Next Steps
250491

251492
- [The Stack](/docs/core-concepts/the-stack) - How the three protocols work together

0 commit comments

Comments
 (0)