diff --git a/apps/site/content/docs/getting-started/index.mdx b/apps/site/content/docs/getting-started/index.mdx index cb22746..2bdbf83 100644 --- a/apps/site/content/docs/getting-started/index.mdx +++ b/apps/site/content/docs/getting-started/index.mdx @@ -27,4 +27,4 @@ Before you begin, ensure you have: ## Next Steps -Continue to [Installation](/getting-started/installation) to set up your development environment. +Continue to [Installation](./installation.mdx) to set up your development environment. diff --git a/apps/site/content/docs/guide/architecture.mdx b/apps/site/content/docs/guide/architecture.mdx new file mode 100644 index 0000000..0c99356 --- /dev/null +++ b/apps/site/content/docs/guide/architecture.mdx @@ -0,0 +1,788 @@ +--- +title: Architecture Deep Dive +--- + +# Architecture Deep Dive + +This guide explains the internal architecture of ObjectOS, how the components interact, and the design principles behind the framework. + +## Overview + +ObjectOS implements a clean **three-layer architecture** following the principle: + +> **"Kernel handles logic, Drivers handle data, Server handles HTTP."** + +This separation provides: +- **Testability**: Each layer can be tested independently +- **Flexibility**: Swap databases without changing business logic +- **Scalability**: Scale each layer independently +- **Maintainability**: Clear boundaries reduce coupling + +## The Layers + +``` +┌─────────────────────────────────────────────────┐ +│ Layer 1: Metadata Protocol (ObjectQL) │ +│ - YAML definitions │ +│ - Type system │ +│ - Validation rules │ +└────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ Layer 2: Runtime Engine (@objectos/kernel) │ +│ - Object registry │ +│ - Permission enforcement │ +│ - Hook execution │ +│ - Query dispatcher │ +└────────────────┬────────────────────────────────┘ + │ + ┌────────────┴──────────────┐ + ▼ ▼ +┌─────────────────┐ ┌─────────────────────────┐ +│ Data Layer │ │ Application Layer │ +│ (Drivers) │ │ (@objectos/server) │ +│ - PostgreSQL │ │ - REST API │ +│ - MongoDB │ │ - Authentication │ +│ - SQLite │ │ - Rate limiting │ +└─────┬───────────┘ └───────────┬─────────────┘ + │ │ + ▼ ▼ +┌─────────────┐ ┌──────────────┐ +│ Database │ │ React UI │ +└─────────────┘ └──────────────┘ +``` + +## Layer 1: Metadata Protocol + +### What is Metadata? + +Metadata is "data about data" - it describes the structure, relationships, and behavior of your business objects. + +### ObjectQL YAML Format + +```yaml +# Object definition +name: contacts # API name (snake_case) +label: Contact # Display name +icon: user # UI icon +description: A person # Documentation + +# Field definitions +fields: + first_name: + type: text # Data type + label: First Name # Display label + required: true # Validation rule + max_length: 100 # Constraint + + email: + type: email + unique: true # Database constraint + index: true # Performance optimization + + account: + type: lookup # Relationship type + reference_to: accounts # Target object + on_delete: set_null # Cascade behavior + +# Permissions +permission_set: + allowRead: true # Public read + allowCreate: ['sales', 'admin'] # Role-based create + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] # Admin only + +# Triggers +triggers: + - name: validate_email + when: before_insert + function: validateEmailDomain +``` + +### Type System + +ObjectOS supports rich data types: + +| Type | Storage | Validation | +|------|---------|------------| +| `text` | VARCHAR(255) | Max length | +| `textarea` | TEXT | Max length | +| `number` | NUMERIC | Min/max, precision | +| `currency` | DECIMAL(18,2) | Min/max, scale | +| `date` | DATE | Date range | +| `datetime` | TIMESTAMP | Date range | +| `boolean` | BOOLEAN | True/false only | +| `select` | VARCHAR | Enum options | +| `lookup` | Foreign Key | Reference exists | +| `email` | VARCHAR | Email format | +| `url` | VARCHAR | URL format | +| `phone` | VARCHAR | Phone format | + +## Layer 2: Runtime Engine (Kernel) + +### Architecture + +```typescript +┌─────────────────────────────────────┐ +│ ObjectOS (Main Class) │ +├─────────────────────────────────────┤ +│ - registry: ObjectRegistry │ +│ - driver: ObjectQLDriver │ +│ - hooks: HookManager │ +│ - permissions: PermissionChecker │ +└─────────────────────────────────────┘ + │ + ├──> ObjectRegistry + │ └─ Map + │ + ├──> HookManager + │ └─ Map + │ + ├──> PermissionChecker + │ └─ checkPermission(user, object, action) + │ + └──> ObjectQLDriver + └─ Database operations +``` + +### Key Responsibilities + +#### 1. Object Registry + +Maintains metadata in memory for fast access: + +```typescript +class ObjectRegistry { + private objects = new Map(); + + register(config: ObjectConfig): void { + // Validate schema + this.validate(config); + + // Store in registry + this.objects.set(config.name, config); + + // Index fields for quick lookup + this.indexFields(config); + } + + get(name: string): ObjectConfig { + const config = this.objects.get(name); + if (!config) { + throw new ObjectNotFoundError(name); + } + return config; + } +} +``` + +#### 2. Query Dispatcher + +Translates high-level queries to driver calls: + +```typescript +async find(objectName: string, options: FindOptions): Promise { + // 1. Get metadata + const config = this.registry.get(objectName); + + // 2. Check permissions + await this.permissions.check(options.user, config, 'read'); + + // 3. Run beforeFind hooks + await this.hooks.run('beforeFind', { objectName, options }); + + // 4. Apply field-level security + options.fields = this.filterFields(config, options.user); + + // 5. Dispatch to driver + const results = await this.driver.find(objectName, options); + + // 6. Run afterFind hooks + await this.hooks.run('afterFind', { results }); + + return results; +} +``` + +#### 3. Validation Engine + +Enforces constraints before database operations: + +```typescript +async validate(objectName: string, data: any): Promise { + const config = this.registry.get(objectName); + + for (const [fieldName, fieldConfig] of Object.entries(config.fields)) { + const value = data[fieldName]; + + // Required check + if (fieldConfig.required && !value) { + throw new ValidationError(`${fieldName} is required`); + } + + // Type check + if (value && !this.isValidType(value, fieldConfig.type)) { + throw new ValidationError(`${fieldName} must be ${fieldConfig.type}`); + } + + // Custom validators + if (fieldConfig.validate) { + await fieldConfig.validate(value); + } + } +} +``` + +#### 4. Hook System + +Extensibility through lifecycle hooks: + +```typescript +type HookContext = { + objectName: string; + data?: any; + user?: User; + result?: any; +}; + +class HookManager { + private hooks = new Map(); + + register(type: HookType, hook: Hook): void { + const existing = this.hooks.get(type) || []; + this.hooks.set(type, [...existing, hook]); + } + + async run(type: HookType, context: HookContext): Promise { + const hooks = this.hooks.get(type) || []; + + // Execute in order of registration + for (const hook of hooks) { + await hook(context); + } + } +} + +// Usage +kernel.on('beforeInsert', async (ctx) => { + ctx.data.created_at = new Date(); + ctx.data.created_by = ctx.user.id; +}); +``` + +### Dependency Injection + +The Kernel never directly instantiates drivers: + +```typescript +// ❌ BAD: Hard-coded dependency +class ObjectOS { + constructor() { + this.driver = new PostgresDriver(); // Tight coupling! + } +} + +// ✅ GOOD: Injected dependency +const driver = new PostgresDriver({ connection: {...} }); +const kernel = new ObjectOS(); +kernel.useDriver(driver); +``` + +Benefits: +- **Testing**: Use mock drivers in tests +- **Flexibility**: Swap databases at runtime +- **Multi-tenancy**: Different databases per tenant + +## Layer 3: Data Layer (Drivers) + +### Driver Interface + +All drivers implement this interface: + +```typescript +interface ObjectQLDriver { + // Lifecycle + connect(): Promise; + disconnect(): Promise; + + // Schema management + syncSchema(config: ObjectConfig): Promise; + + // CRUD + find(objectName: string, options: FindOptions): Promise; + findOne(objectName: string, id: string): Promise; + insert(objectName: string, data: any): Promise; + update(objectName: string, id: string, data: any): Promise; + delete(objectName: string, id: string): Promise; + + // Transactions + beginTransaction(): Promise; + commit(tx: Transaction): Promise; + rollback(tx: Transaction): Promise; +} +``` + +### PostgreSQL Driver (via Knex) + +```typescript +class PostgresDriver implements ObjectQLDriver { + private knex: Knex; + + async syncSchema(config: ObjectConfig): Promise { + const tableName = config.name; + + // Create table if not exists + await this.knex.schema.createTable(tableName, (table) => { + // Primary key + table.uuid('id').primary().defaultTo(this.knex.raw('uuid_generate_v4()')); + + // Fields + for (const [name, field] of Object.entries(config.fields)) { + this.addColumn(table, name, field); + } + + // Audit fields + table.timestamp('created_at').defaultTo(this.knex.fn.now()); + table.timestamp('updated_at').defaultTo(this.knex.fn.now()); + }); + + // Add indexes + for (const [name, field] of Object.entries(config.fields)) { + if (field.unique) { + await this.knex.schema.alterTable(tableName, (table) => { + table.unique([name]); + }); + } + if (field.index) { + await this.knex.schema.alterTable(tableName, (table) => { + table.index([name]); + }); + } + } + } + + async find(objectName: string, options: FindOptions): Promise { + let query = this.knex(objectName); + + // Apply filters + if (options.filters) { + query = this.applyFilters(query, options.filters); + } + + // Select fields + if (options.fields) { + query = query.select(options.fields); + } + + // Sort + if (options.sort) { + query = query.orderBy(options.sort); + } + + // Pagination + if (options.limit) { + query = query.limit(options.limit); + } + if (options.offset) { + query = query.offset(options.offset); + } + + return query; + } +} +``` + +### MongoDB Driver + +```typescript +class MongoDriver implements ObjectQLDriver { + private db: Db; + + async find(objectName: string, options: FindOptions): Promise { + const collection = this.db.collection(objectName); + + // Build MongoDB query + const filter = this.buildFilter(options.filters); + + let cursor = collection.find(filter); + + // Projection + if (options.fields) { + const projection = {}; + for (const field of options.fields) { + projection[field] = 1; + } + cursor = cursor.project(projection); + } + + // Sort + if (options.sort) { + cursor = cursor.sort(options.sort); + } + + // Pagination + if (options.limit) { + cursor = cursor.limit(options.limit); + } + if (options.skip) { + cursor = cursor.skip(options.skip); + } + + return cursor.toArray(); + } +} +``` + +## Layer 4: Application Layer (Server) + +### NestJS Architecture + +```typescript +@Module({ + imports: [ + // Kernel module provides ObjectOS instance + KernelModule.forRoot({ + driver: new PostgresDriver({...}), + objectsPath: './objects' + }) + ], + controllers: [ObjectDataController], + providers: [AuthService] +}) +export class AppModule {} +``` + +### Controller Pattern + +Controllers are thin wrappers around kernel: + +```typescript +@Controller('api/data') +export class ObjectDataController { + constructor(private kernel: ObjectOS) {} + + @Post(':objectName/query') + @UseGuards(AuthGuard) + async query( + @Param('objectName') name: string, + @Body() dto: QueryDTO, + @CurrentUser() user: User + ) { + // Validate input + if (!dto.filters) { + throw new BadRequestException('filters required'); + } + + // Call kernel + const results = await this.kernel.find(name, { + filters: dto.filters, + fields: dto.fields, + sort: dto.sort, + limit: dto.limit, + user: user + }); + + return { + data: results, + count: results.length + }; + } + + @Post(':objectName') + @UseGuards(AuthGuard) + async create( + @Param('objectName') name: string, + @Body() data: any, + @CurrentUser() user: User + ) { + const result = await this.kernel.insert(name, { + ...data, + user: user + }); + + return result; + } +} +``` + +### Error Handling + +Map kernel exceptions to HTTP status codes: + +```typescript +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + let status = 500; + let message = 'Internal server error'; + + if (exception instanceof ObjectNotFoundError) { + status = 404; + message = exception.message; + } else if (exception instanceof ValidationError) { + status = 400; + message = exception.message; + } else if (exception instanceof PermissionDeniedError) { + status = 403; + message = exception.message; + } + + response.status(status).json({ + statusCode: status, + message: message, + timestamp: new Date().toISOString() + }); + } +} +``` + +## Data Flow Example + +Let's trace a complete request: + +``` +User clicks "Create Contact" button in UI +│ +├─> React component calls POST /api/data/contacts +│ Body: { first_name: "John", last_name: "Doe", email: "john@example.com" } +│ +├─> NestJS receives request +│ ├─> AuthGuard extracts user from JWT +│ └─> ObjectDataController.create() is called +│ +├─> Controller calls kernel.insert('contacts', data) +│ +├─> Kernel processes request +│ ├─> 1. Load metadata: registry.get('contacts') +│ ├─> 2. Check permissions: user can create contacts? +│ ├─> 3. Run beforeInsert hooks +│ │ └─> Hook adds: created_at, created_by +│ ├─> 4. Validate data +│ │ └─> Check: first_name required? ✓ +│ │ └─> Check: email format valid? ✓ +│ ├─> 5. Call driver.insert('contacts', data) +│ +├─> Driver executes +│ ├─> Build SQL: INSERT INTO contacts ... +│ ├─> Execute query +│ └─> Return inserted row with id +│ +├─> Kernel post-processes +│ ├─> Run afterInsert hooks +│ │ └─> Hook sends welcome email +│ └─> Return result to controller +│ +├─> Controller returns JSON response +│ Status: 201 Created +│ Body: { id: "...", first_name: "John", ... } +│ +└─> React component updates UI + └─> Shows success message + └─> Adds new contact to grid +``` + +## Security Flow + +### Authentication + +1. User logs in → Server generates JWT +2. Client stores JWT in HTTP-only cookie +3. Every request includes JWT in Authorization header +4. AuthGuard validates JWT and extracts user + +### Authorization + +```typescript +// 1. Object-level permission +if (!config.permission_set.allowCreate.includes(user.role)) { + throw new PermissionDeniedError(); +} + +// 2. Field-level security +const visibleFields = config.fields.filter(field => { + return field.visible_to?.includes(user.role) ?? true; +}); + +// 3. Record-level security (RLS) +kernel.on('beforeFind', async (ctx) => { + // Only show records owned by user's team + if (!ctx.user.isAdmin) { + ctx.filters.push({ team_id: ctx.user.team_id }); + } +}); +``` + +## Performance Optimizations + +### 1. Metadata Caching + +```typescript +// Load once at startup +await kernel.loadFromDirectory('./objects'); + +// Access from memory (fast) +const config = kernel.getObject('contacts'); +``` + +### 2. Query Optimization + +```typescript +// Driver automatically adds indexes +fields: + email: + type: email + index: true // CREATE INDEX idx_contacts_email +``` + +### 3. Connection Pooling + +```typescript +const driver = new PostgresDriver({ + connection: {...}, + pool: { + min: 2, + max: 10 + } +}); +``` + +### 4. Lazy Loading + +```typescript +// Don't load related records unless requested +const contact = await kernel.findOne('contacts', id); +// contact.account is just the ID + +// Load related record on demand +if (options.include?.includes('account')) { + contact.account = await kernel.findOne('accounts', contact.account); +} +``` + +## Testing Strategy + +### Unit Tests (Kernel) + +```typescript +describe('ObjectOS.insert', () => { + it('should validate required fields', async () => { + const kernel = new ObjectOS(); + const mockDriver = { + insert: jest.fn() + }; + kernel.useDriver(mockDriver); + + await kernel.load({ + name: 'contacts', + fields: { + email: { type: 'email', required: true } + } + }); + + await expect( + kernel.insert('contacts', {}) // Missing email + ).rejects.toThrow('email is required'); + + expect(mockDriver.insert).not.toHaveBeenCalled(); + }); +}); +``` + +### Integration Tests (Server) + +```typescript +describe('POST /api/data/contacts', () => { + it('should create contact', async () => { + const response = await request(app) + .post('/api/data/contacts') + .set('Authorization', `Bearer ${token}`) + .send({ + first_name: 'John', + last_name: 'Doe', + email: 'john@example.com' + }) + .expect(201); + + expect(response.body).toHaveProperty('id'); + expect(response.body.first_name).toBe('John'); + }); +}); +``` + +## Best Practices + +### 1. Keep Controllers Thin + +```typescript +// ❌ BAD: Logic in controller +@Post('contacts') +async create(@Body() data: any) { + // Validation logic + if (!data.email) throw new Error('Email required'); + + // Business logic + if (await this.isDuplicate(data.email)) { + throw new Error('Duplicate'); + } + + return this.db.insert(data); +} + +// ✅ GOOD: Logic in kernel/hooks +@Post('contacts') +async create(@Body() data: any, @CurrentUser() user: User) { + return this.kernel.insert('contacts', { ...data, user }); +} +``` + +### 2. Use Hooks for Side Effects + +```typescript +// ❌ BAD: Side effects in controller +@Post('contacts') +async create(@Body() data: any) { + const contact = await this.kernel.insert('contacts', data); + await this.sendEmail(contact.email); // Side effect + return contact; +} + +// ✅ GOOD: Side effects in hooks +kernel.on('afterInsert', async (ctx) => { + if (ctx.objectName === 'contacts') { + await sendWelcomeEmail(ctx.result.email); + } +}); +``` + +### 3. Type Everything + +```typescript +// ❌ BAD: Untyped +async find(name: string, opts: any): Promise { + // ... +} + +// ✅ GOOD: Fully typed +async find( + name: string, + options: FindOptions +): Promise[]> { + // ... +} +``` + +## Summary + +ObjectOS achieves its goal through: + +1. **Clear Separation**: Kernel, Driver, Server have distinct roles +2. **Protocol-Driven**: Implements ObjectQL standard +3. **Dependency Injection**: Flexible and testable +4. **Hook System**: Extensible without modifying core +5. **Type Safety**: Full TypeScript coverage + +This architecture allows ObjectOS to generate complete applications from YAML while remaining maintainable and scalable. \ No newline at end of file diff --git a/apps/site/content/docs/guide/contributing-development.mdx b/apps/site/content/docs/guide/contributing-development.mdx new file mode 100644 index 0000000..ea372ba --- /dev/null +++ b/apps/site/content/docs/guide/contributing-development.mdx @@ -0,0 +1,247 @@ +--- +title: Contributing to Development +--- + +# Contributing to Development + +Welcome to the ObjectOS development effort! This guide helps you understand how to contribute to the project's ongoing development. + +## Quick Links + +- **[Development Plan (Q1 2026)](./development-plan.mdx)** - Current quarter's detailed implementation roadmap +- **[Architecture Guide](./architecture.mdx)** - System design and architectural principles +- **[Security Guide](./security-guide.mdx)** - Security best practices and guidelines + +## How to Get Involved + +### 1. Choose Your Area + +ObjectOS development is organized into several key areas: + +**🔐 Permission System** (High Priority) +- Object-level permissions (CRUD) +- Field-level security +- Record-level security (RLS) +- See: [Development Plan § 3.1-3.3](./development-plan.mdx#phase-1-permission-system-implementation-2-3-weeks) + +**🪝 Lifecycle Hooks** (High Priority) +- Standard hooks implementation +- Hook debugging tools +- See: [Development Plan § 3.4-3.5](./development-plan.mdx#phase-2-lifecycle-hooks-system-1-2-weeks) + +**🔗 Relationship Fields** (High Priority) +- Lookup fields (many-to-one) +- Master-Detail relationships +- Many-to-many relationships +- See: [Development Plan § 3.6-3.8](./development-plan.mdx#phase-3-relationship-fields-implementation-2-3-weeks) + +**🧪 Testing & Quality** (Ongoing) +- Unit tests (target: 90% Kernel, 80% Server, 70% UI) +- Integration tests +- E2E tests +- See: [Development Plan § 3.9-3.11](./development-plan.mdx#phase-4-testing--documentation-ongoing) + +### 2. Understand the Standards + +Before contributing code, please review: + +- **[Architecture Principles](./architecture.mdx)** - Understand the kernel/driver/server separation +- **[SDK Reference](./sdk-reference.mdx)** - Learn the ObjectOS API +- **[Contributing Guide](../../../../../CONTRIBUTING.md)** - Code style and workflow + +### 3. Set Up Your Environment + +```bash +# Clone the repository +git clone https://github.com/objectstack-ai/objectos.git +cd objectos + +# Install dependencies +pnpm install + +# Build all packages +pnpm run build + +# Run tests +pnpm run test + +# Start development server +pnpm run dev +``` + +### 4. Pick a Task + +Tasks are organized in the [Development Plan](./development-plan.mdx) with: +- **Task Lists** - Specific implementation steps +- **Acceptance Criteria** - What "done" looks like +- **Estimated Time** - How long it should take + +Look for: +- Tasks marked as high-priority +- Tasks in the current week (see [Timeline](./development-plan.mdx#4-timeline--milestones)) +- Tasks that match your skills and interests + +### 5. Submit Your Work + +1. **Create a feature branch** + ```bash + git checkout -b feature/permission-checker + ``` + +2. **Implement the feature** + - Follow the task list in the development plan + - Write tests as you go (TDD recommended) + - Add JSDoc comments for public APIs + +3. **Test thoroughly** + ```bash + # Run unit tests + pnpm --filter @objectos/kernel test + + # Run integration tests + pnpm --filter @objectos/server test + + # Check coverage + pnpm run test --coverage + ``` + +4. **Submit a pull request** + - Reference the development plan section + - Include before/after examples + - Note any breaking changes + - Request review from maintainers + +## Development Workflow + +### Weekly Cycle + +Each week follows this pattern: + +**Monday-Wednesday: Implementation** +- Work on assigned tasks +- Write tests alongside code +- Document as you go + +**Thursday: Code Review** +- Submit PRs for review +- Review others' PRs +- Address feedback + +**Friday: Integration** +- Merge approved PRs +- Update development plan status +- Plan next week's tasks + +### Communication Channels + +- **GitHub Issues** - Bug reports and feature requests +- **GitHub Discussions** - Questions and general discussion +- **Pull Requests** - Code review and technical discussion + +## Quality Standards + +All contributions must meet these standards: + +### Code Quality +- ✅ TypeScript strict mode enabled +- ✅ No `any` types (use `unknown` with guards) +- ✅ All public APIs have JSDoc comments +- ✅ Consistent naming conventions (see [Contributing Guide](../../../../../CONTRIBUTING.md)) + +### Test Coverage +- ✅ Kernel: ≥ 90% coverage +- ✅ Server: ≥ 80% coverage +- ✅ UI: ≥ 70% coverage +- ✅ All edge cases tested + +### Performance +- ✅ API response time P95 < 100ms +- ✅ Permission check overhead < 10ms +- ✅ No N+1 queries in relationships + +### Documentation +- ✅ README updated if behavior changes +- ✅ Guide updated for new features +- ✅ Migration notes for breaking changes +- ✅ Examples provided for complex features + +## Current Priorities (Q1 2026) + +### Week 1-2: Permission System Foundation +Focus on object-level and field-level permissions. This is the foundation for enterprise security. + +**Key Tasks:** +- Implement `PermissionChecker` class +- Add `PermissionGuard` to server +- Create field visibility filters + +**Success Metrics:** +- All CRUD operations check permissions +- 95%+ test coverage +- < 5ms permission check overhead + +### Week 3-4: RLS & Hooks +Complete the permission system with record-level security, then move to lifecycle hooks. + +**Key Tasks:** +- Implement `RLSInjector` +- Complete `HookManager` +- Add hook debugging tools + +**Success Metrics:** +- Sharing rules work correctly +- All 8 hook types functional +- Hook execution traceable + +### Week 5-7: Relationships +Implement full relationship support, the most complex feature set. + +**Key Tasks:** +- Lookup field resolver +- Master-Detail cascade delete +- Many-to-many junction tables + +**Success Metrics:** +- No N+1 queries +- Cascade operations work +- Related records query correctly + +## Getting Help + +### For Contributors + +**Not sure where to start?** +- Look for issues labeled `good first issue` +- Start with documentation improvements +- Review the [Architecture Guide](./architecture.mdx) to understand the system + +**Stuck on implementation?** +- Check the [Development Plan](./development-plan.mdx) for code examples +- Review the [SDK Reference](./sdk-reference.mdx) +- Ask in GitHub Discussions + +**Tests failing?** +- Check the [Testing Guide](../../../../../CONTRIBUTING.md#testing) +- Look at existing tests for examples +- Run tests in watch mode: `pnpm test --watch` + +### For Maintainers + +**Reviewing PRs?** +- Check against acceptance criteria in development plan +- Verify test coverage meets standards +- Ensure documentation is updated + +**Planning next sprint?** +- Review [Timeline & Milestones](./development-plan.mdx#4-timeline--milestones) +- Assess completion status +- Adjust priorities based on progress + +## Recognition + +Contributors who make significant progress on the development plan will be: +- Listed in release notes +- Acknowledged in documentation +- Invited to join the core team + +Thank you for helping build the future of ObjectOS! 🚀 \ No newline at end of file diff --git a/apps/site/content/docs/guide/data-modeling.mdx b/apps/site/content/docs/guide/data-modeling.mdx new file mode 100644 index 0000000..238364b --- /dev/null +++ b/apps/site/content/docs/guide/data-modeling.mdx @@ -0,0 +1,689 @@ +--- +title: Data Modeling Guide +--- + +# Data Modeling Guide + +ObjectOS uses declarative YAML files to define data models. This guide covers how to model your business data using the ObjectQL metadata format. + +## Overview + +Data modeling in ObjectOS is done entirely in YAML files with the `.object.yml` extension. These files define: + +- **Objects**: Business entities (like Contact, Account, Order) +- **Fields**: Properties of objects (name, email, price, etc.) +- **Relationships**: How objects relate to each other +- **Validation**: Rules that data must satisfy +- **Permissions**: Who can read, create, update, or delete records + +## Creating Your First Object + +### Step 1: Create the File + +Create a file named `contact.object.yml` in your `objects/` directory: + +```yaml +name: contacts +label: Contact +icon: user +description: Customer and prospect contact information +``` + +### Step 2: Add Fields + +Add fields to describe your contact: + +```yaml +name: contacts +label: Contact +icon: user + +fields: + first_name: + type: text + label: First Name + required: true + max_length: 100 + + last_name: + type: text + label: Last Name + required: true + max_length: 100 + + email: + type: email + label: Email Address + unique: true + required: true + + phone: + type: text + label: Phone Number + + birthdate: + type: date + label: Birth Date +``` + +### Step 3: Add Permissions + +Define who can access this object: + +```yaml +permission_set: + allowRead: true # Everyone can read + allowCreate: ['sales', 'admin'] # Sales and admin can create + allowEdit: ['sales', 'admin'] # Sales and admin can edit + allowDelete: ['admin'] # Only admin can delete +``` + +## Object Structure + +### Object Attributes + +| Attribute | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | Yes | API name (snake_case, no spaces) | +| `label` | string | No | Display name for UI | +| `icon` | string | No | Icon name (from icon library) | +| `description` | string | No | Object description | +| `enable_api` | boolean | No | Enable REST API (default: true) | +| `enable_audit` | boolean | No | Enable audit logging (default: false) | + +### Example + +```yaml +name: projects +label: Project +icon: folder +description: Project management and tracking +enable_api: true +enable_audit: true +``` + +## Field Types + +### Text Fields + +#### Short Text (`text`) + +```yaml +first_name: + type: text + label: First Name + required: true + max_length: 100 +``` + +#### Long Text (`textarea`) + +```yaml +description: + type: textarea + label: Description + max_length: 5000 +``` + +#### Email (`email`) + +```yaml +email: + type: email + label: Email Address + unique: true + required: true +``` + +#### URL (`url`) + +```yaml +website: + type: url + label: Website +``` + +### Numeric Fields + +#### Number (`number`) + +```yaml +age: + type: number + label: Age + min: 0 + max: 150 +``` + +#### Currency (`currency`) + +```yaml +annual_revenue: + type: currency + label: Annual Revenue + currency_code: USD + precision: 2 +``` + +#### Percent (`percent`) + +```yaml +discount_rate: + type: percent + label: Discount Rate + min: 0 + max: 100 + precision: 2 +``` + +### Date Fields + +#### Date (`date`) + +```yaml +birth_date: + type: date + label: Birth Date +``` + +#### DateTime (`datetime`) + +```yaml +created_at: + type: datetime + label: Created At + default: now +``` + +### Selection Fields + +#### Select (`select`) + +Single choice dropdown: + +```yaml +status: + type: select + label: Status + options: + - value: draft + label: Draft + - value: active + label: Active + - value: archived + label: Archived + default: draft +``` + +#### Multi-Select (`multiselect`) + +Multiple choice: + +```yaml +interests: + type: multiselect + label: Interests + options: + - value: tech + label: Technology + - value: sports + label: Sports + - value: music + label: Music +``` + +### Boolean (`boolean`) + +```yaml +is_active: + type: boolean + label: Active + default: true +``` + +## Relationships + +### Many-to-One (Lookup) + +A Contact belongs to one Account: + +```yaml +# In contact.object.yml +fields: + account: + type: lookup + label: Account + reference_to: accounts + on_delete: set_null # When account deleted, set to null +``` + +### One-to-Many (Inverse of Lookup) + +One Account has many Contacts. This is automatically created by the lookup field above. + +### Master-Detail + +Like lookup, but with cascade delete: + +```yaml +fields: + account: + type: master_detail + label: Account + reference_to: accounts + # on_delete: cascade is automatic +``` + +When the Account is deleted, all related Contacts are also deleted. + +### Many-to-Many (Junction Object) + +Create a junction object for many-to-many relationships: + +```yaml +# opportunity_contact.object.yml +name: opportunity_contacts +label: Opportunity Contact +fields: + opportunity: + type: master_detail + reference_to: opportunities + + contact: + type: master_detail + reference_to: contacts + + role: + type: select + options: + - Decision Maker + - Influencer + - User +``` + +## Field Validation + +### Required Fields + +```yaml +email: + type: email + required: true +``` + +### Unique Fields + +```yaml +email: + type: email + unique: true +``` + +### Min/Max Values + +```yaml +age: + type: number + min: 18 + max: 100 + +price: + type: currency + min: 0 +``` + +### String Length + +```yaml +name: + type: text + min_length: 2 + max_length: 100 +``` + +### Pattern Matching + +```yaml +phone: + type: text + pattern: '^\+?[1-9]\d{1,14}$' # E.164 phone format +``` + +## Default Values + +Set default values for new records: + +```yaml +status: + type: select + default: draft + +created_at: + type: datetime + default: now + +is_active: + type: boolean + default: true +``` + +## Field-Level Permissions + +Control who can see or edit specific fields: + +```yaml +salary: + type: currency + label: Salary + visible_to: ['hr', 'admin'] # Only HR and admin can see + editable_by: ['hr'] # Only HR can edit +``` + +## Calculated Fields + +### Formula Fields + +Define calculated fields using formulas: + +```yaml +full_name: + type: formula + label: Full Name + formula: 'first_name + " " + last_name' + return_type: text +``` + +### Rollup Summary Fields + +Aggregate data from related records: + +```yaml +# In account.object.yml +total_opportunities: + type: rollup_summary + label: Total Opportunities + reference_to: opportunities + summary_type: count + +total_revenue: + type: rollup_summary + label: Total Revenue + reference_to: opportunities + summary_field: amount + summary_type: sum + filters: + stage: 'closed_won' +``` + +## Auto-Number Fields + +Generate sequential numbers: + +```yaml +ticket_number: + type: autonumber + label: Ticket # + format: 'TKT-{0000}' + start_number: 1 +``` + +## Complete Example: CRM Objects + +### Account Object + +```yaml +# objects/account.object.yml +name: accounts +label: Account +icon: building +description: Company or organization + +fields: + name: + type: text + label: Account Name + required: true + max_length: 255 + + industry: + type: select + label: Industry + options: + - value: technology + label: Technology + - value: finance + label: Financial Services + - value: healthcare + label: Healthcare + - value: retail + label: Retail + + website: + type: url + label: Website + + annual_revenue: + type: currency + label: Annual Revenue + currency_code: USD + + employee_count: + type: number + label: Number of Employees + min: 0 + + type: + type: select + label: Type + options: + - Customer + - Prospect + - Partner + default: Prospect + +permission_set: + allowRead: true + allowCreate: ['sales', 'admin'] + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] +``` + +### Contact Object + +```yaml +# objects/contact.object.yml +name: contacts +label: Contact +icon: user +description: Person associated with an account + +fields: + first_name: + type: text + label: First Name + required: true + + last_name: + type: text + label: Last Name + required: true + + email: + type: email + label: Email + unique: true + required: true + + phone: + type: text + label: Phone + + title: + type: text + label: Job Title + + account: + type: lookup + label: Account + reference_to: accounts + on_delete: set_null + + birthdate: + type: date + label: Birth Date + + is_primary: + type: boolean + label: Primary Contact + default: false + +permission_set: + allowRead: true + allowCreate: ['sales', 'admin'] + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] +``` + +### Opportunity Object + +```yaml +# objects/opportunity.object.yml +name: opportunities +label: Opportunity +icon: currency-dollar +description: Sales opportunity + +fields: + name: + type: text + label: Opportunity Name + required: true + + account: + type: lookup + label: Account + reference_to: accounts + required: true + + amount: + type: currency + label: Amount + currency_code: USD + min: 0 + + probability: + type: percent + label: Probability + min: 0 + max: 100 + + expected_revenue: + type: formula + label: Expected Revenue + formula: 'amount * (probability / 100)' + return_type: currency + + stage: + type: select + label: Stage + required: true + options: + - value: prospecting + label: Prospecting + - value: qualification + label: Qualification + - value: proposal + label: Proposal + - value: negotiation + label: Negotiation + - value: closed_won + label: Closed Won + - value: closed_lost + label: Closed Lost + default: prospecting + + close_date: + type: date + label: Expected Close Date + required: true + + owner: + type: lookup + label: Owner + reference_to: users + required: true + +permission_set: + allowRead: ['sales', 'admin'] + allowCreate: ['sales', 'admin'] + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] +``` + +## Best Practices + +### 1. Naming Conventions + +- **Object names**: Use snake_case plural (e.g., `contacts`, `sales_orders`) +- **Field names**: Use snake_case (e.g., `first_name`, `annual_revenue`) +- **Labels**: Use Title Case (e.g., "First Name", "Annual Revenue") + +### 2. Required Fields + +Make fields required if they are essential for the object to make sense: + +```yaml +# Good: Contact must have a name and email +first_name: + required: true +email: + required: true +``` + +### 3. Unique Constraints + +Use unique constraints to prevent duplicates: + +```yaml +email: + unique: true +``` + +### 4. Field Length + +Set reasonable maximum lengths: + +```yaml +name: + max_length: 255 # Standard for most text fields +``` + +### 5. Default Values + +Provide sensible defaults: + +```yaml +status: + default: draft +is_active: + default: true +``` + +## Testing Your Models + +After creating objects, test them: + +```bash +# Start the server +pnpm run dev + +# Test API endpoints +curl http://localhost:3000/api/metadata/contacts +curl http://localhost:3000/api/data/contacts/query +``` + +## Next Steps + +- **[Logic Hooks](./logic-hooks.mdx)** - Add custom business logic +- **[Security Guide](./security-guide.mdx)** - Configure permissions +- **[Metadata Format Spec](../spec/metadata-format.mdx)** - Complete field type reference \ No newline at end of file diff --git a/apps/site/content/docs/guide/development-plan.mdx b/apps/site/content/docs/guide/development-plan.mdx new file mode 100644 index 0000000..f4e3c40 --- /dev/null +++ b/apps/site/content/docs/guide/development-plan.mdx @@ -0,0 +1,721 @@ +--- +title: ObjectOS Development Plan +--- + +# ObjectOS Development Plan + +> **Current Version**: v0.2.0 +> **Target Version**: v0.3.0 (Q1 2026) +> **Last Updated**: January 2026 + +--- + +## 1. Current Status Analysis + +### 1.1 Completed Core Features + +✅ **Infrastructure** +- ObjectOS Kernel core engine implemented +- Object Registry operational +- NestJS-based HTTP server +- Basic CRUD operations through driver layer + +✅ **Data Layer** +- PostgreSQL driver support +- MongoDB driver support +- YAML metadata parser + +✅ **Authentication System** +- Better-Auth integration +- Basic authentication flow + +✅ **UI Components** +- React UI component library (Grid, Form) +- Basic documentation structure + +### 1.2 Key Areas Needing Improvement + +🔴 **High-Priority Missing Features** +- Incomplete permission system (missing field-level and record-level permissions) +- Incomplete relationship field resolution (Lookup, Master-Detail) +- Insufficient test coverage (target: 80%+) +- Incomplete lifecycle hooks system + +🟡 **Medium-Priority Missing Features** +- Workflow engine not implemented +- GraphQL API not implemented +- Real-time sync capability missing +- Batch operation API incomplete + +🟢 **Low-Priority Improvements** +- UI components need optimization +- Documentation needs expansion +- Developer tools (CLI) missing + +--- + +## 2. Q1 2026 Development Goals + +### 2.1 Core Objectives + +**Goal 1: Implement Production-Grade Permission System** +- Complete object-level permissions (CRUD) +- Implement field-level security +- Implement record-level security (RLS) +- Integrate into Kernel and Server layers + +**Goal 2: Complete Lifecycle Hooks System** +- Implement all standard hooks (beforeFind, afterInsert, etc.) +- Support async hook chains +- Add hook priority and ordering +- Provide hook debugging tools + +**Goal 3: Complete Relationship Field Implementation** +- Lookup fields (many-to-one) +- Master-Detail relationships (cascade delete) +- Many-to-many relationships +- Relationship query optimization + +**Goal 4: Improve Test Coverage** +- Kernel: 90%+ unit test coverage +- Server: 80%+ integration test coverage +- Critical path E2E tests + +--- + +## 3. Detailed Implementation Plan + +### Phase 1: Permission System Implementation (2-3 weeks) + +#### 3.1 Object-Level Permissions + +**Task List:** +1. Define permission interface in `@objectql/types` + ```typescript + interface PermissionSet { + allowRead?: boolean | string[]; + allowCreate?: boolean | string[]; + allowEdit?: boolean | string[]; + allowDelete?: boolean | string[]; + } + ``` + +2. Implement permission checker in Kernel + ```typescript + // packages/kernel/src/security/permission-checker.ts + class PermissionChecker { + canRead(object: string, user: User): boolean + canCreate(object: string, user: User): boolean + canUpdate(object: string, user: User): boolean + canDelete(object: string, user: User): boolean + } + ``` + +3. Add permission guard in Server layer + ```typescript + // packages/server/src/guards/permission.guard.ts + @Injectable() + export class PermissionGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean + } + ``` + +**Acceptance Criteria:** +- [ ] All CRUD operations go through permission checks +- [ ] Permission denial returns 403 error +- [ ] Unit test coverage 95%+ +- [ ] Integration tests verify end-to-end flow + +**Estimated Time:** 5-7 days + +#### 3.2 Field-Level Security + +**Task List:** +1. Extend field definition to support visibility rules + ```yaml + fields: + salary: + type: currency + label: Salary + visible_to: ['hr', 'admin'] + editable_by: ['hr'] + ``` + +2. Implement field filter + ```typescript + // packages/kernel/src/security/field-filter.ts + class FieldFilter { + filterReadable(object: string, fields: string[], user: User): string[] + filterEditable(object: string, fields: string[], user: User): string[] + } + ``` + +3. Automatically filter fields in query results + ```typescript + // Kernel filters invisible fields before returning data + const visibleFields = fieldFilter.filterReadable(objectName, fields, user); + return records.map(r => pick(r, visibleFields)); + ``` + +**Acceptance Criteria:** +- [ ] Users can only see permitted fields +- [ ] Edit operations automatically ignore unpermitted fields +- [ ] API responses don't include restricted fields +- [ ] Tests cover all edge cases + +**Estimated Time:** 4-5 days + +#### 3.3 Record-Level Security + +**Task List:** +1. Implement sharing rules + ```yaml + sharing_rules: + - name: owner_full_access + criteria: { owner: $current_user } + access: read_write + - name: manager_read_access + criteria: { manager: $current_user } + access: read_only + ``` + +2. Inject filters during queries + ```typescript + // packages/kernel/src/security/rls-injector.ts + class RLSInjector { + injectFilters( + objectName: string, + filters: FilterGroup, + user: User + ): FilterGroup + } + ``` + +3. Automatically apply in beforeFind hook + ```typescript + kernel.on('beforeFind', async (ctx) => { + ctx.filters = rlsInjector.injectFilters( + ctx.objectName, + ctx.filters, + ctx.user + ); + }); + ``` + +**Acceptance Criteria:** +- [ ] Users can only query records they have permission for +- [ ] Sharing rules applied correctly +- [ ] Performance tests pass (query time increase < 10%) +- [ ] Complete security test suite + +**Estimated Time:** 6-8 days + +--- + +### Phase 2: Lifecycle Hooks System (1-2 weeks) + +#### 3.4 Standard Hooks Implementation + +**Task List:** +1. Define complete hook types + ```typescript + type HookType = + | 'beforeFind' | 'afterFind' + | 'beforeInsert' | 'afterInsert' + | 'beforeUpdate' | 'afterUpdate' + | 'beforeDelete' | 'afterDelete' + | 'beforeValidate' | 'afterValidate'; + + interface HookContext { + objectName: string; + operation: 'find' | 'insert' | 'update' | 'delete'; + user: User; + data?: T; + filters?: FilterGroup; + result?: any; + } + ``` + +2. Implement hook manager + ```typescript + // packages/kernel/src/hooks/hook-manager.ts + class HookManager { + register( + hookType: HookType, + handler: HookHandler, + priority?: number + ): void + + async execute( + hookType: HookType, + context: HookContext + ): Promise + + unregister(hookType: HookType, handler: HookHandler): void + } + ``` + +3. Insert hook call points in Kernel operations + ```typescript + async insert(objectName: string, data: any): Promise { + const context = { objectName, operation: 'insert', data, user }; + + // Before hooks + await this.hooks.execute('beforeValidate', context); + await this.hooks.execute('beforeInsert', context); + + // Actual insert + const result = await this.driver.insert(objectName, context.data); + + // After hooks + context.result = result; + await this.hooks.execute('afterInsert', context); + + return context.result; + } + ``` + +**Acceptance Criteria:** +- [ ] All 8 hook types work correctly +- [ ] Hooks execute in priority order +- [ ] Async hook handling supported +- [ ] Hook errors don't crash the system +- [ ] Complete hook documentation and examples + +**Estimated Time:** 5-6 days + +#### 3.5 Hook Debugging Tools + +**Task List:** +1. Add hook execution logging + ```typescript + class HookManager { + enableDebug(enabled: boolean): void + + async execute(hookType: HookType, context: HookContext) { + if (this.debugEnabled) { + console.log(`[Hook] ${hookType} started`, context); + } + // ... + if (this.debugEnabled) { + console.log(`[Hook] ${hookType} completed in ${duration}ms`); + } + } + } + ``` + +2. Implement hook performance monitoring + ```typescript + interface HookMetrics { + hookType: HookType; + executionTime: number; + timestamp: Date; + success: boolean; + error?: Error; + } + ``` + +3. Add hook testing utility + ```typescript + // packages/kernel/src/testing/hook-tester.ts + class HookTester { + testHook( + hookType: HookType, + context: HookContext + ): Promise + } + ``` + +**Acceptance Criteria:** +- [ ] Can view all registered hooks +- [ ] Can trace hook execution order +- [ ] Can measure hook performance +- [ ] Hook debugging documentation provided + +**Estimated Time:** 3-4 days + +--- + +### Phase 3: Relationship Fields Implementation (2-3 weeks) + +#### 3.6 Lookup Fields (Many-to-One) + +**Task List:** +1. Extend field definition + ```yaml + fields: + account: + type: lookup + reference_to: accounts + label: Account + relationship_name: contacts + ``` + +2. Implement relationship resolver + ```typescript + // packages/kernel/src/relations/lookup-resolver.ts + class LookupResolver { + async resolve( + objectName: string, + records: any[], + lookupField: string + ): Promise + } + ``` + +3. Auto-load related objects during queries + ```typescript + // Auto-populate lookup fields + const contacts = await kernel.find('contacts', { + fields: ['name', 'account.name', 'account.industry'], + populate: ['account'] + }); + ``` + +**Acceptance Criteria:** +- [ ] Lookup fields correctly save reference IDs +- [ ] Optional loading of related objects during queries +- [ ] Support cascading queries (account.owner.name) +- [ ] Relationship query performance optimization (avoid N+1 problem) + +**Estimated Time:** 6-7 days + +#### 3.7 Master-Detail Relationships + +**Task List:** +1. Define Master-Detail relationship + ```yaml + fields: + opportunity: + type: master_detail + reference_to: opportunities + cascade_delete: true + rollup_summary: true + ``` + +2. Implement cascade delete + ```typescript + // When deleting master, delete all detail records + async delete(objectName: string, id: string) { + const config = this.registry.get(objectName); + + // Find and delete detail records + for (const field of config.fields) { + if (field.type === 'master_detail' && field.cascade_delete) { + await this.deleteDetailRecords(objectName, id, field); + } + } + + // Delete master record + await this.driver.delete(objectName, id); + } + ``` + +3. Implement rollup summary fields + ```yaml + # On opportunity object + fields: + total_amount: + type: rollup_summary + summarized_object: line_items + summarized_field: amount + operation: SUM + ``` + +**Acceptance Criteria:** +- [ ] Master-Detail relationships correctly established +- [ ] Deleting master records automatically deletes detail records +- [ ] Rollup summary fields automatically calculated +- [ ] Prevent orphan records + +**Estimated Time:** 7-8 days + +#### 3.8 Many-to-Many Relationships + +**Task List:** +1. Define many-to-many relationship + ```yaml + # On project object + fields: + members: + type: many_to_many + reference_to: users + junction_object: project_members + ``` + +2. Auto-create junction table + ```typescript + // Auto-generate junction object + const junctionObject = { + name: 'project_members', + fields: { + project: { type: 'lookup', reference_to: 'projects' }, + user: { type: 'lookup', reference_to: 'users' } + } + }; + ``` + +3. Implement many-to-many operations API + ```typescript + // Add members to project + await kernel.addRelation('projects', projectId, 'members', [userId1, userId2]); + + // Remove member + await kernel.removeRelation('projects', projectId, 'members', [userId1]); + + // Query with members + const projects = await kernel.find('projects', { + populate: ['members'] + }); + ``` + +**Acceptance Criteria:** +- [ ] Many-to-many relationships correctly established +- [ ] Junction table auto-created and managed +- [ ] Support adding/removing relationships +- [ ] Support querying related records + +**Estimated Time:** 5-6 days + +--- + +### Phase 4: Testing & Documentation (Ongoing) + +#### 3.9 Unit Testing + +**Goals:** +- Kernel package: 90%+ coverage +- Server package: 80%+ coverage +- UI package: 70%+ coverage + +**Task List:** +1. Write unit tests for all new features +2. Add tests for existing code +3. Set up code coverage reporting +4. Integrate into CI/CD pipeline + +**Estimated Time:** Ongoing, 30% of each feature's time for testing + +#### 3.10 Integration Testing + +**Task List:** +1. End-to-end API tests + ```typescript + describe('Permissions E2E', () => { + it('should deny access to unpermitted field', async () => { + const response = await request(app) + .get('/api/data/employees/123') + .set('Authorization', `Bearer ${salesUserToken}`) + .expect(200); + + expect(response.body).not.toHaveProperty('salary'); + }); + }); + ``` + +2. Database integration tests +3. Permission system end-to-end tests +4. Relationship field query tests + +**Estimated Time:** 2-3 days per phase + +#### 3.11 Documentation Updates + +**Task List:** +1. API documentation (OpenAPI/Swagger) +2. Permission system usage guide +3. Hooks system development guide +4. Relationship field configuration guide +5. Best practices documentation + +**Estimated Time:** 1-2 days per major feature + +--- + +## 4. Timeline & Milestones + +### Week 1-2: Permission System Foundation +- [ ] Object-level permissions implementation (Week 1) +- [ ] Field-level security implementation (Week 2) +- [ ] Unit tests and documentation (Week 2) + +### Week 3-4: Permission System Completion & Hooks System +- [ ] Record-level security implementation (Week 3) +- [ ] Hooks system implementation (Week 4) +- [ ] Hook debugging tools (Week 4) + +### Week 5-7: Relationship Fields Implementation +- [ ] Lookup fields (Week 5) +- [ ] Master-Detail relationships (Week 6) +- [ ] Many-to-many relationships (Week 7) + +### Week 8-9: Integration Testing & Optimization +- [ ] Integration test writing (Week 8) +- [ ] Performance optimization (Week 8) +- [ ] Bug fixes (Week 9) +- [ ] Documentation completion (Week 9) + +### Week 10: Release Preparation +- [ ] Code review +- [ ] Security audit +- [ ] Release v0.3.0 +- [ ] Release announcement and migration guide + +--- + +## 5. Technical Debt Cleanup + +### 5.1 High-Priority Technical Debt + +1. **Kernel Dependency Injection Refactor** + - Issue: Some places still have hardcoded dependencies + - Solution: Fully use DI pattern + - Time: 2-3 days + +2. **Error Handling Standardization** + - Issue: Error types not unified + - Solution: Create unified error classes and error codes + - Time: 2 days + +3. **Type Definition Completion** + - Issue: Some interfaces use `any` + - Solution: Add strict type definitions + - Time: 3-4 days + +### 5.2 Code Quality Improvements + +1. Add ESLint rules +2. Configure Prettier +3. Add pre-commit hooks +4. Code review checklist + +--- + +## 6. Risk Assessment & Mitigation + +### 6.1 Technical Risks + +| Risk | Impact | Probability | Mitigation | +|------|--------|-------------|------------| +| Permission system performance issues | High | Medium | Early performance testing, use caching optimization | +| Relationship query N+1 problem | High | High | Implement DataLoader pattern, batch queries | +| Hook system complexity | Medium | Medium | Provide clear documentation, limit hook nesting depth | +| Database compatibility | Medium | Low | Integration testing on multiple databases | + +### 6.2 Schedule Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Requirement changes | High | Freeze Q1 requirements, move new requirements to Q2 | +| Insufficient manpower | High | Recruit contributors, simplify some feature scope | +| Insufficient testing time | Medium | Test and develop in parallel, TDD mode | +| Documentation delays | Low | Write documentation synchronously with code | + +--- + +## 7. Success Criteria + +### 7.1 Functional Completeness + +- [ ] All planned features implemented +- [ ] All tests passing +- [ ] Complete documentation + +### 7.2 Quality Metrics + +- **Test Coverage** + - Kernel: ≥ 90% + - Server: ≥ 80% + - UI: ≥ 70% + +- **Performance Metrics** + - API response time (P95): < 100ms + - Permission check overhead: < 10ms + - Relationship query optimization: Avoid N+1 + +- **Code Quality** + - 0 TypeScript errors + - 0 ESLint errors + - Code review pass rate: 100% + +### 7.3 User Feedback + +- At least 3 real projects piloting +- Collect feedback and improve +- GitHub Stars growth 20%+ + +--- + +## 8. Future Plans + +### Q2 2026 Preview + +1. **Workflow Engine** + - Visual process designer + - Approval processes + - Scheduled tasks + +2. **GraphQL API** + - Auto-generate GraphQL Schema + - Query optimization + - Real-time subscriptions + +3. **Advanced Data Features** + - Data import/export + - Batch operations + - Data deduplication + +4. **Developer Tools** + - CLI tool + - VS Code extension + - Debugging tools + +--- + +## 9. Resource Requirements + +### 9.1 Human Resources + +- **Core Development**: 2-3 full-time developers +- **Contributors**: 5-10 part-time contributors +- **Testing**: 1 dedicated test engineer +- **Documentation**: 1 technical writer + +### 9.2 Infrastructure + +- CI/CD server (GitHub Actions) +- Test databases (PostgreSQL, MongoDB) +- Documentation hosting (VitePress + Vercel) +- NPM package publishing + +### 9.3 Community Building + +- Discord server setup +- Regular livestreams / technical sharing +- Documentation translation (English/Chinese) +- Example project library + +--- + +## 10. Summary + +The core goals of ObjectOS Q1 2026 are to **implement a production-grade permission system, complete lifecycle hooks, and full relationship field support**. Through 10 weeks of systematic development, we will: + +✅ **Enhance Security**: Multi-layer permission protection, achieving enterprise-grade security standards +✅ **Increase Flexibility**: Complete hook system, supporting business logic customization +✅ **Expand Data Capabilities**: Relationship field support, meeting complex business needs +✅ **Ensure Quality**: 80%+ test coverage, comprehensive documentation + +**Making ObjectOS a truly production-ready enterprise low-code platform!** + +--- + +**Next Actions:** +1. Team review of this plan +2. Assign tasks to developers +3. Set up project board (GitHub Projects) +4. Begin Sprint 1 development + +**Contact:** +- GitHub Issues: https://github.com/objectstack-ai/objectos/issues +- Project Discussions: GitHub Discussions \ No newline at end of file diff --git a/apps/site/content/docs/guide/index.mdx b/apps/site/content/docs/guide/index.mdx new file mode 100644 index 0000000..f8a7f53 --- /dev/null +++ b/apps/site/content/docs/guide/index.mdx @@ -0,0 +1,449 @@ +--- +title: Getting Started with ObjectOS +--- + +# Getting Started with ObjectOS + +Welcome to ObjectOS! This guide will help you understand what ObjectOS is, how it works, and how to build your first metadata-driven application. + +## What is ObjectOS? + +ObjectOS is a **metadata-driven runtime engine** that interprets and executes business applications defined in YAML format. + +### The Big Picture + +``` +┌─────────────────────────────────────────────────┐ +│ ObjectQL (Protocol Repository) │ +│ - Metadata standard in YAML │ +│ - Type definitions (@objectql/types) │ +│ - Database drivers │ +└─────────────────────┬───────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ ObjectOS (Runtime Repository - This One) │ +│ - @objectos/kernel: Execution engine │ +│ - @objectos/server: HTTP API layer │ +│ - @objectos/ui: React components │ +└─────────────────────┬───────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ Your Enterprise Application │ +│ - contacts.object.yml │ +│ - accounts.object.yml │ +│ - opportunities.object.yml │ +└─────────────────────────────────────────────────┘ +``` + +**Key Concept**: ObjectQL is the "blueprint language", ObjectOS is the "builder" that constructs applications from those blueprints. + +## Core Concepts + +### 1. Objects + +An **Object** represents a business entity (like Contact, Account, or Order). Objects are defined in `.object.yml` files. + +```yaml +name: contacts +label: Contact +icon: user +fields: + first_name: + type: text + required: true +``` + +### 2. Fields + +**Fields** define the properties of an object. ObjectOS supports various field types: + +| Field Type | Description | Example | +|------------|-------------|---------| +| `text` | Short text (up to 255 characters) | First Name | +| `textarea` | Long text | Description | +| `number` | Numeric value | Age | +| `currency` | Money value | Price | +| `date` | Date only | Birth Date | +| `datetime` | Date and time | Created At | +| `boolean` | True/false | Is Active | +| `select` | Dropdown options | Status | +| `lookup` | Reference to another object | Account | +| `email` | Email address | Email | +| `url` | Web URL | Website | + +### 3. The Three Layers + +ObjectOS follows a strict architectural principle: + +> **"Kernel handles logic, Drivers handle data, Server handles HTTP."** + +- **Kernel** (`@objectos/kernel`): Validates, enforces permissions, runs hooks +- **Drivers** (`@objectql/driver-*`): Translates to SQL/NoSQL queries +- **Server** (`@objectos/server`): Exposes REST APIs + +This separation means you can swap databases without changing business logic! + +## Installation + +### Prerequisites + +- Node.js 18+ (LTS recommended) +- PostgreSQL 13+ or MongoDB 5+ or SQLite 3+ +- npm, yarn, or pnpm + +### Option 1: Clone the Monorepo + +```bash +# Clone the repository +git clone https://github.com/objectql/objectos.git +cd objectos + +# Install dependencies +pnpm install + +# Build all packages +pnpm run build + +# Start the development server +pnpm run dev +``` + +### Option 2: Use as Dependencies + +```bash +# Create a new project +mkdir my-app +cd my-app +npm init -y + +# Install ObjectOS packages +npm install @objectos/kernel @objectos/server @objectql/driver-sql + +# Install database driver +npm install pg # for PostgreSQL +# or +npm install mongodb # for MongoDB +``` + +## Quick Start: Build a CRM + +Let's build a simple CRM with Contacts and Accounts in under 5 minutes. + +### Step 1: Create Object Definitions + +Create a directory for your metadata: + +```bash +mkdir -p objects +``` + +**objects/account.object.yml**: + +```yaml +name: accounts +label: Account +icon: building +fields: + name: + type: text + label: Account Name + required: true + + industry: + type: select + label: Industry + options: + - Technology + - Finance + - Healthcare + - Retail + + website: + type: url + label: Website + + annual_revenue: + type: currency + label: Annual Revenue + +permission_set: + allowRead: true + allowCreate: ['sales', 'admin'] + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] +``` + +**objects/contact.object.yml**: + +```yaml +name: contacts +label: Contact +icon: user +fields: + first_name: + type: text + label: First Name + required: true + + last_name: + type: text + label: Last Name + required: true + + email: + type: email + label: Email + unique: true + + phone: + type: text + label: Phone + + account: + type: lookup + label: Account + reference_to: accounts + +permission_set: + allowRead: true + allowCreate: ['sales', 'admin'] + allowEdit: ['sales', 'admin'] + allowDelete: ['admin'] +``` + +### Step 2: Initialize the Kernel + +Create `src/main.ts`: + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { PostgresDriver } from '@objectql/driver-sql'; +import * as yaml from 'js-yaml'; +import * as fs from 'fs'; +import * as path from 'path'; + +// 1. Create the kernel +const kernel = new ObjectOS(); + +// 2. Configure database driver +const driver = new PostgresDriver({ + client: 'pg', + connection: { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'postgres', + database: 'mycrm' + } +}); + +// 3. Connect driver to kernel +kernel.useDriver(driver); +await driver.connect(); + +// 4. Load object definitions +const objectsDir = path.join(__dirname, '../objects'); +const files = fs.readdirSync(objectsDir); + +for (const file of files) { + if (file.endsWith('.object.yml')) { + const content = fs.readFileSync(path.join(objectsDir, file), 'utf8'); + const config = yaml.load(content); + await kernel.load(config); + } +} + +console.log('✅ Kernel initialized with objects:', kernel.getObjects()); +``` + +### Step 3: Start the Server + +If using the monorepo, the server is already configured. Otherwise, create a NestJS server: + +```bash +pnpm install @nestjs/core @nestjs/common @nestjs/platform-express +``` + +The server will automatically expose endpoints: + +- `POST /api/data/accounts/query` - Query accounts +- `POST /api/data/accounts` - Create account +- `PATCH /api/data/accounts/:id` - Update account +- `DELETE /api/data/accounts/:id` - Delete account + +Same for contacts! + +### Step 4: Test with cURL + +```bash +# Create an account +curl -X POST http://localhost:3000/api/data/accounts \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Acme Corp", + "industry": "Technology", + "website": "https://acme.com" + }' + +# Query accounts +curl -X POST http://localhost:3000/api/data/accounts/query \ + -H "Content-Type: application/json" \ + -d '{ + "filters": { "industry": "Technology" }, + "limit": 10 + }' + +# Create a contact +curl -X POST http://localhost:3000/api/data/contacts \ + -H "Content-Type: application/json" \ + -d '{ + "first_name": "John", + "last_name": "Doe", + "email": "john@acme.com", + "account": "" + }' +``` + +## Using the UI Components + +ObjectOS provides React components that automatically render based on metadata: + +```tsx +import { ObjectGrid, ObjectForm } from '@objectos/ui'; + +function ContactsPage() { + return ( +
+

Contacts

+ {/* Automatically generates a data grid */} + +
+ ); +} + +function ContactDetail({ contactId }) { + return ( +
+ {/* Automatically generates a form */} + +
+ ); +} +``` + +The components automatically: +- Fetch metadata from the server +- Render appropriate input types +- Handle validation +- Submit data to API +- Display errors + +## Next Steps + +Now that you have a basic understanding: + +1. **[Data Modeling](./data-modeling.mdx)** - Learn about field types, relationships, and validation +2. **[Architecture Guide](./architecture.mdx)** - Understand the three-layer architecture +3. **[Security Guide](./security-guide.mdx)** - Implement authentication and permissions +4. **[Writing Hooks](./logic-hooks.mdx)** - Add custom business logic +5. **[SDK Reference](./sdk-reference.mdx)** - Full API documentation + +## Common Patterns + +### Adding Validation + +```yaml +fields: + email: + type: email + required: true + unique: true + + age: + type: number + min: 0 + max: 150 + + phone: + type: text + pattern: '^\+?[1-9]\d{1,14}$' # E.164 format +``` + +### Adding Relationships + +```yaml +# Master-detail: Deleting Account deletes all Contacts +fields: + account: + type: master_detail + reference_to: accounts + label: Account + +# Lookup: Deleting Account sets Contact.account to null +fields: + account: + type: lookup + reference_to: accounts + label: Account +``` + +### Adding Hooks + +```typescript +// Auto-populate created_by +kernel.on('beforeInsert', async (ctx) => { + if (!ctx.data.created_by) { + ctx.data.created_by = ctx.user.id; + } + ctx.data.created_at = new Date(); +}); + +// Send email when contact is created +kernel.on('afterInsert', async (ctx) => { + if (ctx.objectName === 'contacts') { + await sendWelcomeEmail(ctx.result.email); + } +}); +``` + +## Troubleshooting + +### "Object not found" + +Make sure your `.object.yml` file is in the correct directory and has been loaded: + +```typescript +console.log(kernel.getObjects()); // Should include your object +``` + +### "Driver not connected" + +Ensure you called `driver.connect()` before loading objects: + +```typescript +await driver.connect(); +await kernel.load(objectConfig); +``` + +### "Permission denied" + +Check your `permission_set` in the object definition and ensure the user has the correct role. + +## Resources + +- **[GitHub Repository](https://github.com/objectstack-ai/objectos)** - Source code +- **[ObjectQL Protocol](https://github.com/objectstack-ai/objectql)** - Metadata standard +- **[API Reference](./sdk-reference.mdx)** - Complete API docs + +## Community + +- **Discord**: Coming soon +- **Forum**: Coming soon +- **Twitter**: @ObjectOS (coming soon) + +Need help? Open an issue on GitHub! \ No newline at end of file diff --git a/apps/site/content/docs/guide/logic-actions.mdx b/apps/site/content/docs/guide/logic-actions.mdx new file mode 100644 index 0000000..e8cb4a3 --- /dev/null +++ b/apps/site/content/docs/guide/logic-actions.mdx @@ -0,0 +1,584 @@ +--- +title: Custom Actions Guide +--- + +# Custom Actions Guide + +Actions are custom business logic endpoints that extend ObjectOS beyond standard CRUD operations. Use actions to encapsulate complex business processes, integrate with external systems, or provide specialized functionality. + +## What Are Actions? + +Actions are custom API endpoints that: + +- Execute complex business logic +- Orchestrate multiple operations +- Integrate with external services +- Provide specialized functionality beyond CRUD + +Unlike hooks (which intercept standard operations), actions are explicitly invoked endpoints. + +## When to Use Actions + +Use actions when you need to: + +- **Batch Operations**: Update multiple records with custom logic +- **Complex Workflows**: Implement multi-step processes +- **External Integration**: Call third-party APIs +- **Calculations**: Perform complex computations +- **File Processing**: Handle uploads, conversions, etc. +- **Reports**: Generate custom reports or exports + +## Defining Actions + +### Method 1: Programmatic Registration + +Register actions using the kernel: + +```typescript +import { ObjectOS } from '@objectos/kernel'; + +const kernel = new ObjectOS(); + +kernel.registerAction('contacts.sendEmail', async (ctx) => { + const { id, subject, body } = ctx.params; + + // Get contact + const contact = await kernel.findOne('contacts', id); + + // Send email + await sendEmail({ + to: contact.email, + subject: subject, + body: body + }); + + return { + success: true, + message: 'Email sent successfully' + }; +}); +``` + +### Method 2: Action Files (YAML) + +Define actions in `.action.yml` files: + +```yaml +# actions/send-email.action.yml +name: contacts.sendEmail +label: Send Email +description: Send email to a contact +object: contacts + +parameters: + id: + type: string + required: true + description: Contact ID + + subject: + type: string + required: true + description: Email subject + + body: + type: string + required: true + description: Email body + +handler: ./handlers/send-email.ts +``` + +Handler file: + +```typescript +// handlers/send-email.ts +export async function handler(ctx) { + const { id, subject, body } = ctx.params; + + const contact = await ctx.kernel.findOne('contacts', id); + + await sendEmail({ + to: contact.email, + subject, + body + }); + + return { + success: true, + message: 'Email sent successfully' + }; +} +``` + +## Action Context + +Actions receive a context object with: + +```typescript +interface ActionContext { + // Action parameters + params: Record\; + + // Current user + user: { + id: string; + email: string; + roles: string[]; + }; + + // Kernel instance for data operations + kernel: ObjectOS; + + // Request metadata + request?: { + ip: string; + userAgent: string; + }; +} +``` + +## Common Action Patterns + +### 1. Batch Update + +Update multiple records with custom logic: + +```typescript +kernel.registerAction('opportunities.updateStage', async (ctx) => { + const { ids, stage } = ctx.params; + + // Validate stage + const validStages = ['prospecting', 'qualification', 'proposal', 'negotiation']; + if (!validStages.includes(stage)) { + throw new Error('Invalid stage'); + } + + // Update all opportunities + const results = []; + for (const id of ids) { + const updated = await ctx.kernel.update('opportunities', id, { stage }); + results.push(updated); + } + + return { + updated: results.length, + records: results + }; +}); +``` + +**Usage:** +```bash +POST /api/actions/opportunities.updateStage +{ + "ids": ["opp_1", "opp_2", "opp_3"], + "stage": "qualification" +} +``` + +### 2. Complex Workflow + +Orchestrate multi-step processes: + +```typescript +kernel.registerAction('opportunities.convertToOrder', async (ctx) => { + const { opportunityId } = ctx.params; + + // 1. Get opportunity + const opportunity = await ctx.kernel.findOne('opportunities', opportunityId); + + if (opportunity.stage !== 'closed_won') { + throw new Error('Can only convert closed-won opportunities'); + } + + // 2. Create sales order + const order = await ctx.kernel.insert('sales_orders', { + account: opportunity.account, + amount: opportunity.amount, + status: 'pending', + opportunity: opportunityId + }); + + // 3. Update opportunity + await ctx.kernel.update('opportunities', opportunityId, { + converted_to_order: true, + sales_order: order.id + }); + + // 4. Send notification + await sendEmail({ + to: opportunity.owner.email, + subject: 'Opportunity Converted', + body: `Opportunity ${opportunity.name} has been converted to order ${order.number}` + }); + + return { + success: true, + order: order, + message: 'Opportunity converted to order' + }; +}); +``` + +### 3. External Integration + +Integrate with third-party services: + +```typescript +kernel.registerAction('accounts.enrichFromClearbit', async (ctx) => { + const { accountId } = ctx.params; + + // Get account + const account = await ctx.kernel.findOne('accounts', accountId); + + // Call Clearbit API + const response = await fetch( + `https://company.clearbit.com/v2/companies/find?domain=${account.website}`, + { + headers: { + Authorization: `Bearer ${process.env.CLEARBIT_API_KEY}` + } + } + ); + + const data = await response.json(); + + // Update account with enriched data + const updated = await ctx.kernel.update('accounts', accountId, { + industry: data.category.industry, + employee_count: data.metrics.employees, + annual_revenue: data.metrics.estimatedAnnualRevenue, + description: data.description + }); + + return { + success: true, + enriched_fields: ['industry', 'employee_count', 'annual_revenue', 'description'], + account: updated + }; +}); +``` + +### 4. Data Export + +Generate custom exports: + +```typescript +kernel.registerAction('contacts.exportToCSV', async (ctx) => { + const { filters } = ctx.params; + + // Query contacts + const contacts = await ctx.kernel.find('contacts', { + filters: filters || {}, + fields: ['first_name', 'last_name', 'email', 'phone', 'account.name'], + limit: 10000 + }); + + // Generate CSV + const csv = [ + ['First Name', 'Last Name', 'Email', 'Phone', 'Account'], + ...contacts.map(c => [ + c.first_name, + c.last_name, + c.email, + c.phone, + c.account?.name || '' + ]) + ].map(row => row.join(',')).join('\n'); + + return { + success: true, + filename: `contacts-${Date.now()}.csv`, + content: csv, + count: contacts.length + }; +}); +``` + +### 5. Calculations + +Perform complex calculations: + +```typescript +kernel.registerAction('accounts.calculateHealthScore', async (ctx) => { + const { accountId } = ctx.params; + + const account = await ctx.kernel.findOne('accounts', accountId); + + // Get opportunities + const opportunities = await ctx.kernel.find('opportunities', { + filters: { account: accountId } + }); + + // Get contacts + const contacts = await ctx.kernel.find('contacts', { + filters: { account: accountId } + }); + + // Calculate health score + let score = 0; + + // Revenue factor + if (account.annual_revenue > 1000000) score += 30; + else if (account.annual_revenue > 100000) score += 20; + else score += 10; + + // Opportunity factor + score += Math.min(opportunities.length * 5, 30); + + // Contact factor + score += Math.min(contacts.length * 3, 20); + + // Activity factor (last 90 days) + const recentOpps = opportunities.filter(o => { + const created = new Date(o.created_at); + const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); + return created > ninetyDaysAgo; + }); + score += Math.min(recentOpps.length * 10, 20); + + // Update account + await ctx.kernel.update('accounts', accountId, { + health_score: score + }); + + return { + success: true, + score: score, + factors: { + revenue: account.annual_revenue > 1000000 ? 30 : account.annual_revenue > 100000 ? 20 : 10, + opportunities: Math.min(opportunities.length * 5, 30), + contacts: Math.min(contacts.length * 3, 20), + recent_activity: Math.min(recentOpps.length * 10, 20) + } + }; +}); +``` + +### 6. Bulk Import + +Handle bulk data imports: + +```typescript +kernel.registerAction('contacts.importFromCSV', async (ctx) => { + const { csvData } = ctx.params; + + const lines = csvData.split('\n'); + const headers = lines[0].split(','); + + const results = { + created: 0, + updated: 0, + failed: 0, + errors: [] + }; + + for (let i = 1; i < lines.length; i++) { + const values = lines[i].split(','); + const row = {}; + + headers.forEach((header, index) => { + row[header.trim()] = values[index]?.trim(); + }); + + try { + // Check if contact exists + const existing = await ctx.kernel.find('contacts', { + filters: { email: row.email }, + limit: 1 + }); + + if (existing.length > 0) { + // Update + await ctx.kernel.update('contacts', existing[0].id, row); + results.updated++; + } else { + // Create + await ctx.kernel.insert('contacts', row); + results.created++; + } + } catch (error) { + results.failed++; + results.errors.push({ + row: i, + email: row.email, + error: error.message + }); + } + } + + return { + success: true, + results: results + }; +}); +``` + +## Action Permissions + +Control who can execute actions: + +```yaml +# actions/send-email.action.yml +name: contacts.sendEmail +permissions: + allow: ['sales', 'admin'] +``` + +Or in code: + +```typescript +kernel.registerAction('contacts.sendEmail', async (ctx) => { + // Check permissions + if (!ctx.user.roles.includes('admin') && !ctx.user.roles.includes('sales')) { + throw new Error('Permission denied'); + } + + // Action logic... +}, { + permissions: ['sales', 'admin'] +}); +``` + +## Calling Actions + +### From API + +```bash +POST /api/actions/{actionName} +Content-Type: application/json +Authorization: Bearer + +{ + "param1": "value1", + "param2": "value2" +} +``` + +### From Code + +```typescript +const result = await kernel.executeAction('contacts.sendEmail', { + id: 'contact_123', + subject: 'Hello', + body: 'Test email' +}, ctx); +``` + +### From UI + +```typescript +import { useAction } from '@objectos/ui'; + +function ContactDetail({ contactId }) { + const sendEmail = useAction('contacts.sendEmail'); + + const handleSendEmail = async () => { + const result = await sendEmail({ + id: contactId, + subject: 'Follow up', + body: 'Thank you for your interest' + }); + + alert(result.message); + }; + + return ( + + ); +} +``` + +## Error Handling + +Handle errors gracefully: + +```typescript +kernel.registerAction('opportunities.convert', async (ctx) => { + try { + // Action logic... + } catch (error) { + return { + success: false, + error: error.message + }; + } +}); +``` + +## Testing Actions + +Test actions like any other function: + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { createMockDriver } from '@objectos/test-utils'; + +describe('contacts.sendEmail', () => { + let kernel: ObjectOS; + + beforeEach(() => { + kernel = new ObjectOS(); + kernel.useDriver(createMockDriver()); + + // Register action + kernel.registerAction('contacts.sendEmail', async (ctx) => { + // Action logic... + }); + }); + + it('should send email', async () => { + const result = await kernel.executeAction('contacts.sendEmail', { + id: 'contact_123', + subject: 'Test', + body: 'Test email' + }); + + expect(result.success).toBe(true); + }); +}); +``` + +## Best Practices + +1. **Validate Input**: Always validate parameters +2. **Handle Errors**: Catch and return meaningful errors +3. **Check Permissions**: Verify user has rights to execute action +4. **Use Transactions**: Wrap multiple operations in transactions +5. **Log Activity**: Log action execution for auditing +6. **Return Useful Data**: Return actionable results +7. **Document Actions**: Provide clear descriptions and examples + +## Advanced: Action Plugins + +Create reusable action libraries: + +```typescript +// plugins/email-actions.ts +export function EmailActionsPlugin(kernel: ObjectOS) { + kernel.registerAction('contacts.sendEmail', async (ctx) => { + // Send email logic + }); + + kernel.registerAction('contacts.sendBulkEmail', async (ctx) => { + // Bulk email logic + }); + + kernel.registerAction('contacts.scheduleEmail', async (ctx) => { + // Schedule email logic + }); +} + +// Usage +import { EmailActionsPlugin } from './plugins/email-actions'; +EmailActionsPlugin(kernel); +``` + +## Related Documentation + +- [Logic Hooks](./logic-hooks.mdx) - Intercept standard operations +- [Security Guide](./security-guide.mdx) - Configure permissions +- [SDK Reference](./sdk-reference.mdx) - Complete API reference \ No newline at end of file diff --git a/apps/site/content/docs/guide/logic-hooks.mdx b/apps/site/content/docs/guide/logic-hooks.mdx new file mode 100644 index 0000000..567ceee --- /dev/null +++ b/apps/site/content/docs/guide/logic-hooks.mdx @@ -0,0 +1,439 @@ +--- +title: Writing Logic Hooks +--- + +# Writing Logic Hooks + +Hooks allow you to intercept and modify standard CRUD operations in ObjectOS. They enable you to add custom business logic at specific points in the data lifecycle without modifying the core system. + +## Overview + +Hooks are callback functions that execute before or after specific operations: + +- **Before Hooks**: Validate data, modify input, enforce business rules +- **After Hooks**: Send notifications, update related records, log activities + +## Hook Events + +ObjectOS provides the following lifecycle hooks: + +### Find Operations + +- **`beforeFind`**: Executed before querying records + - Use case: Add additional filters, enforce record-level security +- **`afterFind`**: Executed after records are retrieved + - Use case: Transform data, calculate derived fields + +### Create Operations + +- **`beforeInsert`** (or `beforeCreate`): Executed before creating a record + - Use case: Validate data, set default values, check business rules +- **`afterInsert`** (or `afterCreate`): Executed after record is created + - Use case: Send notifications, create related records, log activity + +### Update Operations + +- **`beforeUpdate`**: Executed before updating a record + - Use case: Validate changes, enforce business constraints +- **`afterUpdate`**: Executed after record is updated + - Use case: Notify stakeholders, sync related data + +### Delete Operations + +- **`beforeDelete`**: Executed before deleting a record + - Use case: Check dependencies, prevent deletion if conditions not met +- **`afterDelete`**: Executed after record is deleted + - Use case: Clean up related data, archive information + +## Hook Registration + +### Basic Registration + +Register a hook using the kernel's `on()` method: + +```typescript +import { ObjectOS } from '@objectos/kernel'; + +const kernel = new ObjectOS(); + +kernel.on('beforeInsert', async (ctx) => { + console.log('Creating object:', ctx.objectName); + console.log('Data:', ctx.data); +}); +``` + +### Object-Specific Hooks + +Register hooks for specific objects only: + +```typescript +kernel.on('beforeInsert', async (ctx) => { + // Only run for contacts + if (ctx.objectName === 'contacts') { + // Custom logic for contacts + } +}); +``` + +Or use a more elegant pattern: + +```typescript +kernel.on('beforeInsert:contacts', async (ctx) => { + // This hook only runs for contacts +}); +``` + +## Hook Context + +Every hook receives a context object with information about the operation: + +```typescript +interface HookContext { + // Object being operated on + objectName: string; + + // User performing the operation + user: { + id: string; + email: string; + roles: string[]; + }; + + // For create/update: the data being saved + data?: Record\; + + // For update/delete: the record ID + id?: string; + + // For find: the query options + filters?: any; + sort?: any; + limit?: number; + + // For after hooks: the result of the operation + result?: any; + + // For update: the old values before update + oldValues?: Record\; +} +``` + +## Common Hook Patterns + +### 1. Auto-Populate Fields + +Automatically set fields when creating records: + +```typescript +kernel.on('beforeInsert', async (ctx) => { + // Set created_at timestamp + ctx.data.created_at = new Date(); + + // Set created_by to current user + ctx.data.created_by = ctx.user.id; + + // Generate unique code + if (ctx.objectName === 'projects') { + ctx.data.code = `PRJ-${Date.now()}`; + } +}); +``` + +### 2. Data Validation + +Enforce business rules before saving: + +```typescript +kernel.on('beforeInsert:contacts', async (ctx) => { + // Ensure email is lowercase + if (ctx.data.email) { + ctx.data.email = ctx.data.email.toLowerCase(); + } + + // Validate age + if (ctx.data.age && ctx.data.age < 18) { + throw new Error('Contacts must be 18 or older'); + } +}); + +kernel.on('beforeUpdate:opportunities', async (ctx) => { + // Check that close_date is set when status is closed + if (ctx.data.status === 'closed' && !ctx.data.close_date) { + throw new Error('Close date is required when closing an opportunity'); + } +}); +``` + +### 3. Send Notifications + +Notify users when records change: + +```typescript +kernel.on('afterInsert:contacts', async (ctx) => { + // Send welcome email to new contact + await sendEmail({ + to: ctx.result.email, + subject: 'Welcome!', + body: `Hello ${ctx.result.first_name}, welcome to our platform!` + }); + + // Notify sales team + await notifySlack({ + channel: '#sales', + message: `New contact created: ${ctx.result.first_name} ${ctx.result.last_name}` + }); +}); + +kernel.on('afterUpdate:opportunities', async (ctx) => { + // Notify owner if stage changed + if (ctx.oldValues.stage !== ctx.result.stage) { + await sendEmail({ + to: ctx.result.owner.email, + subject: 'Opportunity Stage Changed', + body: `Opportunity "${ctx.result.name}" moved to ${ctx.result.stage}` + }); + } +}); +``` + +### 4. Record-Level Security + +Add filters to enforce security: + +```typescript +kernel.on('beforeFind', async (ctx) => { + // Non-admin users can only see their own records + if (!ctx.user.roles.includes('admin')) { + ctx.filters = ctx.filters || {}; + ctx.filters.owner = ctx.user.id; + } +}); +``` + +### 5. Update Related Records + +Sync data across related objects: + +```typescript +kernel.on('afterUpdate:accounts', async (ctx) => { + // If account status changed to inactive, update all contacts + if (ctx.oldValues.status !== ctx.result.status && ctx.result.status === 'inactive') { + await kernel.update('contacts', + { account: ctx.result.id }, + { status: 'inactive' } + ); + } +}); +``` + +### 6. Calculate Derived Fields + +Compute values based on other fields: + +```typescript +kernel.on('beforeInsert:opportunities', async (ctx) => { + // Calculate expected_revenue = amount * probability + if (ctx.data.amount && ctx.data.probability) { + ctx.data.expected_revenue = ctx.data.amount * (ctx.data.probability / 100); + } +}); + +kernel.on('beforeUpdate:opportunities', async (ctx) => { + if (ctx.data.amount || ctx.data.probability) { + const amount = ctx.data.amount || ctx.oldValues.amount; + const probability = ctx.data.probability || ctx.oldValues.probability; + ctx.data.expected_revenue = amount * (probability / 100); + } +}); +``` + +### 7. Prevent Deletion + +Block deletion based on business rules: + +```typescript +kernel.on('beforeDelete:accounts', async (ctx) => { + // Check if account has opportunities + const opportunities = await kernel.find('opportunities', { + filters: { account: ctx.id } + }); + + if (opportunities.length > 0) { + throw new Error('Cannot delete account with active opportunities'); + } +}); +``` + +### 8. Audit Logging + +Log all changes for compliance: + +```typescript +kernel.on('afterInsert', async (ctx) => { + await kernel.insert('audit_log', { + action: 'create', + object: ctx.objectName, + record_id: ctx.result.id, + user_id: ctx.user.id, + timestamp: new Date(), + data: ctx.data + }); +}); + +kernel.on('afterUpdate', async (ctx) => { + await kernel.insert('audit_log', { + action: 'update', + object: ctx.objectName, + record_id: ctx.id, + user_id: ctx.user.id, + timestamp: new Date(), + old_values: ctx.oldValues, + new_values: ctx.result + }); +}); +``` + +## Hook Priority + +When multiple hooks are registered for the same event, they execute in registration order: + +```typescript +// This runs first +kernel.on('beforeInsert', async (ctx) => { + console.log('Hook 1'); +}); + +// This runs second +kernel.on('beforeInsert', async (ctx) => { + console.log('Hook 2'); +}); +``` + +To control execution order, use priority: + +```typescript +kernel.on('beforeInsert', async (ctx) => { + console.log('Low priority - runs last'); +}, { priority: 1 }); + +kernel.on('beforeInsert', async (ctx) => { + console.log('High priority - runs first'); +}, { priority: 10 }); +``` + +## Error Handling + +Throwing an error in a hook will: +1. Stop execution of remaining hooks +2. Rollback the database transaction +3. Return error to the client + +```typescript +kernel.on('beforeInsert:contacts', async (ctx) => { + if (!ctx.data.email) { + throw new Error('Email is required'); + } + + // Check for duplicate email + const existing = await kernel.find('contacts', { + filters: { email: ctx.data.email } + }); + + if (existing.length > 0) { + throw new Error('Email already exists'); + } +}); +``` + +## Asynchronous Hooks + +All hooks support async/await for asynchronous operations: + +```typescript +kernel.on('afterInsert:contacts', async (ctx) => { + // Call external API + await fetch('https://crm.example.com/api/contacts', { + method: 'POST', + body: JSON.stringify(ctx.result) + }); + + // Wait for email to send + await sendEmail({ + to: ctx.result.email, + subject: 'Welcome' + }); +}); +``` + +## Testing Hooks + +Test hooks using a mock driver: + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { createMockDriver } from '@objectos/test-utils'; + +describe('Contact Hooks', () => { + let kernel: ObjectOS; + + beforeEach(() => { + kernel = new ObjectOS(); + kernel.useDriver(createMockDriver()); + + // Register hooks + kernel.on('beforeInsert:contacts', async (ctx) => { + ctx.data.email = ctx.data.email.toLowerCase(); + }); + }); + + it('should lowercase email on insert', async () => { + const result = await kernel.insert('contacts', { + first_name: 'John', + last_name: 'Doe', + email: 'JOHN@EXAMPLE.COM' + }); + + expect(result.email).toBe('john@example.com'); + }); +}); +``` + +## Best Practices + +1. **Keep Hooks Simple**: Each hook should do one thing well +2. **Avoid Circular Dependencies**: Don't create infinite loops by triggering the same event +3. **Handle Errors Gracefully**: Always validate data before processing +4. **Use Async/Await**: For better error handling and readability +5. **Document Side Effects**: Comment what your hooks do +6. **Test Thoroughly**: Hooks can have wide-reaching effects + +## Advanced: Plugin-Based Hooks + +For reusable hooks, create plugins: + +```typescript +// plugins/audit-plugin.ts +export function AuditPlugin(kernel: ObjectOS) { + kernel.on('afterInsert', async (ctx) => { + await logAudit('create', ctx); + }); + + kernel.on('afterUpdate', async (ctx) => { + await logAudit('update', ctx); + }); + + kernel.on('afterDelete', async (ctx) => { + await logAudit('delete', ctx); + }); +} + +// Usage +import { AuditPlugin } from './plugins/audit-plugin'; +AuditPlugin(kernel); +``` + +## Related Documentation + +- [Custom Actions](./logic-actions.mdx) - Create custom API endpoints +- [Security Guide](./security-guide.mdx) - Implement authentication and permissions +- [SDK Reference](./sdk-reference.mdx) - Complete API reference \ No newline at end of file diff --git a/apps/site/content/docs/guide/meta.json b/apps/site/content/docs/guide/meta.json new file mode 100644 index 0000000..e79f99f --- /dev/null +++ b/apps/site/content/docs/guide/meta.json @@ -0,0 +1,16 @@ +{ + "title": "Guide", + "pages": [ + "index", + "architecture", + "platform-components", + "data-modeling", + "security-guide", + "sdk-reference", + "logic-hooks", + "logic-actions", + "ui-framework", + "development-plan", + "contributing-development" + ] +} diff --git a/apps/site/content/docs/guide/platform-components.mdx b/apps/site/content/docs/guide/platform-components.mdx new file mode 100644 index 0000000..ee7a300 --- /dev/null +++ b/apps/site/content/docs/guide/platform-components.mdx @@ -0,0 +1,104 @@ +--- +title: Platform Component Design & Implementation +--- + +# Platform Component Design & Implementation + +This document provides a comprehensive architectural breakdown of the ObjectOS components, focusing on how they interact to realize the full platform functionality. It is intended for Solution Architects and Core Contributors. + +## 📐 Design Philosophy + +1. **Strict Layering**: Interactions flow one way: `UI -> Server -> Kernel -> Driver`. Bypassing layers (e.g., Server directly querying DB) is forbidden. +2. **Metadata Sovereignty**: The state of the system is defined by `*.object.yml` files. Code is just the interpreter. +3. **Adapter Pattern**: All external systems (DB, Auth, Storage) are accessed via pluggable adapters (Drivers). + +--- + +## 🏗️ 1. Logic & State Support (Kernel Layer) + +The `@objectos/kernel` package is the runtime engine. It encapsulates the "Business Intelligence" of the platform. + +### Component Breakdown + +| Component | Responsibility | Implementation Notes | +| :--- | :--- | :--- | +| **ObjectRegistry** | In-memory cache of all object definitions. | Parses YAML on startup. Handles merging of "Standard" and "Custom" objects. | +| **FieldTypeFactory** | Converts metadata types to JS behaviors. | `resolve('currency')` -> Returns formatter, validation regex, and DB column type. | +| **QueryDispatcher** | Translates high-level requests to Driver calls. | `kernel.find('contact')` -> `driver.find('contact_table')`. Handles alias mapping. | +| **HookExecutor** | Runs logic before/after CRUD events. | Supports synchronous and asynchronous patterns. | +| **PermissionGrant** | Resolves "Can User X do Action Y?". | Evaluates Profile rules and Record-level sharing (RLS). | + +### Functional Realization: "Metadata Hot-Reloading" +* **Design**: The Registry watches the filesystem. +* **Flow**: `FileChanged` event -> `Registry.reload()` -> `Driver.syncSchema()` -> `Server.clearCache()`. + +--- + +## 🌐 2. Interface Support (Server Layer) + +The `@objectos/server` package is the Gateway. It translates HTTP/WebSockets into Kernel calls. + +### Component Breakdown + +| Component | Responsibility | Implementation Notes | +| :--- | :--- | :--- | +| **ObjectQLController** | Generic REST endpoint for all objects. | `GET /:objectName/*`. No need to write manual controllers for new objects. | +| **AuthProvider** | Authentication strategy manager. | Wraps `better-auth`. Supports pluggable strategies (GitHub, Google, SSO). | +| **StaticServeModule** | Hosting the compiled frontend. | Resolves `@objectos/web` dist path dynamically for production deployments. | +| **ExceptionFilter** | Standardized error formatting. | Converts `ObjectOSError` into JSON: `{ error: { code: 404, message: "..." } }`. | + +### Functional Realization: "Context-Aware Request" +* **Design**: Every Kernel request requires a `SessionContext`. +* **Flow**: HTTP Header `Authorization` -> Middleware decodes JWT -> Creates `SessionContext(userId)` -> Passes to `kernel.find(ctx, ...)`. + +--- + +## 🖥️ 3. Interaction Support (UI Layer) + +The `@objectos/ui` (Components) and `@objectos/web` (App) packages provide the human interface. + +### Component Breakdown + +| Component | Responsibility | Implementation Notes | +| :--- | :--- | :--- | +| **ObjectGrid** | Data Table with "Excel-like" features. | Uses **TanStack Table**. Implements Virtual Scroll for 100k+ rows. | +| **ObjectForm** | Dynamic Record Editor. | Uses **React-Hook-Form**. Generates Zod schema from Metadata at runtime. | +| **LayoutShell** | Application chrome (Sidebar, Header). | Responsive. Adapts menu based on user permissions. | +| **DataQueryHook** | React Query wrapper for API. | Cache management. `useQuery(['data', 'contacts'], ...)` | + +### Functional Realization: "Dynamic Types" +* **Design**: The UI downloads metadata initially. +* **Flow**: `schema.json` received -> `FieldFactory` maps `type: 'date'` to `` -> Renders Cell. + +--- + +## 🔌 4. Infrastructure Support (Driver Layer) + +Reliable persistence on any database. + +| Component | Responsibility | Implementation Notes | +| :--- | :--- | :--- | +| **AbstractDriver** | Base class for all drivers. | Defines the `IObjectQLDriver` interface. | +| **KnexDriver** | SQL implementation (Postgres/SQLite). | Handles SQL generation, schema migration (`CREATE TABLE`), and transactions. | +| **MongoDriver** | NoSQL implementation. | Maps objects to Collections. Handles `_id` mapping. | + +--- + +## ✅ System Capability Checklist + +To prove functional completeness for a release (e.g., v1.0), the system must pass these integration scenarios: + +### A. The "Zero-Code" Flow +1. **Define**: Create `cars.object.yml` with `brand`, `model`, `price`. +2. **Verify**: API `/api/v4/cars` is immediately available (200 OK). +3. **Interact**: UI shows "Cars" menu item. Grid works. Form works. + +### B. The "Secure-By-Default" Flow +1. **Restrict**: Set `cars.permission.yml` to `read: false` for Guest. +2. **Verify**: Unauthenticated API call returns 401/403. +3. **Interact**: UI hides "Cars" from navigation for Guests. + +### C. The "Enterprise-Scale" Flow +1. **Load**: Insert 10,000 records via Script. +2. **Verify**: Grid loads first page in < 200ms (Persistence + Network). +3. **Interact**: Search/Filter works instantly (Backend Indexing). \ No newline at end of file diff --git a/apps/site/content/docs/guide/sdk-reference.mdx b/apps/site/content/docs/guide/sdk-reference.mdx new file mode 100644 index 0000000..f657a5b --- /dev/null +++ b/apps/site/content/docs/guide/sdk-reference.mdx @@ -0,0 +1,670 @@ +--- +title: SDK Reference +--- + +# SDK Reference + +This document provides a complete reference for the ObjectOS Kernel SDK. Use the Kernel to programmatically interact with your data and business logic. + +## Installation + +```bash +npm install @objectos/kernel @objectql/driver-sql +``` + +## Basic Setup + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { PostgresDriver } from '@objectql/driver-sql'; + +// Create kernel instance +const kernel = new ObjectOS(); + +// Configure database driver +const driver = new PostgresDriver({ + client: 'pg', + connection: { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'password', + database: 'myapp' + } +}); + +// Connect driver to kernel +kernel.useDriver(driver); +await driver.connect(); + +// Load object definitions +await kernel.load(objectConfig); +``` + +## Core Methods + +### `kernel.load(config)` + +Load an object definition into the registry. + +**Parameters:** +- `config`: Object configuration (ObjectConfig) + +**Returns:** `Promise` + +**Example:** +```typescript +await kernel.load({ + name: 'contacts', + label: 'Contact', + fields: { + first_name: { type: 'text', required: true }, + last_name: { type: 'text', required: true }, + email: { type: 'email', unique: true } + } +}); +``` + +### `kernel.find(objectName, options, user?)` + +Query multiple records. + +**Parameters:** +- `objectName`: String - Object API name +- `options`: FindOptions - Query options +- `user?`: User - Current user context (for permissions) + +**Returns:** `Promise[]>` + +**Example:** +```typescript +const contacts = await kernel.find('contacts', { + filters: { + status: 'active', + age: { $gte: 18 } + }, + fields: ['first_name', 'last_name', 'email'], + sort: [{ field: 'last_name', order: 'asc' }], + limit: 50, + skip: 0 +}, currentUser); +``` + +**FindOptions Interface:** +```typescript +interface FindOptions { + filters?: FilterExpression; + fields?: string[]; + sort?: SortExpression[]; + limit?: number; + skip?: number; + include?: string[]; +} +``` + +### `kernel.findOne(objectName, id, options?, user?)` + +Get a single record by ID. + +**Parameters:** +- `objectName`: String - Object API name +- `id`: String - Record ID +- `options?`: FindOptions - Optional query options +- `user?`: User - Current user context + +**Returns:** `Promise>` + +**Example:** +```typescript +const contact = await kernel.findOne('contacts', 'contact_123', { + fields: ['first_name', 'last_name', 'email'], + include: ['account'] +}, currentUser); +``` + +### `kernel.insert(objectName, data, user?)` + +Create a new record. + +**Parameters:** +- `objectName`: String - Object API name +- `data`: Object - Record data +- `user?`: User - Current user context + +**Returns:** `Promise>` + +**Example:** +```typescript +const newContact = await kernel.insert('contacts', { + first_name: 'John', + last_name: 'Doe', + email: 'john@example.com', + phone: '+1234567890' +}, currentUser); + +console.log(newContact.id); // Generated ID +``` + +### `kernel.update(objectName, id, data, user?)` + +Update an existing record. + +**Parameters:** +- `objectName`: String - Object API name +- `id`: String - Record ID +- `data`: Object - Fields to update +- `user?`: User - Current user context + +**Returns:** `Promise>` + +**Example:** +```typescript +const updated = await kernel.update('contacts', 'contact_123', { + phone: '+1987654321', + status: 'inactive' +}, currentUser); +``` + +### `kernel.delete(objectName, id, user?)` + +Delete a record. + +**Parameters:** +- `objectName`: String - Object API name +- `id`: String - Record ID +- `user?`: User - Current user context + +**Returns:** `Promise` + +**Example:** +```typescript +await kernel.delete('contacts', 'contact_123', currentUser); +``` + +### `kernel.count(objectName, filters?, user?)` + +Count records matching filters. + +**Parameters:** +- `objectName`: String - Object API name +- `filters?`: FilterExpression - Filter criteria +- `user?`: User - Current user context + +**Returns:** `Promise` + +**Example:** +```typescript +const activeCount = await kernel.count('contacts', { + status: 'active' +}, currentUser); + +console.log(`${activeCount} active contacts`); +``` + +## Hook Methods + +### `kernel.on(event, handler, options?)` + +Register a lifecycle hook. + +**Parameters:** +- `event`: String - Hook event name +- `handler`: Function - Hook callback +- `options?`: HookOptions - Hook configuration + +**Returns:** `void` + +**Events:** +- `beforeFind`, `afterFind` +- `beforeInsert`, `afterInsert` +- `beforeUpdate`, `afterUpdate` +- `beforeDelete`, `afterDelete` + +**Example:** +```typescript +kernel.on('beforeInsert', async (ctx) => { + ctx.data.created_at = new Date(); + ctx.data.created_by = ctx.user.id; +}); + +kernel.on('afterInsert:contacts', async (ctx) => { + await sendWelcomeEmail(ctx.result.email); +}); +``` + +**Hook Context:** +```typescript +interface HookContext { + objectName: string; + user: User; + data?: Record\; + id?: string; + filters?: any; + result?: any; + oldValues?: Record\; +} +``` + +### `kernel.off(event, handler?)` + +Unregister a hook. + +**Parameters:** +- `event`: String - Hook event name +- `handler?`: Function - Specific handler to remove (optional) + +**Returns:** `void` + +**Example:** +```typescript +// Remove all handlers for event +kernel.off('beforeInsert'); + +// Remove specific handler +kernel.off('beforeInsert', myHandler); +``` + +## Action Methods + +### `kernel.registerAction(name, handler, options?)` + +Register a custom action. + +**Parameters:** +- `name`: String - Action name (e.g., 'contacts.sendEmail') +- `handler`: Function - Action callback +- `options?`: ActionOptions - Action configuration + +**Returns:** `void` + +**Example:** +```typescript +kernel.registerAction('contacts.sendEmail', async (ctx) => { + const { id, subject, body } = ctx.params; + + const contact = await ctx.kernel.findOne('contacts', id); + + await sendEmail({ + to: contact.email, + subject: subject, + body: body + }); + + return { + success: true, + message: 'Email sent' + }; +}); +``` + +### `kernel.executeAction(name, params, user?)` + +Execute a registered action. + +**Parameters:** +- `name`: String - Action name +- `params`: Object - Action parameters +- `user?`: User - Current user context + +**Returns:** `Promise` + +**Example:** +```typescript +const result = await kernel.executeAction('contacts.sendEmail', { + id: 'contact_123', + subject: 'Hello', + body: 'Welcome!' +}, currentUser); + +console.log(result.message); +``` + +## Registry Methods + +### `kernel.getObjects()` + +Get list of all registered objects. + +**Returns:** `string[]` + +**Example:** +```typescript +const objects = kernel.getObjects(); +console.log(objects); // ['contacts', 'accounts', 'opportunities'] +``` + +### `kernel.getObjectConfig(objectName)` + +Get configuration for a specific object. + +**Parameters:** +- `objectName`: String - Object API name + +**Returns:** `ObjectConfig` + +**Example:** +```typescript +const config = kernel.getObjectConfig('contacts'); +console.log(config.label); // 'Contact' +console.log(config.fields.email); // { type: 'email', unique: true, ... } +``` + +### `kernel.hasObject(objectName)` + +Check if an object is registered. + +**Parameters:** +- `objectName`: String - Object API name + +**Returns:** `boolean` + +**Example:** +```typescript +if (kernel.hasObject('contacts')) { + console.log('Contacts object is available'); +} +``` + +## Driver Methods + +### `kernel.useDriver(driver)` + +Set the database driver. + +**Parameters:** +- `driver`: ObjectQLDriver - Driver instance + +**Returns:** `void` + +**Example:** +```typescript +import { PostgresDriver } from '@objectql/driver-sql'; + +const driver = new PostgresDriver({ /* config */ }); +kernel.useDriver(driver); +await driver.connect(); +``` + +### `kernel.getDriver()` + +Get the current database driver. + +**Returns:** `ObjectQLDriver | null` + +**Example:** +```typescript +const driver = kernel.getDriver(); +if (driver) { + console.log('Driver connected'); +} +``` + +## Transaction Methods + +### `kernel.transaction(callback)` + +Execute operations in a database transaction. + +**Parameters:** +- `callback`: Function - Callback with transaction context + +**Returns:** `Promise` + +**Example:** +```typescript +await kernel.transaction(async (trx) => { + // Create account + const account = await trx.insert('accounts', { + name: 'Acme Corp' + }); + + // Create contact + const contact = await trx.insert('contacts', { + first_name: 'John', + last_name: 'Doe', + account: account.id + }); + + // If any operation fails, all changes are rolled back + return { account, contact }; +}); +``` + +## Validation Methods + +### `kernel.validate(objectName, data)` + +Validate data against object schema. + +**Parameters:** +- `objectName`: String - Object API name +- `data`: Object - Data to validate + +**Returns:** `ValidationResult` + +**Example:** +```typescript +const result = kernel.validate('contacts', { + first_name: 'John', + // Missing required 'last_name' + email: 'invalid-email' // Invalid email format +}); + +if (!result.valid) { + console.log(result.errors); + // [ + // { field: 'last_name', message: 'Field is required' }, + // { field: 'email', message: 'Invalid email format' } + // ] +} +``` + +## Permission Methods + +### `kernel.checkPermission(objectName, action, user)` + +Check if user has permission for an action. + +**Parameters:** +- `objectName`: String - Object API name +- `action`: String - Action ('read', 'create', 'update', 'delete') +- `user`: User - User context + +**Returns:** `boolean` + +**Example:** +```typescript +const canCreate = kernel.checkPermission('contacts', 'create', currentUser); + +if (!canCreate) { + throw new Error('Permission denied'); +} +``` + +## Type Definitions + +### User + +```typescript +interface User { + id: string; + email: string; + name?: string; + roles: string[]; + [key: string]: any; +} +``` + +### FilterExpression + +```typescript +type FilterExpression = { + [field: string]: any | { + $eq?: any; + $ne?: any; + $gt?: any; + $gte?: any; + $lt?: any; + $lte?: any; + $in?: any[]; + $nin?: any[]; + $like?: string; + $ilike?: string; + $null?: boolean; + $between?: [any, any]; + }; + $and?: FilterExpression[]; + $or?: FilterExpression[]; + $not?: FilterExpression; +}; +``` + +### SortExpression + +```typescript +interface SortExpression { + field: string; + order: 'asc' | 'desc'; +} +``` + +### ObjectConfig + +```typescript +interface ObjectConfig { + name: string; + label?: string; + icon?: string; + description?: string; + enable_api?: boolean; + enable_audit?: boolean; + fields: { + [fieldName: string]: FieldConfig; + }; + permission_set?: PermissionSet; +} +``` + +### FieldConfig + +```typescript +interface FieldConfig { + type: FieldType; + label?: string; + description?: string; + required?: boolean; + unique?: boolean; + default?: any; + readonly?: boolean; + hidden?: boolean; + // Type-specific attributes + [key: string]: any; +} + +type FieldType = + | 'text' | 'textarea' | 'email' | 'url' | 'phone' + | 'number' | 'currency' | 'percent' + | 'date' | 'datetime' | 'time' + | 'boolean' | 'select' | 'multiselect' + | 'lookup' | 'master_detail' + | 'autonumber' | 'formula' | 'rollup_summary'; +``` + +## Complete Example + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { PostgresDriver } from '@objectql/driver-sql'; + +// Initialize +const kernel = new ObjectOS(); +const driver = new PostgresDriver({ + connection: process.env.DATABASE_URL +}); + +kernel.useDriver(driver); +await driver.connect(); + +// Load objects +await kernel.load({ + name: 'contacts', + label: 'Contact', + fields: { + first_name: { type: 'text', required: true }, + last_name: { type: 'text', required: true }, + email: { type: 'email', unique: true } + } +}); + +// Register hooks +kernel.on('beforeInsert', async (ctx) => { + ctx.data.created_at = new Date(); + ctx.data.created_by = ctx.user.id; +}); + +// Register actions +kernel.registerAction('contacts.sendEmail', async (ctx) => { + const contact = await ctx.kernel.findOne('contacts', ctx.params.id); + await sendEmail(contact.email, ctx.params.subject, ctx.params.body); + return { success: true }; +}); + +// Use the kernel +const currentUser = { id: 'user_123', roles: ['sales'] }; + +// Create +const contact = await kernel.insert('contacts', { + first_name: 'John', + last_name: 'Doe', + email: 'john@example.com' +}, currentUser); + +// Query +const contacts = await kernel.find('contacts', { + filters: { status: 'active' }, + sort: [{ field: 'last_name', order: 'asc' }], + limit: 50 +}, currentUser); + +// Update +await kernel.update('contacts', contact.id, { + phone: '+1234567890' +}, currentUser); + +// Execute action +await kernel.executeAction('contacts.sendEmail', { + id: contact.id, + subject: 'Welcome', + body: 'Hello!' +}, currentUser); + +// Delete +await kernel.delete('contacts', contact.id, currentUser); +``` + +## Error Handling + +The SDK throws specific error types: + +```typescript +try { + await kernel.insert('contacts', data); +} catch (error) { + if (error instanceof ValidationError) { + console.log('Validation failed:', error.errors); + } else if (error instanceof PermissionDeniedError) { + console.log('Permission denied'); + } else if (error instanceof NotFoundError) { + console.log('Record not found'); + } else { + console.log('Unexpected error:', error); + } +} +``` + +## Related Documentation + +- [Data Modeling](./data-modeling.mdx) - Define objects and fields +- [Logic Hooks](./logic-hooks.mdx) - Intercept operations +- [Custom Actions](./logic-actions.mdx) - Create custom endpoints +- [Query Language](../spec/query-language.mdx) - Filter syntax reference \ No newline at end of file diff --git a/apps/site/content/docs/guide/security-guide.mdx b/apps/site/content/docs/guide/security-guide.mdx new file mode 100644 index 0000000..a5cafd4 --- /dev/null +++ b/apps/site/content/docs/guide/security-guide.mdx @@ -0,0 +1,612 @@ +--- +title: Security Guide +--- + +# Security Guide + +Security is a fundamental concern in ObjectOS. This guide covers authentication, authorization, permissions, and security best practices for building secure applications. + +## Security Architecture + +ObjectOS implements a multi-layered security model: + +1. **Authentication**: Verify user identity +2. **Authorization**: Control access to objects and records +3. **Field-Level Security**: Control access to specific fields +4. **Record-Level Security**: Filter records based on ownership/sharing +5. **Audit Logging**: Track all data changes + +## Authentication + +### Using Better-Auth + +ObjectOS uses [Better-Auth](https://better-auth.com) for authentication, supporting multiple strategies: + +- Local (email/password) +- OAuth 2.0 (Google, GitHub, Microsoft) +- SAML (Enterprise SSO) +- LDAP +- Magic Links + +### Setup Authentication + +```typescript +import { ObjectOS } from '@objectos/kernel'; +import { AuthPlugin } from '@objectos/plugin-auth'; + +const kernel = new ObjectOS(); + +// Configure auth plugin +kernel.use(AuthPlugin({ + providers: { + local: { + enabled: true + }, + google: { + enabled: true, + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET + } + }, + jwt: { + secret: process.env.JWT_SECRET, + expiresIn: '7d' + } +})); +``` + +### Login Endpoint + +```bash +POST /api/auth/login +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "securepassword" +} +``` + +**Response:** +```json +{ + "success": true, + "token": "eyJhbGciOiJIUzI1NiIs...", + "user": { + "id": "user_123", + "email": "user@example.com", + "name": "John Doe", + "roles": ["sales"] + } +} +``` + +### Using Tokens + +Include the token in all authenticated requests: + +```bash +GET /api/data/contacts +Authorization: Bearer eyJhbGciOiJIUzI1NiIs... +``` + +## Authorization + +### Object-Level Permissions + +Control CRUD access to entire objects using permission sets: + +```yaml +# objects/contacts.object.yml +name: contacts +label: Contact + +permission_set: + allowRead: true # Everyone can read + allowCreate: ['sales', 'admin'] # Only sales and admin can create + allowEdit: ['sales', 'admin'] # Only sales and admin can edit + allowDelete: ['admin'] # Only admin can delete +``` + +**Permission Values:** +- `true`: Everyone has permission +- `false`: No one has permission (system only) +- `['role1', 'role2']`: Only specified roles have permission + +### Permission Checking + +ObjectOS automatically checks permissions on all operations: + +```typescript +// User without 'sales' or 'admin' role tries to create contact +await kernel.insert('contacts', { ... }); +// Throws: PermissionDeniedError: Insufficient permissions to create contacts +``` + +### Custom Permission Checks + +Check permissions programmatically: + +```typescript +kernel.on('beforeInsert', async (ctx) => { + // Custom permission logic + if (ctx.objectName === 'opportunities' && ctx.data.amount > 100000) { + // Large opportunities require special approval + if (!ctx.user.roles.includes('senior_sales')) { + throw new Error('Large opportunities require senior sales approval'); + } + } +}); +``` + +## Field-Level Security + +Control access to specific fields: + +```yaml +fields: + salary: + type: currency + label: Salary + visible_to: ['hr', 'admin'] # Only HR and admin can see + editable_by: ['hr'] # Only HR can edit + + social_security: + type: text + label: SSN + visible_to: ['hr'] # Only HR can see + editable_by: [] # No one can edit (read-only) +``` + +**Behavior:** +- Users without `visible_to` roles cannot see the field in queries or forms +- Users without `editable_by` roles cannot update the field +- Attempts to read/write restricted fields return 403 Forbidden + +## Record-Level Security (RLS) + +Filter records based on ownership or custom criteria: + +### Ownership-Based Access + +```typescript +kernel.on('beforeFind', async (ctx) => { + // Non-admin users can only see their own records + if (ctx.objectName === 'opportunities' && !ctx.user.roles.includes('admin')) { + ctx.filters = ctx.filters || {}; + ctx.filters.owner = ctx.user.id; + } +}); +``` + +### Team-Based Access + +```typescript +kernel.on('beforeFind', async (ctx) => { + if (ctx.objectName === 'accounts') { + // Users can see accounts owned by their team + const userTeam = await kernel.findOne('users', ctx.user.id, { + fields: ['team_id'] + }); + + ctx.filters = ctx.filters || {}; + ctx.filters.team_id = userTeam.team_id; + } +}); +``` + +### Sharing Rules + +Define sharing rules in metadata: + +```yaml +# objects/opportunities.object.yml +sharing_rules: + - name: Team Access + criteria: + team: current_user.team_id + access: read_write + + - name: Manager Access + criteria: + reports_to: current_user.id + access: read_only +``` + +## Roles and Profiles + +### Defining Roles + +Roles are defined in the auth system: + +```yaml +# objects/_roles.object.yml +name: _roles +label: Role +system: true + +records: + - name: admin + label: Administrator + description: Full system access + + - name: sales + label: Sales User + description: Sales team member + + - name: support + label: Support User + description: Customer support + + - name: guest + label: Guest + description: Limited read-only access +``` + +### Assigning Roles + +```typescript +// Assign role to user +await kernel.update('users', userId, { + roles: ['sales', 'support'] +}); +``` + +### Role Hierarchy + +Define role inheritance: + +```yaml +role_hierarchy: + admin: + inherits: [sales, support, guest] + + sales: + inherits: [guest] + + support: + inherits: [guest] +``` + +## Input Validation & Sanitization + +### Automatic Validation + +ObjectOS automatically validates: +- Required fields +- Data types +- Unique constraints +- Min/max values +- Pattern matching + +### Custom Validation + +Add custom validation in hooks: + +```typescript +kernel.on('beforeInsert:contacts', async (ctx) => { + // Validate email domain + if (ctx.data.email && !ctx.data.email.endsWith('@company.com')) { + throw new Error('Only company email addresses allowed'); + } + + // Sanitize phone number + if (ctx.data.phone) { + ctx.data.phone = ctx.data.phone.replace(/[^0-9+]/g, ''); + } +}); +``` + +### SQL Injection Prevention + +ObjectOS drivers automatically parameterize queries: + +```typescript +// Safe - parameters are escaped +await kernel.find('contacts', { + filters: { + email: userInput // Automatically sanitized + } +}); +``` + +### XSS Prevention + +Sanitize HTML content: + +```typescript +import { sanitizeHtml } from '@objectos/security'; + +kernel.on('beforeInsert', async (ctx) => { + if (ctx.data.description) { + ctx.data.description = sanitizeHtml(ctx.data.description); + } +}); +``` + +## Audit Logging + +Enable audit logging to track all changes: + +```yaml +# objects/contacts.object.yml +name: contacts +enable_audit: true +``` + +### Audit Log Structure + +```yaml +# objects/_audit_log.object.yml (system object) +name: _audit_log +system: true + +fields: + action: + type: select + options: [create, update, delete] + + object: + type: text + + record_id: + type: text + + user_id: + type: lookup + reference_to: users + + old_values: + type: json + + new_values: + type: json + + timestamp: + type: datetime + + ip_address: + type: text +``` + +### Querying Audit Logs + +```typescript +// Get all changes to a record +const logs = await kernel.find('_audit_log', { + filters: { + object: 'contacts', + record_id: 'contact_123' + }, + sort: [{ field: 'timestamp', order: 'desc' }] +}); +``` + +## Rate Limiting + +Prevent abuse with rate limiting: + +```typescript +import { RateLimitPlugin } from '@objectos/plugin-rate-limit'; + +kernel.use(RateLimitPlugin({ + windowMs: 60 * 1000, // 1 minute + maxRequests: 100, // 100 requests per window + perUser: true // Rate limit per user +})); +``` + +## CORS Configuration + +Configure Cross-Origin Resource Sharing: + +```typescript +// packages/server/src/main.ts +const app = await NestFactory.create(AppModule); + +app.enableCors({ + origin: [ + 'http://localhost:5173', // Development + 'https://app.example.com' // Production + ], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'] +}); +``` + +## API Key Authentication + +For server-to-server communication: + +```typescript +kernel.use(ApiKeyPlugin({ + keys: [ + { + key: process.env.API_KEY_1, + name: 'Integration Server', + scopes: ['read:contacts', 'write:contacts'] + } + ] +})); +``` + +**Usage:** +```bash +GET /api/data/contacts +X-API-Key: your-api-key-here +``` + +## Security Headers + +Set security headers in production: + +```typescript +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"] + } + }, + hsts: { + maxAge: 31536000, + includeSubDomains: true, + preload: true + } +})); +``` + +## Encryption + +### Encrypting Sensitive Fields + +```typescript +import { encrypt, decrypt } from '@objectos/security'; + +kernel.on('beforeInsert', async (ctx) => { + // Encrypt SSN before storing + if (ctx.data.social_security) { + ctx.data.social_security = encrypt(ctx.data.social_security); + } +}); + +kernel.on('afterFind', async (ctx) => { + // Decrypt SSN when retrieving + ctx.result.forEach(record => { + if (record.social_security) { + record.social_security = decrypt(record.social_security); + } + }); +}); +``` + +### Environment Variables + +Never hard-code secrets: + +```bash +# .env +DATABASE_URL=postgresql://... +JWT_SECRET=your-secret-key-here +ENCRYPTION_KEY=your-encryption-key-here +API_KEY=your-api-key-here +``` + +```typescript +// ❌ BAD +const secret = 'my-secret-key'; + +// ✅ GOOD +const secret = process.env.JWT_SECRET; +``` + +## Security Checklist + +### Development + +- [ ] Use environment variables for secrets +- [ ] Enable strict TypeScript mode +- [ ] Validate all user input +- [ ] Sanitize HTML content +- [ ] Use parameterized queries (automatic with drivers) +- [ ] Implement proper error handling +- [ ] Don't expose stack traces to clients + +### Production + +- [ ] Use HTTPS only +- [ ] Set security headers (helmet) +- [ ] Enable rate limiting +- [ ] Configure CORS properly +- [ ] Enable audit logging +- [ ] Use strong JWT secrets +- [ ] Implement session timeout +- [ ] Regular security audits +- [ ] Keep dependencies updated +- [ ] Enable database encryption at rest +- [ ] Use VPC/private networks +- [ ] Implement IP whitelisting (if needed) + +## Common Security Patterns + +### 1. Multi-Factor Authentication + +```typescript +kernel.use(MFAPlugin({ + enabled: true, + methods: ['totp', 'sms'], + required_for_roles: ['admin'] +})); +``` + +### 2. Password Policies + +```typescript +kernel.use(PasswordPolicyPlugin({ + minLength: 12, + requireUppercase: true, + requireLowercase: true, + requireNumbers: true, + requireSpecialChars: true, + preventReuse: 5, // Can't reuse last 5 passwords + expiryDays: 90 // Force reset every 90 days +})); +``` + +### 3. Session Management + +```typescript +kernel.use(SessionPlugin({ + maxAge: 24 * 60 * 60 * 1000, // 24 hours + rolling: true, // Refresh on activity + secure: true, // HTTPS only + httpOnly: true, // Not accessible via JavaScript + sameSite: 'strict' +})); +``` + +## Testing Security + +Test security in your test suite: + +```typescript +describe('Security', () => { + it('should deny access without authentication', async () => { + await expect( + kernel.find('contacts', {}, null) // No user context + ).rejects.toThrow('Authentication required'); + }); + + it('should deny create without permission', async () => { + const guestUser = { id: '1', roles: ['guest'] }; + + await expect( + kernel.insert('contacts', { email: 'test@test.com' }, guestUser) + ).rejects.toThrow('Insufficient permissions'); + }); + + it('should filter records by ownership', async () => { + const user = { id: 'user_1', roles: ['sales'] }; + + const results = await kernel.find('opportunities', {}, user); + + // All results should belong to user + expect(results.every(r => r.owner === user.id)).toBe(true); + }); +}); +``` + +## Security Resources + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Better-Auth Documentation](https://better-auth.com) +- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) + +## Related Documentation + +- [Logic Hooks](./logic-hooks.mdx) - Implement custom security logic +- [SDK Reference](./sdk-reference.mdx) - API reference +- [HTTP Protocol](../spec/http-protocol.mdx) - Authentication headers \ No newline at end of file diff --git a/apps/site/content/docs/guide/ui-framework.mdx b/apps/site/content/docs/guide/ui-framework.mdx new file mode 100644 index 0000000..46e347a --- /dev/null +++ b/apps/site/content/docs/guide/ui-framework.mdx @@ -0,0 +1,245 @@ +--- +title: Standard UI Components Reference +--- + +# Standard UI Components Reference + +This document defines the standard component library for `@objectos/ui`. These components are the reference implementations for the **View & Layout Specifications**. + +--- + +## 1. Page Architecture + +The top-level system for composing screens from Widgets and Layouts. Driven by `*.page.yml`. + +### `ObjectPage` +The master controller for any URL-addressable page in the application. + +- **Props**: + - `pageId`: string (e.g., "dashboard-sales") - *The ID of the page definition to load.* + - `context`: Record\ - *Optional global variable to pass to widgets (e.g., `{ recordId: "123" }`)*. +- **Behavior**: + 1. Loads `*.page.yml` from the Metadata Kernel. + 2. Resolves permissions (can the user see this page?). + 3. Injects the `PageContext` provider. + 4. Renders the appropriate **Layout** renderer. + +### `DashboardLayout` +**Implements:** `layout: dashboard` / `layout: grid` + +A Responsive Grid Layout Engine (based on `react-grid-layout` or CSS Grid). + +- **Features**: + - **Grid System**: 12-column grid. + - **Responsive**: Stacks widgets on mobile, respects `(x, y, w, h)` on desktop. + - **Editable**: (Future) Allows dragging widgets to rearrange if user has "Customize Page" permission. + +### `SingleColumnLayout` +**Implements:** `layout: single_column` / `layout: list` + +A simple stack layout, mostly used for Wiki-style pages or Mobile app views. + +--- + +## 2. Page Components (Widgets) + +These are the "Blocks" that live inside a Layout. In `*.page.yml`, these are the items in the `components` array. + +### `WidgetMetric` +**Type:** `metric` +Displays a single KPI with trend indicator. +- **Props**: `label`, `value` (expression or query), `trend`, `format`. +- **UI**: A compact card (`Card`, `CardHeader`, `CardContent`). + +### `WidgetChart` +**Type:** `chart` +Renders a visualization. +- **Props**: `chart_id`. +- **Behavior**: Use the `chart_id` to fetch the Chart definition (`*.chart.yml`), then renders the appropriate **Visualization Primitive** (see Section 7). + +### `WidgetView` +**Type:** `view` + Embeds a full `ObjectView` inside a dashboard tile. +- **Props**: `view_id` OR (`object`, `view`). +- **Behavior**: Renders the `` component (see Section 3) within a constrained container. Key difference: The toolbar might be simplified (e.g., no global search) to fit into a widget. + +### `WidgetHtml` +**Type:** `html` / `markdown` +Renders static or dynamic content. +- **Props**: `content` (HTML/Markdown string). + +--- + +## 3. View Architecture + +The system for rendering Object Data Collections. Can appear standalone (as a full page) or inside a Widget. + +### `ObjectView` +The "Switchboard" component. It connects to the Metadata Registry, fetches the requested view definition (YAML), and renders the appropriate concrete View Component. + +- **Props**: + - `objectName`: string (e.g., "tasks") + - `viewName`: string (e.g., "task_kanban") - *Optional, defaults to object's default view* + - `initialFilter`: FindOptions - *Optional runtime filters* +- **Behavior**: + 1. Loads `*.view.yml`. + 2. Resolves `type` (e.g., `kanban`). + 3. Renders ``. + +--- + +## 4. View Component Library + +These "Smart Components" implement the specific logic for each View Type defined in `*.view.yml`. + +### `ObjectGridView` +**Implements:** `type: list`, `type: grid` + +A high-density, interactive data table based on **AG Grid**. + +- **Features**: + - **Advanced Layout**: Supports Tabs for switching Views (e.g., "Outline", "Summary"). + - **Row Drag & Drop**: Manual reordering of records. + - **Selection**: Checkbox selection with "Select All" and Indeterminate states. + - **Cell Rendering**: + - **Status**: Visual badges with icons. + - **User/Reviewer**: User avatars or dropdown assignment. + - **Progress**: visual progress bars or drag handles. + - **Columns**: + - **Visibility Control**: Users can toggle columns via dropdown. + - **Resizing & Sorting**: Built-in AG Grid capabilities. + - **Pagination**: Custom footer with "Rows per page" and navigation controls. +- **Config Support**: `columns`, `sort`, `filters`, `row_actions`, `bulk_actions`, `tabs`. + +### `ObjectKanbanView` +**Implements:** `type: kanban` + +A drag-and-drop board for stage-based management. + +- **Features**: + - **Grouping**: Buckets records by `group_by` field (Select/Lookup). + - **Drag & Drop**: Updates the `group_by` field on drop. + - **Summaries**: Column headers show count/sum (e.g., "Expected Revenue"). +- **Config Support**: `group_by`, `card`, `columns` (colors, limits). + +### `ObjectCalendarView` +**Implements:** `type: calendar` + +A full-sized calendar for date-based records. + +- **Features**: + - **Views**: Month, Week, Day, Agenda. + - **DnD**: Drag to reschedule (update `start_date` / `end_date`). + - **Events**: Color-coded based on `color_mapping`. +- **Config Support**: `start_date_field`, `end_date_field`, `color_mapping`. + +### `ObjectTimelineView` +**Implements:** `type: timeline` + +A Gantt-chart style visualization for project planning. + +- **Features**: + - **Dependencies**: Renders connecting lines if dependency field exists. + - **Resizing**: Drag edge to change duration. + - **Grouping**: Group rows by User or Project. +- **Config Support**: `start_date_field`, `end_date_field`, `group_by`. + +### `ObjectGalleryView` +**Implements:** `type: card` + +A responsive grid of cards, ideal for visual assets or mobile views. + +- **Features**: + - **Cover Image**: Renders large media preview. + - **Responsive**: Reflows from 1 column (mobile) to N columns (desktop). +- **Config Support**: `card` (title, subtitle, image). + +### `ObjectDetailView` +**Implements:** `type: detail` + +A read-only presentation of a single record. + +- **Features**: + - **Sections**: Layout grouping (2-col, 1-col). + - **Related Lists**: Renders child records in sub-grids (e.g., "Project Tasks"). + - **Activity Timeline**: System generated history and comments. +- **Config Support**: `sections`, `related_lists`. + +### `ObjectFormView` +**Implements:** `type: form` + +A full-featured editor for creating or updating records. + +- **Features**: + - **Validation**: Client-side (Zod) + Server-side error mapping. + - **Conditional Fields**: Hides inputs based on other field values. + - **Layout**: Respects the same section layout as Detail View. +- **Config Support**: `sections`, `visible_if`. + +--- + +## 5. Layout & Shell + +Building blocks for the outer application shell and view wrappers. + +### `ViewToolbar` +The header bar rendered above any view. +- **Props**: `title`, `actions`, `viewSwitcherOptions`. +- **Contains**: + - **Breadcrumbs**: Navigation path. + - **View Switcher**: Dropdown to change current view. + - **Global Actions**: "New Record", "Import/Export". + +### `FilterBuilder` +A complex filter construction UI. +- **Features**: + - Add/Remove criteria rows. + - Field-aware method selection (e.g., Date fields show "Before", "After"; Text shows "Contains"). + - "And/Or" logic groups. + +--- + +## 6. Field System + +The `Field` component is the factory that decides which specific widget to render inside Forms, Cells, and Cards. + +**Import:** `import { Field } from '@objectos/ui/components/fields/Field'` + +| ObjectQL Type | UI Component | Usage | +| :--- | :--- | :--- | +| `text`, `string` | `TextField` | Single-line input. | +| `textarea` | `TextAreaField` | Multi-line text. | +| `email`, `url`, `phone` | `TextField` | Input with specific validation/masking. | +| `number`, `integer` | `NumberField` | Numeric input with step. | +| `currency`, `percent` | `NumberField` | Formatted numeric display. | +| `boolean` | `BooleanField` | Checkbox (Grid/List) or Switch (Form). | +| `date`, `datetime` | `DateField` | Popover calendar picker. | +| `select` | `SelectField` | Dropdown or Command Palette. | +| `lookup`, `master_detail` | `LookupField` | Async searchable select for related records. | +| `image`, `file` | `FileUploadField` | Drag-and-drop uploader + Preview. | +| `formula` | `ReadOnlyField` | Display-only calculated value. | + +--- + +## 7. Visualization Primitives + +Low-level charting components used by `WidgetChart`. + +### `ChartAreaInteractive` +Interactive area chart for trends. + +### `ChartBarInteractive` +Bar chart for categorical comparisons. + +### `ChartDonut` +Donut/Pie chart for part-to-whole analysis. + +--- + +## 8. UI Atoms + +We strictly use **Tailwind CSS** and **Radix UI** (shadcn/ui) primitives. + +- **`src/components/ui/`**: Low-level atoms. + - `button.tsx`, `input.tsx`, `dialog.tsx`, `popover.tsx`, `calendar.tsx`. +- **Styling**: All components must use CSS variables for theming (`--primary`, `--radius`). \ No newline at end of file diff --git a/apps/site/content/docs/index.mdx b/apps/site/content/docs/index.mdx index b182736..5984253 100644 --- a/apps/site/content/docs/index.mdx +++ b/apps/site/content/docs/index.mdx @@ -1,66 +1,80 @@ --- -title: Welcome to ObjectOS -description: The Business Operating System for the ObjectStack ecosystem +title: ObjectOS Documentation +description: The Business Operating System - Orchestrate Identity, Workflows, and Local-First Sync in one unified runtime. The Kernel for your Enterprise. --- -# Welcome to ObjectOS Documentation +import { Card, Cards } from 'fumadocs-ui/components/card'; + +# ObjectOS - The Business Operating System + +**Orchestrate Identity, Workflows, and Local-First Sync in one unified runtime. The Kernel for your Enterprise.** ObjectOS is the **Business Operating System** for the ObjectStack ecosystem, providing: -- **State Management** - Centralized state orchestration and synchronization -- **Identity & Security** - Authentication, RBAC, and audit logging -- **Workflow Engine** - Business process automation with state machines -- **Plugin System** - Extensible architecture with manifest-driven plugins -- **Local-First Sync** - Offline-first data synchronization with conflict resolution +- 🛡️ **Identity & Governance** - Built-in RBAC, SSO (OIDC/SAML), and granular field-level security. Not just a login library, but a complete governance engine. +- ⚙️ **Workflow Orchestration** - Finite State Machines (FSM) as code. Define approval chains and automation rules declaratively in YAML. +- 🔄 **Local-First Sync Engine** - Handles conflict resolution (CRDT/LWW) to keep offline clients in sync with the server. +- 🔌 **Plugin Architecture** - Micro-kernel design. Everything—CRM, HRM, ERP—is just a plugin loaded via a Manifest. +- 🎯 **Declarative Logic** - Business processes defined as metadata, not code. State machines, validation rules, and automation triggers. +- 🏗️ **Three-Layer Architecture** - Clean separation—Kernel handles orchestration, Drivers handle persistence, Server handles protocols. -## Quick Start +## The Problem with Traditional Development -```bash -# Install dependencies -pnpm install +You don't build an operating system by writing your own process scheduler and file system from scratch. You use an OS like Linux or Windows. -# Start development server -pnpm docs:dev +**So why are you writing your own Auth system, Workflow engine, and Sync logic for every business application?** -# Build for production -pnpm docs:build -``` +Modern enterprise applications suffer from: +- **Microservices Sprawl:** Too many fragmented services with duplicated auth, logging, and data access logic. +- **Spaghetti Monoliths:** Business logic tangled with HTTP handlers, making testing and changes painful. +- **Reinventing Sync:** Every team builds their own half-baked solution for offline-first applications. -## Documentation Structure +## The Solution: ObjectOS as Your Enterprise Kernel -This documentation site is built with [Fumadocs](https://fumadocs.vercel.app/), a modern documentation framework based on Next.js. +**ObjectOS is the Operating System for your business applications.** It provides the unified Control Plane that orchestrates: -### Adding Documentation +- **Identity & Access:** Centralized authentication and RBAC enforcement +- **Data Synchronization:** Automatic conflict resolution for local-first architectures +- **Business Processes:** Workflow state machines and automation triggers +- **Plugin Ecosystem:** Modular extensions that integrate seamlessly -Create MDX files in `apps/site/content/docs/`: +Think of it as the **kernel layer** between your business logic and infrastructure. Just as Linux manages processes, memory, and I/O, ObjectOS manages identity, state, and synchronization. -``` -apps/site/content/docs/ -├── index.mdx # Home page -├── getting-started/ # Getting started guides -│ ├── index.mdx -│ └── installation.mdx -└── guides/ # User guides - ├── index.mdx - └── authentication.mdx -``` +## Quick Start -### Navigation +```bash +# Clone and install +git clone https://github.com/objectstack-ai/objectos +cd objectos && pnpm install -Navigation is configured in `meta.json` files within each directory: +# Start the kernel +pnpm dev -```json -{ - "title": "Section Title", - "pages": ["page-slug", "another-page"] -} +# The API is live at http://localhost:3000 +# Visit http://localhost:3000/api/metadata to see loaded objects ``` ## Next Steps -- Add your documentation content in MDX format -- Configure navigation with `meta.json` files -- Customize the theme in `tailwind.config.ts` -- Deploy to your preferred hosting platform - -For more information about Fumadocs, visit the [official documentation](https://fumadocs.vercel.app/). + + + Get up and running with ObjectOS quickly + + + + Deep dive into the kernel design + + + + Learn about protocols and formats + + diff --git a/apps/site/content/docs/meta.json b/apps/site/content/docs/meta.json new file mode 100644 index 0000000..5af3596 --- /dev/null +++ b/apps/site/content/docs/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Documentation", + "pages": [ + "index", + "getting-started", + "guide", + "spec" + ] +} diff --git a/apps/site/content/docs/spec/http-protocol.mdx b/apps/site/content/docs/spec/http-protocol.mdx new file mode 100644 index 0000000..db06b7c --- /dev/null +++ b/apps/site/content/docs/spec/http-protocol.mdx @@ -0,0 +1,640 @@ +--- +title: HTTP Protocol Specification +--- + +# HTTP Protocol Specification + +The ObjectOS Server exposes a RESTful HTTP API that provides complete CRUD operations for all objects defined in metadata. This document specifies the complete HTTP protocol. + +## Base URL + +``` +http://localhost:3000/api +``` + +For production deployments, replace with your actual domain. + +## Authentication + +### Header Format + +All authenticated requests must include the Authorization header: + +``` +Authorization: Bearer +``` + +### Getting a Token + +Obtain an authentication token through the auth endpoint: + +```bash +POST /api/auth/login +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "password" +} +``` + +**Response:** +```json +{ + "token": "eyJhbGciOiJIUzI1NiIs...", + "user": { + "id": "user_123", + "email": "user@example.com", + "name": "John Doe" + } +} +``` + +## Request Headers + +### Required Headers + +``` +Content-Type: application/json +``` + +### Optional Headers + +``` +Authorization: Bearer # For authenticated requests +X-Request-ID: # For request tracking +Accept-Language: en-US # For internationalization +``` + +## Response Format + +### Success Response + +All successful responses follow this structure: + +```json +{ + "success": true, + "data": { + // Response data + }, + "meta": { + "count": 100, // Total records (for queries) + "page": 1, // Current page + "limit": 20 // Records per page + } +} +``` + +### Error Response + +All error responses follow this structure: + +```json +{ + "success": false, + "error": { + "code": 400, + "message": "Validation failed", + "details": { + "field": "email", + "reason": "Email is required" + } + } +} +``` + +## HTTP Status Codes + +| Code | Meaning | Description | +|------|---------|-------------| +| 200 | OK | Request succeeded | +| 201 | Created | Resource created successfully | +| 400 | Bad Request | Invalid request syntax or validation error | +| 401 | Unauthorized | Authentication required | +| 403 | Forbidden | Insufficient permissions | +| 404 | Not Found | Resource not found | +| 409 | Conflict | Unique constraint violation | +| 422 | Unprocessable Entity | Business logic validation failed | +| 500 | Internal Server Error | Server error | + +## Standard Endpoints + +### 1. Query Records + +Retrieve multiple records with filtering, sorting, and pagination. + +**Endpoint:** +``` +POST /api/data/{object}/query +``` + +**Request Body:** +```json +{ + "filters": { + "status": "active" + }, + "fields": ["id", "name", "email"], + "sort": [ + { "field": "created_at", "order": "desc" } + ], + "limit": 20, + "skip": 0 +} +``` + +**Response:** +```json +{ + "success": true, + "data": [ + { + "id": "contact_123", + "name": "John Doe", + "email": "john@example.com" + } + ], + "meta": { + "count": 150, + "limit": 20, + "skip": 0 + } +} +``` + +**Example:** +```bash +curl -X POST http://localhost:3000/api/data/contacts/query \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "filters": { "status": "active" }, + "limit": 20 + }' +``` + +### 2. Get Single Record + +Retrieve a single record by ID. + +**Endpoint:** +``` +GET /api/data/{object}/{id} +``` + +**Query Parameters:** +- `fields`: Comma-separated list of fields to return +- `include`: Comma-separated list of related objects to include + +**Response:** +```json +{ + "success": true, + "data": { + "id": "contact_123", + "first_name": "John", + "last_name": "Doe", + "email": "john@example.com", + "created_at": "2024-01-15T10:30:00Z" + } +} +``` + +**Example:** +```bash +curl -X GET http://localhost:3000/api/data/contacts/contact_123 \ + -H "Authorization: Bearer " +``` + +### 3. Create Record + +Create a new record. + +**Endpoint:** +``` +POST /api/data/{object} +``` + +**Request Body:** +```json +{ + "first_name": "Jane", + "last_name": "Smith", + "email": "jane@example.com", + "phone": "+1234567890" +} +``` + +**Response (201 Created):** +```json +{ + "success": true, + "data": { + "id": "contact_456", + "first_name": "Jane", + "last_name": "Smith", + "email": "jane@example.com", + "phone": "+1234567890", + "created_at": "2024-01-15T11:00:00Z", + "updated_at": "2024-01-15T11:00:00Z" + } +} +``` + +**Example:** +```bash +curl -X POST http://localhost:3000/api/data/contacts \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "first_name": "Jane", + "last_name": "Smith", + "email": "jane@example.com" + }' +``` + +### 4. Update Record + +Update an existing record. + +**Endpoint:** +``` +PATCH /api/data/{object}/{id} +``` + +**Request Body:** +```json +{ + "phone": "+1987654321", + "status": "inactive" +} +``` + +**Response:** +```json +{ + "success": true, + "data": { + "id": "contact_123", + "first_name": "John", + "last_name": "Doe", + "email": "john@example.com", + "phone": "+1987654321", + "status": "inactive", + "updated_at": "2024-01-15T11:30:00Z" + } +} +``` + +**Example:** +```bash +curl -X PATCH http://localhost:3000/api/data/contacts/contact_123 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "phone": "+1987654321" + }' +``` + +### 5. Delete Record + +Delete a record. + +**Endpoint:** +``` +DELETE /api/data/{object}/{id} +``` + +**Response (200 OK):** +```json +{ + "success": true, + "data": { + "id": "contact_123", + "deleted": true + } +} +``` + +**Example:** +```bash +curl -X DELETE http://localhost:3000/api/data/contacts/contact_123 \ + -H "Authorization: Bearer " +``` + +### 6. Batch Operations + +Create, update, or delete multiple records in a single request. + +**Endpoint:** +``` +POST /api/data/{object}/batch +``` + +**Request Body:** +```json +{ + "operations": [ + { + "type": "create", + "data": { "name": "Record 1" } + }, + { + "type": "update", + "id": "contact_123", + "data": { "status": "active" } + }, + { + "type": "delete", + "id": "contact_456" + } + ] +} +``` + +**Response:** +```json +{ + "success": true, + "data": { + "created": 1, + "updated": 1, + "deleted": 1, + "results": [ + { "type": "create", "id": "contact_789", "success": true }, + { "type": "update", "id": "contact_123", "success": true }, + { "type": "delete", "id": "contact_456", "success": true } + ] + } +} +``` + +## Metadata Endpoints + +### Get Object Metadata + +Retrieve metadata definition for an object. + +**Endpoint:** +``` +GET /api/metadata/{object} +``` + +**Response:** +```json +{ + "success": true, + "data": { + "name": "contacts", + "label": "Contact", + "icon": "user", + "fields": { + "first_name": { + "type": "text", + "label": "First Name", + "required": true + }, + "email": { + "type": "email", + "label": "Email", + "unique": true + } + }, + "permission_set": { + "allowRead": true, + "allowCreate": ["sales", "admin"], + "allowEdit": ["sales", "admin"], + "allowDelete": ["admin"] + } + } +} +``` + +### List All Objects + +Get list of all available objects. + +**Endpoint:** +``` +GET /api/metadata +``` + +**Response:** +```json +{ + "success": true, + "data": [ + { + "name": "contacts", + "label": "Contact", + "icon": "user" + }, + { + "name": "accounts", + "label": "Account", + "icon": "building" + } + ] +} +``` + +## Advanced Features + +### Filtering + +See [Query Language Specification](./query-language.mdx) for complete filter syntax. + +```bash +curl -X POST http://localhost:3000/api/data/contacts/query \ + -H "Content-Type: application/json" \ + -d '{ + "filters": { + "$and": [ + { "status": "active" }, + { "age": { "$gte": 18 } } + ] + } + }' +``` + +### Including Related Records + +Include related object data in a single request: + +```bash +curl -X POST http://localhost:3000/api/data/opportunities/query \ + -H "Content-Type: application/json" \ + -d '{ + "include": ["account", "owner"], + "limit": 10 + }' +``` + +**Response includes nested objects:** +```json +{ + "data": [ + { + "id": "opp_123", + "name": "Big Deal", + "account": { + "id": "acc_456", + "name": "Acme Corp" + }, + "owner": { + "id": "user_789", + "name": "John Doe" + } + } + ] +} +``` + +### Pagination + +Use `limit` and `skip` for pagination: + +```bash +# Page 1 +curl -X POST http://localhost:3000/api/data/contacts/query \ + -d '{ "limit": 20, "skip": 0 }' + +# Page 2 +curl -X POST http://localhost:3000/api/data/contacts/query \ + -d '{ "limit": 20, "skip": 20 }' + +# Page 3 +curl -X POST http://localhost:3000/api/data/contacts/query \ + -d '{ "limit": 20, "skip": 40 }' +``` + +## Error Examples + +### 400 Bad Request - Validation Error + +```json +{ + "success": false, + "error": { + "code": 400, + "message": "Validation failed", + "details": { + "email": "Email is required" + } + } +} +``` + +### 401 Unauthorized + +```json +{ + "success": false, + "error": { + "code": 401, + "message": "Authentication required" + } +} +``` + +### 403 Forbidden + +```json +{ + "success": false, + "error": { + "code": 403, + "message": "Insufficient permissions", + "details": { + "action": "delete", + "object": "contacts" + } + } +} +``` + +### 404 Not Found + +```json +{ + "success": false, + "error": { + "code": 404, + "message": "Record not found", + "details": { + "object": "contacts", + "id": "contact_999" + } + } +} +``` + +### 409 Conflict - Unique Constraint + +```json +{ + "success": false, + "error": { + "code": 409, + "message": "Unique constraint violation", + "details": { + "field": "email", + "value": "john@example.com" + } + } +} +``` + +## Rate Limiting + +API requests are rate-limited per user: + +- **Free tier**: 100 requests per minute +- **Pro tier**: 1000 requests per minute + +Rate limit headers are included in responses: + +``` +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 95 +X-RateLimit-Reset: 1642341600 +``` + +## CORS + +Cross-Origin Resource Sharing (CORS) is enabled for the following origins: + +- `http://localhost:5173` (development) +- Configured production domains + +## WebSocket Protocol + +For real-time updates, ObjectOS supports WebSocket connections: + +**Endpoint:** +``` +ws://localhost:3000/ws +``` + +**Message Format:** +```json +{ + "type": "subscribe", + "object": "contacts", + "filters": { "status": "active" } +} +``` + +> **Note**: WebSocket real-time functionality is planned for a future release. + +## API Versioning + +Current API version: `v1` + +Version is included in the URL path: +``` +/api/v1/data/{object} +``` + +Breaking changes will introduce new versions (`v2`, `v3`, etc.). \ No newline at end of file diff --git a/apps/site/content/docs/spec/index.mdx b/apps/site/content/docs/spec/index.mdx new file mode 100644 index 0000000..ea1c27a --- /dev/null +++ b/apps/site/content/docs/spec/index.mdx @@ -0,0 +1,74 @@ +--- +title: Protocol Specifications +--- + +# Protocol Specifications + +This section provides comprehensive technical specifications for ObjectOS protocols, file formats, and APIs. These specifications are the authoritative reference for developers building with or extending ObjectOS. + +## Overview + +ObjectOS implements three core protocols: + +1. **Metadata Format** - YAML schema for defining business objects +2. **Query Language** - Powerful filtering and querying syntax +3. **HTTP Protocol** - RESTful API endpoints and conventions + +## Specifications + +### [Metadata Format](./metadata-format.mdx) + +Complete specification of the YAML schema used to define objects, fields, relationships, and permissions. Includes: + +- Object schema structure +- All field types (text, number, date, lookup, etc.) +- Field attributes and validation rules +- Permission configuration +- Relationship definitions + +**Use this when:** Defining new objects or understanding how metadata is structured. + +### [Query Language](./query-language.mdx) + +Comprehensive guide to the ObjectOS Query Language, including filtering, sorting, and pagination. Includes: + +- Filter operators (comparison, string, array, null, range) +- Logical operators (AND, OR, NOT) +- Complex query composition +- Field selection and sorting +- Pagination and performance optimization + +**Use this when:** Querying data programmatically or building custom queries. + +### [HTTP Protocol](./http-protocol.mdx) + +Complete HTTP API reference including all REST endpoints, authentication, and response formats. Includes: + +- Authentication and authorization +- Standard CRUD endpoints +- Batch operations +- Metadata endpoints +- Error handling and status codes +- Rate limiting and CORS + +**Use this when:** Integrating with ObjectOS via HTTP API or building client applications. + +## Quick Links + +- **[Getting Started Guide](../guide/index.mdx)** - New to ObjectOS? Start here +- **[Architecture Guide](../guide/architecture.mdx)** - Understand the system design +- **[SDK Reference](../guide/sdk-reference.mdx)** - Programmatic API reference + +## Contributing to Specifications + +These specifications are living documents. If you find errors or omissions: + +1. Open an issue on [GitHub](https://github.com/objectql/objectos/issues) +2. Submit a pull request with corrections +3. Discuss in our community forum (coming soon) + +## Version + +Current specification version: **v0.2.0** + +Last updated: January 2026 \ No newline at end of file diff --git a/apps/site/content/docs/spec/meta.json b/apps/site/content/docs/spec/meta.json new file mode 100644 index 0000000..b45d3f9 --- /dev/null +++ b/apps/site/content/docs/spec/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Specifications", + "pages": [ + "index", + "metadata-format", + "query-language", + "http-protocol" + ] +} diff --git a/apps/site/content/docs/spec/metadata-format.mdx b/apps/site/content/docs/spec/metadata-format.mdx new file mode 100644 index 0000000..574bd6f --- /dev/null +++ b/apps/site/content/docs/spec/metadata-format.mdx @@ -0,0 +1,407 @@ +--- +title: Metadata Format Specification +--- + +# Metadata Format Specification + +ObjectOS uses strict YAML schemas for defining application metadata. This document specifies the complete format for object definitions. + +## Object Schema + +### Basic Structure + +```yaml +name: # Required: Internal API name (snake_case) +label: # Optional: Human-readable display name +description: # Optional: Object description +icon: # Optional: Icon name (from icon library) +enable_api: # Optional: Enable API endpoints (default: true) +enable_audit: # Optional: Enable audit logging (default: false) + +fields: + : + type: # Required: Field data type + label: # Optional: Display label + description: # Optional: Help text + required: # Optional: Is field required (default: false) + unique: # Optional: Must be unique (default: false) + default: # Optional: Default value + +permission_set: + allowRead: # Who can read records + allowCreate: # Who can create records + allowEdit: # Who can edit records + allowDelete: # Who can delete records +``` + +### Example: Complete Object Definition + +```yaml +name: contacts +label: Contact +description: Customer and prospect contact information +icon: user +enable_api: true +enable_audit: true + +fields: + first_name: + type: text + label: First Name + required: true + max_length: 100 + + last_name: + type: text + label: Last Name + required: true + max_length: 100 + + email: + type: email + label: Email Address + unique: true + required: true + + phone: + type: text + label: Phone Number + pattern: '^\+?[1-9]\d{1,14}$' + + birthdate: + type: date + label: Birth Date + + account: + type: lookup + label: Account + reference_to: accounts + on_delete: set_null + + is_active: + type: boolean + label: Active + default: true + + status: + type: select + label: Status + options: + - value: active + label: Active + - value: inactive + label: Inactive + - value: pending + label: Pending + default: pending + +permission_set: + allowRead: true # Everyone can read + allowCreate: ['sales', 'admin'] # Sales and admin can create + allowEdit: ['sales', 'admin'] # Sales and admin can edit + allowDelete: ['admin'] # Only admin can delete +``` + +## Field Types Reference + +### Text Fields + +#### `text` +Short text field (up to 255 characters). + +```yaml +field_name: + type: text + label: Title + max_length: 100 # Optional: Maximum length + min_length: 3 # Optional: Minimum length + pattern: '^[A-Za-z]+$' # Optional: Regex pattern +``` + +#### `textarea` +Long text field (unlimited length). + +```yaml +description: + type: textarea + label: Description + rows: 5 # Optional: Display rows +``` + +#### `email` +Email address field with validation. + +```yaml +email: + type: email + label: Email Address + unique: true +``` + +#### `url` +URL field with validation. + +```yaml +website: + type: url + label: Website +``` + +#### `phone` +Phone number field. + +```yaml +phone: + type: text + label: Phone + pattern: '^\+?[1-9]\d{1,14}$' +``` + +### Numeric Fields + +#### `number` +Integer or decimal number. + +```yaml +quantity: + type: number + label: Quantity + min: 0 # Optional: Minimum value + max: 1000 # Optional: Maximum value + precision: 2 # Optional: Decimal places +``` + +#### `currency` +Monetary value. + +```yaml +price: + type: currency + label: Price + currency_code: USD # Optional: Currency code + precision: 2 # Decimal places +``` + +#### `percent` +Percentage value (0-100). + +```yaml +discount: + type: percent + label: Discount Rate + min: 0 + max: 100 +``` + +### Date and Time Fields + +#### `date` +Date only (no time). + +```yaml +birth_date: + type: date + label: Birth Date + min: '1900-01-01' # Optional: Minimum date + max: '2100-12-31' # Optional: Maximum date +``` + +#### `datetime` +Date and time. + +```yaml +created_at: + type: datetime + label: Created At + default: now # Special value: current timestamp +``` + +#### `time` +Time only (no date). + +```yaml +start_time: + type: time + label: Start Time +``` + +### Boolean and Selection Fields + +#### `boolean` +True/false checkbox. + +```yaml +is_active: + type: boolean + label: Active + default: true +``` + +#### `select` +Dropdown selection (single value). + +```yaml +status: + type: select + label: Status + options: + - value: draft + label: Draft + - value: published + label: Published + default: draft +``` + +#### `multiselect` +Multiple selection. + +```yaml +tags: + type: multiselect + label: Tags + options: + - value: important + label: Important + - value: urgent + label: Urgent +``` + +### Relationship Fields + +#### `lookup` +Many-to-one relationship (foreign key). + +```yaml +account: + type: lookup + label: Account + reference_to: accounts + on_delete: set_null # Options: set_null, cascade, restrict +``` + +#### `master_detail` +Master-detail relationship (cascade delete). + +```yaml +account: + type: master_detail + label: Account + reference_to: accounts + on_delete: cascade # Automatically cascades +``` + +### Special Fields + +#### `autonumber` +Auto-incrementing number. + +```yaml +ticket_number: + type: autonumber + label: Ticket # + format: 'TKT-{0000}' # Format template + start_number: 1 # Starting number +``` + +#### `formula` +Calculated field. + +```yaml +full_name: + type: formula + label: Full Name + formula: 'first_name + " " + last_name' + return_type: text +``` + +#### `rollup_summary` +Aggregation from related records. + +```yaml +total_opportunities: + type: rollup_summary + label: Total Opportunities + reference_to: opportunities + summary_type: count # Options: count, sum, min, max, avg +``` + +## Field Attributes + +### Common Attributes + +All fields support these attributes: + +| Attribute | Type | Description | +|-----------|------|-------------| +| `type` | string | **Required**. Field data type | +| `label` | string | Display label for UI | +| `description` | string | Help text shown in UI | +| `required` | boolean | Field is required (default: false) | +| `unique` | boolean | Value must be unique (default: false) | +| `default` | any | Default value for new records | +| `readonly` | boolean | Field cannot be edited (default: false) | +| `hidden` | boolean | Hide from UI (default: false) | + +### Validation Attributes + +| Attribute | Applies To | Description | +|-----------|------------|-------------| +| `min` | number, currency, percent, date | Minimum value | +| `max` | number, currency, percent, date | Maximum value | +| `min_length` | text, textarea | Minimum character length | +| `max_length` | text, textarea, email, url | Maximum character length | +| `pattern` | text, email, url, phone | Regex validation pattern | +| `precision` | number, currency, percent | Decimal places | + +### Relationship Attributes + +| Attribute | Applies To | Description | +|-----------|------------|-------------| +| `reference_to` | lookup, master_detail | Target object name | +| `on_delete` | lookup, master_detail | Cascade behavior | +| `related_list_label` | lookup, master_detail | Label in related list | + +## Permission Schema + +Permissions control who can access and modify records. + +### Object-Level Permissions + +```yaml +permission_set: + allowRead: > + allowCreate: > + allowEdit: > + allowDelete: > +``` + +- `true`: Everyone has permission +- `false`: No one has permission +- `['role1', 'role2']`: Only specified roles have permission + +### Field-Level Permissions + +```yaml +fields: + salary: + type: currency + label: Salary + visible_to: ['hr', 'admin'] # Only HR and admin can see + editable_by: ['hr'] # Only HR can edit +``` + +## Validation Rules + +### Built-in Validation + +ObjectOS automatically validates: +- Required fields +- Unique fields +- Data type correctness +- Min/max constraints +- Pattern matching +- Reference integrity + +### Custom Validation + +Define custom validation in hooks (see [Logic Hooks](../guide/logic-hooks.mdx)). \ No newline at end of file diff --git a/apps/site/content/docs/spec/query-language.mdx b/apps/site/content/docs/spec/query-language.mdx new file mode 100644 index 0000000..74ea8f1 --- /dev/null +++ b/apps/site/content/docs/spec/query-language.mdx @@ -0,0 +1,419 @@ +--- +title: Query Language Specification +--- + +# Query Language Specification + +The ObjectOS Query Language provides a powerful and flexible syntax for filtering, sorting, and querying records. This document specifies the complete query syntax. + +## Query Structure + +A complete query consists of: + +```typescript +{ + filters?: FilterExpression; // Filter criteria + fields?: string[]; // Fields to return + sort?: SortExpression[]; // Sort order + limit?: number; // Max records to return + skip?: number; // Records to skip (pagination) + include?: string[]; // Related objects to include +} +``` + +## Filter Expressions + +### Basic Filters + +Filters use an object-based syntax: + +```json +{ + "field_name": "value" +} +``` + +This is equivalent to: `field_name = 'value'` + +### Filter Operators + +Filters support advanced operators using the `$operator` syntax: + +```json +{ + "age": { "$gt": 18 } +} +``` + +#### Comparison Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$eq` or implicit | Equals | `{ "status": "active" }` | +| `$ne` | Not equals | `{ "status": { "$ne": "inactive" } }` | +| `$gt` | Greater than | `{ "age": { "$gt": 18 } }` | +| `$gte` | Greater than or equal | `{ "age": { "$gte": 18 } }` | +| `$lt` | Less than | `{ "price": { "$lt": 100 } }` | +| `$lte` | Less than or equal | `{ "price": { "$lte": 100 } }` | + +#### String Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$like` | Pattern matching (SQL LIKE) | `{ "name": { "$like": "%John%" } }` | +| `$ilike` | Case-insensitive LIKE | `{ "email": { "$ilike": "%@example.com" } }` | +| `$startswith` | Starts with | `{ "name": { "$startswith": "Mr" } }` | +| `$endswith` | Ends with | `{ "email": { "$endswith": "@example.com" } }` | +| `$contains` | Contains substring | `{ "description": { "$contains": "important" } }` | + +#### Array Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$in` | Value in array | `{ "status": { "$in": ["active", "pending"] } }` | +| `$nin` | Value not in array | `{ "status": { "$nin": ["deleted", "archived"] } }` | + +#### Null Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$null` | Is null | `{ "deleted_at": { "$null": true } }` | +| `$notnull` | Is not null | `{ "email": { "$notnull": true } }` | + +#### Range Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$between` | Between two values | `{ "created_at": { "$between": ["2024-01-01", "2024-12-31"] } }` | + +### Logical Operators + +#### AND (default) + +Multiple conditions at the same level are implicitly AND'd: + +```json +{ + "status": "active", + "age": { "$gt": 18 } +} +``` + +Equivalent to: `status = 'active' AND age > 18` + +#### OR + +Use `$or` for OR conditions: + +```json +{ + "$or": [ + { "status": "active" }, + { "status": "pending" } + ] +} +``` + +Equivalent to: `status = 'active' OR status = 'pending'` + +#### NOT + +Use `$not` to negate a condition: + +```json +{ + "$not": { + "status": "deleted" + } +} +``` + +Equivalent to: `NOT (status = 'deleted')` + +#### Complex Logic + +Combine logical operators for complex queries: + +```json +{ + "$and": [ + { "age": { "$gte": 18 } }, + { + "$or": [ + { "country": "US" }, + { "country": "CA" } + ] + } + ] +} +``` + +Equivalent to: `age >= 18 AND (country = 'US' OR country = 'CA')` + +## Field Selection + +Specify which fields to return: + +```json +{ + "fields": ["first_name", "last_name", "email"] +} +``` + +- If omitted, all fields are returned +- Use `["*"]` to explicitly request all fields +- Related fields can be included (see [Include Related Records](#include-related-records)) + +## Sorting + +Sort results by one or more fields: + +```json +{ + "sort": [ + { "field": "last_name", "order": "asc" }, + { "field": "first_name", "order": "asc" } + ] +} +``` + +- `order`: `"asc"` (ascending) or `"desc"` (descending) +- Multiple sort criteria are applied in order +- Shorthand: `{ "sort": "last_name" }` defaults to ascending + +## Pagination + +Control which records are returned: + +```json +{ + "limit": 20, + "skip": 40 +} +``` + +- `limit`: Maximum number of records to return (default: 100, max: 1000) +- `skip`: Number of records to skip (for pagination) +- Page 1: `{ "limit": 20, "skip": 0 }` +- Page 2: `{ "limit": 20, "skip": 20 }` +- Page 3: `{ "limit": 20, "skip": 40 }` + +## Include Related Records + +Load related records in a single query: + +```json +{ + "include": ["account", "created_by"] +} +``` + +This performs a JOIN and includes related object data in the response. + +## Complete Query Examples + +### Example 1: Simple Query + +Find all active contacts: + +```json +{ + "filters": { + "status": "active" + }, + "fields": ["first_name", "last_name", "email"], + "sort": "last_name", + "limit": 50 +} +``` + +### Example 2: Complex Filters + +Find contacts from US or Canada, age 18+, created this year: + +```json +{ + "filters": { + "$and": [ + { + "$or": [ + { "country": "US" }, + { "country": "CA" } + ] + }, + { "age": { "$gte": 18 } }, + { + "created_at": { + "$between": ["2024-01-01", "2024-12-31"] + } + } + ] + }, + "sort": [ + { "field": "created_at", "order": "desc" } + ] +} +``` + +### Example 3: Search Query + +Search for contacts with "john" in name or email: + +```json +{ + "filters": { + "$or": [ + { "first_name": { "$ilike": "%john%" } }, + { "last_name": { "$ilike": "%john%" } }, + { "email": { "$ilike": "%john%" } } + ] + } +} +``` + +### Example 4: Related Records + +Get opportunities with account and owner details: + +```json +{ + "filters": { + "stage": { "$in": ["proposal", "negotiation"] } + }, + "include": ["account", "owner"], + "sort": [ + { "field": "amount", "order": "desc" } + ], + "limit": 10 +} +``` + +## Query Optimization Tips + +### 1. Use Field Selection + +Only request fields you need to reduce payload size: + +```json +// ❌ Bad: Returns all fields +{} + +// ✅ Good: Returns only needed fields +{ "fields": ["id", "name", "email"] } +``` + +### 2. Add Indexes + +For frequently queried fields, ensure database indexes exist. + +### 3. Limit Results + +Always use `limit` to avoid loading too many records: + +```json +{ + "limit": 100 // Good practice +} +``` + +### 4. Use Specific Filters + +More specific filters are faster: + +```json +// ❌ Slower +{ "name": { "$like": "%john%" } } + +// ✅ Faster +{ "email": "john@example.com" } +``` + +## Programmatic Usage + +### Using the Kernel SDK + +```typescript +import { ObjectOS } from '@objectos/kernel'; + +const kernel = new ObjectOS(); + +// Simple query +const contacts = await kernel.find('contacts', { + filters: { status: 'active' }, + sort: 'last_name', + limit: 50 +}); + +// Complex query +const results = await kernel.find('opportunities', { + filters: { + $and: [ + { stage: { $in: ['proposal', 'negotiation'] } }, + { amount: { $gte: 10000 } } + ] + }, + include: ['account', 'owner'], + sort: [ + { field: 'amount', order: 'desc' } + ], + limit: 10 +}); +``` + +### Using the HTTP API + +```bash +curl -X POST http://localhost:3000/api/data/contacts/query \ + -H "Content-Type: application/json" \ + -d '{ + "filters": { + "status": "active", + "age": { "$gte": 18 } + }, + "fields": ["first_name", "last_name", "email"], + "sort": "last_name", + "limit": 50 + }' +``` + +## Database-Specific Considerations + +### PostgreSQL + +- Case-insensitive operations use `ILIKE` +- Date comparisons use ISO 8601 format +- Supports full-text search (future) + +### MongoDB + +- Operators map directly to MongoDB query syntax +- `$text` search available for indexed fields +- Aggregation pipeline for complex queries + +### SQLite + +- Limited date/time functions +- Case-insensitive operations may be slower +- No full-text search in basic mode + +## Error Handling + +Invalid queries return structured errors: + +```json +{ + "error": { + "code": 400, + "message": "Invalid filter: unknown operator '$invalid'", + "details": { + "field": "age", + "operator": "$invalid" + } + } +} +``` + +Common errors: +- `400`: Invalid query syntax +- `404`: Object not found +- `422`: Invalid field name or operator \ No newline at end of file diff --git a/apps/site/lib/page-tree.ts b/apps/site/lib/page-tree.ts index 8df4883..8cc633d 100644 --- a/apps/site/lib/page-tree.ts +++ b/apps/site/lib/page-tree.ts @@ -16,5 +16,40 @@ export const pageTree: Root = { { type: 'page', name: 'Installation', url: '/docs/getting-started/installation' }, ], }, + { + type: 'folder', + name: 'Guide', + index: { + type: 'page', + name: 'Overview', + url: '/docs/guide', + }, + children: [ + { type: 'page', name: 'Architecture', url: '/docs/guide/architecture' }, + { type: 'page', name: 'Platform Components', url: '/docs/guide/platform-components' }, + { type: 'page', name: 'Data Modeling', url: '/docs/guide/data-modeling' }, + { type: 'page', name: 'Security Guide', url: '/docs/guide/security-guide' }, + { type: 'page', name: 'SDK Reference', url: '/docs/guide/sdk-reference' }, + { type: 'page', name: 'Logic Hooks', url: '/docs/guide/logic-hooks' }, + { type: 'page', name: 'Logic Actions', url: '/docs/guide/logic-actions' }, + { type: 'page', name: 'UI Framework', url: '/docs/guide/ui-framework' }, + { type: 'page', name: 'Development Plan', url: '/docs/guide/development-plan' }, + { type: 'page', name: 'Contributing', url: '/docs/guide/contributing-development' }, + ], + }, + { + type: 'folder', + name: 'Specifications', + index: { + type: 'page', + name: 'Overview', + url: '/docs/spec', + }, + children: [ + { type: 'page', name: 'Metadata Format', url: '/docs/spec/metadata-format' }, + { type: 'page', name: 'Query Language', url: '/docs/spec/query-language' }, + { type: 'page', name: 'HTTP Protocol', url: '/docs/spec/http-protocol' }, + ], + }, ], };