diff --git a/examples/basic/README.md b/examples/basic/README.md new file mode 100644 index 000000000..84bc86783 --- /dev/null +++ b/examples/basic/README.md @@ -0,0 +1,174 @@ +# Basic Protocol Examples + +A standalone package containing comprehensive examples demonstrating core ObjectStack protocols and features. + +## ๐Ÿ“ฆ Package Information + +- **Package**: `@objectstack/example-basic` +- **Type**: Demonstration/Reference +- **Status**: Standalone examples with type checking + +## ๐Ÿš€ Usage + +### Build and Type Check + +```bash +# From monorepo root +pnpm install + +# Build the spec package first +pnpm --filter @objectstack/spec build + +# Type check the examples +pnpm --filter @objectstack/example-basic typecheck + +# Or run directly with tsx +npx tsx examples/basic/stack-definition-example.ts +``` + +### Running Examples + +Each example file can be run independently with `tsx`: + +```bash +# Run a specific example +npx tsx examples/basic/ai-rag-example.ts + +# Or uncomment the demonstration function calls at the end of each file +``` + +## ๐Ÿ“š Examples + +### Stack Definition +**File:** [`stack-definition-example.ts`](./stack-definition-example.ts) + +Demonstrates the `defineStack()` helper for creating comprehensive ObjectStack configurations: +- Minimal stack setup +- Task management application with Objects, UI, and Workflows +- CRM with AI agent integration +- Type-safe configuration patterns + +**Key Concepts:** +- Manifest configuration +- Object and Field definitions +- UI components (Apps, Views, Dashboards) +- Automation (Workflows) +- Security (Roles, Permissions) +- AI Agents + +### Capabilities Configuration +**File:** [`capabilities-example.ts`](./capabilities-example.ts) + +Shows how to define runtime capabilities for ObjectStack instances: +- Production environment with full features +- Development environment with minimal features +- AI-focused environment optimized for RAG +- Capability checking helpers + +**Key Concepts:** +- ObjectQL (Data) capabilities +- ObjectUI (UI) capabilities +- ObjectOS (System) capabilities +- Environment-specific configurations + +### API Discovery +**File:** [`api-discovery-example.ts`](./api-discovery-example.ts) + +Demonstrates the API Discovery protocol for runtime introspection: +- Complete discovery response +- Development mode discovery +- Adaptive client behavior +- AI agent system prompt generation + +**Key Concepts:** +- System identity and versioning +- Available API endpoints (REST, GraphQL, OData, WebSocket) +- Runtime capabilities +- Authentication configuration +- Feature flags + +### AI & RAG Pipeline +**File:** [`ai-rag-example.ts`](./ai-rag-example.ts) + +Shows Retrieval-Augmented Generation (RAG) pipeline configuration: +- Document ingestion and chunking +- Vector embeddings and storage +- Semantic search and retrieval +- Context assembly for LLMs +- AI agent with RAG integration + +**Key Concepts:** +- RAG pipeline configuration +- Document processing +- Vector database integration +- Hybrid search (vector + keyword) +- Reranking for better results +- Context template formatting + +### Auth & Permissions +**File:** [`auth-permission-example.ts`](./auth-permission-example.ts) + +Demonstrates authentication and authorization systems: +- User identity and sessions +- Role-based access control (RBAC) with hierarchy +- Object and field-level permissions +- Row-level security (RLS) +- Sharing rules for data access +- Territory management + +**Key Concepts:** +- User authentication and profiles +- Role hierarchy and inheritance +- Permission sets +- Granular access control +- Dynamic data filtering +- Territory-based assignments + +### Automation & Workflows +**File:** [`automation-example.ts`](./automation-example.ts) + +Shows automation capabilities in ObjectStack: +- Workflow rules for field updates +- Email alerts and notifications +- Automatic record creation +- Multi-step approval processes +- Screen flows with user interaction +- ETL processes for data integration + +**Key Concepts:** +- Event-driven automation +- Scheduled workflows (cron) +- Approval hierarchies +- Visual process automation +- Data transformation pipelines +- Error handling and notifications + +## ๐ŸŽฏ Usage + +These examples are TypeScript files in a proper package that can be: + +1. **Type checked:** + ```bash + pnpm --filter @objectstack/example-basic typecheck + ``` + +2. **Run directly:** + ```bash + npx tsx examples/basic/stack-definition-example.ts + ``` + +3. **Used as references:** + Import types and patterns in your own projects + +## ๐Ÿ”— Related Resources + +- **[CRM Example](../crm/)** - Full application using these patterns +- **[Todo Example](../todo/)** - Simple application example +- **[Protocol Reference](../../packages/spec/)** - Complete schema documentation + +## ๐Ÿ“ Notes + +- All examples use TypeScript for type safety +- Examples import types from `@objectstack/spec` +- Each example is self-contained and documented +- Examples demonstrate real-world patterns, not toy scenarios diff --git a/examples/basic/ai-rag-example.ts b/examples/basic/ai-rag-example.ts new file mode 100644 index 000000000..3a36c18ed --- /dev/null +++ b/examples/basic/ai-rag-example.ts @@ -0,0 +1,354 @@ +/** + * Example: AI Protocol - RAG Pipeline + * + * This example demonstrates the Retrieval-Augmented Generation (RAG) pipeline in ObjectStack. + * RAG combines: + * - Vector database for semantic search + * - Document chunking and embedding + * - Context retrieval for LLM prompts + * - AI-powered question answering + */ + +import type { AI } from '@objectstack/spec'; + +/** + * Example 1: RAG Pipeline Configuration + * + * Complete RAG pipeline setup for a knowledge base system + */ +export const knowledgeBaseRAG: AI.RAGPipelineConfig = { + name: 'knowledge_base_rag', + label: 'Knowledge Base RAG Pipeline', + description: 'RAG pipeline for company knowledge base', + + // Embedding Configuration + embedding: { + provider: 'openai', + model: 'text-embedding-ada-002', + dimensions: 1536, + batchSize: 100, + }, + + // Vector Store + vectorStore: { + provider: 'pinecone', + indexName: 'knowledge-base', + namespace: 'production', + dimensions: 1536, + metric: 'cosine', + batchSize: 100, + connectionPoolSize: 10, + timeout: 30000, + }, + + // Chunking strategy + chunking: { + type: 'recursive', + chunkSize: 1000, + chunkOverlap: 200, + separators: ['\n\n', '\n', ' ', ''], + }, + + // Retrieval Configuration + retrieval: { + type: 'hybrid', + topK: 5, + vectorWeight: 0.7, + keywordWeight: 0.3, + }, + + // Reranking for better results + reranking: { + enabled: true, + model: 'cohere-rerank-v3', + provider: 'cohere', + topK: 3, + }, + + // Context Management + maxContextTokens: 3000, + + // Metadata filtering + metadataFilters: { + status: 'published', + }, + + // Cache Configuration + enableCache: true, + cacheTTL: 3600, +}; + +/** + * Example 2: Document Ingestion + * + * How to index documents into the RAG pipeline + */ +export const sampleDocumentMetadata: AI.DocumentMetadata[] = [ + { + source: 'https://docs.objectstack.dev/architecture', + sourceType: 'url', + title: 'ObjectStack Architecture', + author: 'Technical Team', + createdAt: '2024-01-15T00:00:00Z', + category: 'Architecture', + }, + { + source: 'https://docs.objectstack.dev/guides/objects', + sourceType: 'url', + title: 'Creating Objects Guide', + author: 'DevRel Team', + createdAt: '2024-01-20T00:00:00Z', + category: 'Tutorial', + }, + { + source: 'https://docs.objectstack.dev/ai/bridge', + sourceType: 'url', + title: 'AI Bridge Documentation', + author: 'AI Team', + createdAt: '2024-02-01T00:00:00Z', + category: 'AI Features', + }, +]; + +export const sampleDocumentChunks: AI.DocumentChunk[] = [ + { + id: 'chunk_001', + content: `ObjectStack is a metadata-driven low-code platform that enables rapid application development. +It uses a three-layer architecture: ObjectQL for data, ObjectUI for presentation, and ObjectOS for runtime. +The platform supports multiple databases and provides built-in AI capabilities.`, + metadata: sampleDocumentMetadata[0], + chunkIndex: 0, + tokens: 45, + }, + { + id: 'chunk_002', + content: `To create a new object in ObjectStack, use the Object Schema definition. +Define fields with types like text, number, lookup, and more. +Enable features like API access, history tracking, and workflows.`, + metadata: sampleDocumentMetadata[1], + chunkIndex: 0, + tokens: 38, + }, + { + id: 'chunk_003', + content: `ObjectStack's AI Bridge allows integration with LLMs like GPT-4, Claude, and Gemini. +It provides model abstraction, cost tracking, and automatic retries. +Use the Agent protocol to define AI assistants with specific capabilities.`, + metadata: sampleDocumentMetadata[2], + chunkIndex: 0, + tokens: 42, + }, +]; + +/** + * Example 3: RAG Query + * + * Performing a RAG query with filters and options + */ +export const sampleQueries: AI.RAGQueryRequest[] = [ + { + // Simple question + query: 'What is ObjectStack?', + pipelineName: 'knowledge_base_rag', + topK: 5, + includeMetadata: true, + includeSources: true, + }, + { + // Question with metadata filtering + query: 'How do I create objects?', + pipelineName: 'knowledge_base_rag', + topK: 3, + metadataFilters: { + category: 'Tutorial', + }, + includeMetadata: true, + includeSources: true, + }, + { + // Advanced query + query: 'Tell me about AI integration', + pipelineName: 'knowledge_base_rag', + topK: 10, + metadataFilters: { + category: 'AI Features', + }, + includeMetadata: true, + includeSources: true, + }, +]; + +/** + * Example 4: RAG Results + * + * What the pipeline returns + */ +export const sampleResults: AI.RAGQueryResponse = { + query: 'What is ObjectStack?', + + // Retrieved chunks + results: [ + { + content: `ObjectStack is a metadata-driven low-code platform that enables rapid application development. +It uses a three-layer architecture: ObjectQL for data, ObjectUI for presentation, and ObjectOS for runtime.`, + score: 0.92, + metadata: { + source: 'https://docs.objectstack.dev/architecture', + sourceType: 'url', + category: 'Architecture', + }, + }, + { + content: `The platform supports multiple databases and provides built-in AI capabilities.`, + score: 0.85, + metadata: { + source: 'https://docs.objectstack.dev/architecture', + sourceType: 'url', + category: 'Architecture', + }, + }, + ], + + // Context assembled for LLM + context: `Context from knowledge base: + +[Source: https://docs.objectstack.dev/architecture] +ObjectStack is a metadata-driven low-code platform that enables rapid application development. +It uses a three-layer architecture: ObjectQL for data, ObjectUI for presentation, and ObjectOS for runtime. + +[Source: https://docs.objectstack.dev/architecture] +The platform supports multiple databases and provides built-in AI capabilities.`, +}; + +/** + * Example 5: AI Agent with RAG + * + * Integrating RAG into an AI agent + */ +export const ragEnabledAgent: AI.Agent = { + name: 'documentation_assistant', + label: 'Documentation Assistant', + role: 'Documentation Support Specialist', + active: true, + + // System prompt with RAG instructions + instructions: `You are a helpful documentation assistant for ObjectStack. + +You have access to the knowledge base through RAG (Retrieval-Augmented Generation). +When answering questions: +1. Use the retrieved context to provide accurate information +2. Cite sources when possible +3. If the context doesn't contain the answer, say so honestly +4. Be concise but comprehensive + +Always format your responses in a clear, structured way.`, + + // RAG Configuration + knowledge: { + indexes: ['knowledge-base'], + topics: ['ObjectStack', 'documentation'], + }, + + // Model configuration + model: { + provider: 'openai', + model: 'gpt-4-turbo', + temperature: 0.7, + maxTokens: 2000, + }, + + // Tools (optional - for function calling) + tools: [ + { + type: 'query', + name: 'search_documentation', + description: 'Search the ObjectStack documentation for specific topics', + }, + ], +}; + +/** + * Example 6: Using RAG in Practice + * + * Simulated conversation flow + */ +export function demonstrateRAGUsage() { + console.log('=== RAG Pipeline Demo ===\n'); + + // Step 1: User asks a question + const userQuestion = 'What is ObjectStack and what are its main components?'; + console.log(`User: ${userQuestion}\n`); + + // Step 2: RAG retrieves relevant chunks + console.log('RAG Pipeline:'); + console.log('- Embedding query...'); + console.log('- Searching vector database...'); + console.log(`- Retrieved ${sampleResults.results.length} relevant chunks`); + console.log('- Processing complete\n'); + + // Step 3: Context is assembled + console.log('Assembled Context:'); + console.log(sampleResults.context); + console.log(''); + + // Step 4: LLM generates response + const assistantResponse = `Based on the documentation, ObjectStack is a metadata-driven low-code platform designed for rapid application development. + +It has three main architectural layers: +1. **ObjectQL**: The data layer that handles business logic and data operations +2. **ObjectUI**: The presentation layer for user interfaces +3. **ObjectOS**: The runtime layer that manages system operations + +The platform supports multiple databases and includes built-in AI capabilities, making it suitable for creating intelligent applications. + +*Source: https://docs.objectstack.dev/architecture*`; + + console.log('Assistant:'); + console.log(assistantResponse); +} + +/** + * Example 7: Advanced RAG Patterns + */ +export const advancedRAGPatterns = { + // Multi-query RAG: Generate multiple search queries for better retrieval + multiQuery: { + originalQuery: 'How do I use AI in ObjectStack?', + generatedQueries: [ + 'ObjectStack AI integration guide', + 'AI agent configuration in ObjectStack', + 'Using LLMs with ObjectStack', + 'ObjectStack AI Bridge tutorial', + ], + }, + + // Parent Document Retrieval: Retrieve small chunks but return larger parent + parentDocRetrieval: { + retrievedChunk: 'Small focused chunk about AI', + returnedContext: 'Full section containing the chunk with more context', + }, + + // Contextual Compression: Remove irrelevant parts of retrieved chunks + contextualCompression: { + original: 'Long chunk with lots of text, some relevant, some not...', + compressed: 'Only the relevant parts extracted by LLM...', + }, +}; + +// ============================================================================ +// Usage +// ============================================================================ + +// Demonstrate RAG usage (uncomment to run) +// demonstrateRAGUsage(); + +// Export all examples +export default { + knowledgeBaseRAG, + sampleDocumentMetadata, + sampleDocumentChunks, + sampleQueries, + sampleResults, + ragEnabledAgent, + advancedRAGPatterns, +}; diff --git a/examples/basic/api-discovery-example.ts b/examples/basic/api-discovery-example.ts new file mode 100644 index 000000000..9e5f8b88f --- /dev/null +++ b/examples/basic/api-discovery-example.ts @@ -0,0 +1,176 @@ +/** + * Example: API Discovery Protocol + * + * This example demonstrates the API Discovery mechanism in ObjectStack. + * The Discovery API allows clients (frontends, AI agents, tools) to: + * - Discover available capabilities + * - Learn about the system's features and limits + * - Adapt behavior based on runtime capabilities + * + * Typically exposed at: GET /api/discovery + */ + +import type { API } from '@objectstack/spec'; + +/** + * Example 1: Complete Discovery Response + * + * This is what a client receives when calling /api/discovery + */ +export const fullDiscoveryResponse: API.DiscoveryResponse = { + // System Identity + name: 'ObjectStack CRM', + version: '2.1.0', + environment: 'production', + + // Dynamic Routing - tells frontend where to send requests + routes: { + data: '/api/data', + metadata: '/api/meta', + auth: '/api/auth', + automation: '/api/automation', + storage: '/api/storage', + graphql: '/graphql', + }, + + // Feature Flags - what capabilities are enabled + features: { + graphql: true, + search: true, + websockets: true, + files: true, + }, + + // Localization Info + locale: { + default: 'en-US', + supported: ['en-US', 'zh-CN', 'es-ES', 'fr-FR'], + timezone: 'UTC', + }, +}; + +/** + * Example 2: Minimal Discovery Response (Development Mode) + * + * A simplified response for local development + */ +export const devDiscoveryResponse: API.DiscoveryResponse = { + name: 'ObjectStack Dev', + version: '0.1.0', + environment: 'development', + + routes: { + data: '/api/data', + metadata: '/api/meta', + auth: '/api/auth', + }, + + features: { + graphql: false, + search: true, + websockets: false, + files: true, + }, + + locale: { + default: 'en-US', + supported: ['en-US'], + timezone: 'America/New_York', + }, +}; + +/** + * Example 3: Client Usage - Adaptive Behavior + * + * How a client can use the discovery API to adapt its behavior + */ +export class AdaptiveClient { + private discovery: API.DiscoveryResponse | null = null; + + async initialize(baseUrl: string) { + // Fetch discovery information + const response = await fetch(`${baseUrl}/api/discovery`); + this.discovery = await response.json() as API.DiscoveryResponse; + + console.log(`Connected to: ${this.discovery.name} v${this.discovery.version}`); + console.log(`Environment: ${this.discovery.environment}`); + } + + /** + * Check if a specific feature is available + */ + hasFeature(feature: keyof API.ApiCapabilities): boolean { + if (!this.discovery) return false; + return this.discovery.features[feature] === true; + } + + /** + * Get the route for a specific API + */ + getRoute(route: 'data' | 'metadata' | 'auth' | 'automation' | 'storage' | 'graphql'): string | undefined { + if (!this.discovery) return undefined; + return this.discovery.routes[route]; + } + + /** + * Choose the best available API endpoint + */ + getApiEndpoint(preference: 'rest' | 'graphql' = 'rest') { + if (!this.discovery) { + throw new Error('Client not initialized'); + } + + // Check if GraphQL is available + if (preference === 'graphql' && this.discovery.features.graphql && this.discovery.routes.graphql) { + return { type: 'graphql', url: this.discovery.routes.graphql }; + } + + // Fallback to REST (data route) + if (this.discovery.routes.data) { + return { type: 'rest', url: this.discovery.routes.data }; + } + + throw new Error('No API endpoints available'); + } +} + +/** + * Example 4: AI Agent Usage + * + * How an AI agent can use discovery to understand the system + */ +export function generateSystemPromptFromDiscovery(discovery: API.DiscoveryResponse): string { + const { name, version, features, locale } = discovery; + + const prompt = `You are an AI assistant for ${name} (v${version}). + +SYSTEM CAPABILITIES: +${features.graphql ? '- GraphQL API available' : ''} +${features.search ? '- Search functionality available' : ''} +${features.websockets ? '- Real-time updates via WebSockets' : ''} +${features.files ? '- File upload/download supported' : ''} + +LOCALIZATION: +- Default Locale: ${locale.default} +- Supported Languages: ${locale.supported.join(', ')} +- Timezone: ${locale.timezone} + +When helping users, respect these capabilities and localization settings.`; + + return prompt; +} + +// ============================================================================ +// Usage Examples +// ============================================================================ + +// Example: Initialize adaptive client (uncomment to run) +// const client = new AdaptiveClient(); +// await client.initialize('https://api.example.com'); +// if (client.hasFeature('data', 'vectorSearch')) { +// console.log('โœ… Vector search is available - AI/RAG features enabled'); +// } + +// Example: Generate AI prompt (uncomment to run) +// const systemPrompt = generateSystemPromptFromDiscovery(fullDiscoveryResponse); +// console.log('AI System Prompt:\n', systemPrompt); diff --git a/examples/basic/auth-permission-example.ts b/examples/basic/auth-permission-example.ts new file mode 100644 index 000000000..4918b634c --- /dev/null +++ b/examples/basic/auth-permission-example.ts @@ -0,0 +1,465 @@ +/** + * Example: Auth & Permission Protocols + * + * This example demonstrates authentication, authorization, and permission systems in ObjectStack. + * It covers: + * - User identity and sessions + * - Role-based access control (RBAC) + * - Row-level security (RLS) + * - Field-level security (FLS) + * - Sharing and territory management + */ + +import type { Auth, Permission } from '@objectstack/spec'; + +/** + * Example 1: User Identity + * + * User accounts with authentication methods + */ +export const sampleUsers: Auth.User[] = [ + { + id: 'user_001', + email: 'admin@example.com', + name: 'Admin User', + emailVerified: true, + image: 'https://example.com/avatars/admin.jpg', + createdAt: new Date('2024-01-01T00:00:00Z'), + updatedAt: new Date('2024-01-29T10:30:00Z'), + }, + { + id: 'user_002', + email: 'sales@example.com', + name: 'Sales Manager', + emailVerified: true, + image: 'https://example.com/avatars/sales.jpg', + createdAt: new Date('2024-01-05T00:00:00Z'), + updatedAt: new Date('2024-01-29T09:15:00Z'), + }, + { + id: 'user_003', + email: 'rep@example.com', + name: 'Sales Rep', + emailVerified: true, + createdAt: new Date('2024-01-10T00:00:00Z'), + updatedAt: new Date('2024-01-29T08:45:00Z'), + }, +]; + +/** + * Example 2: Role Hierarchy + * + * Organizational role structure with inheritance + * Note: Roles define reporting structure and hierarchy, not permissions. + * Permissions are defined separately in PermissionSets. + */ +export const roleHierarchy: Auth.Role[] = [ + // Top-level admin role + { + name: 'system_administrator', + label: 'System Administrator', + description: 'Top-level system administrator', + // No parent = top of hierarchy + parent: undefined, + }, + + // Sales hierarchy + { + name: 'sales_manager', + label: 'Sales Manager', + description: 'Manages sales team and data', + parent: 'system_administrator', + }, + { + name: 'sales_rep', + label: 'Sales Representative', + description: 'Standard sales user', + parent: 'sales_manager', // Inherits from manager + }, +]; + +/** + * Example 3: Permission Sets + * + * Granular object-level permissions + */ +export const permissionSets: Permission.PermissionSet[] = [ + { + name: 'sales_user_permissions', + label: 'Sales User Permissions', + isProfile: false, + + // Object permissions (record of ObjectPermission) + objects: { + account: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: false, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, + }, + contact: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: false, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, + }, + opportunity: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: false, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, + }, + lead: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, // Can delete own leads + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, + }, + }, + + // Field-level permissions (Field-Level Security, record of FieldPermission) + fields: { + 'account.annual_revenue': { + readable: true, + editable: false, // Can see but not edit revenue + }, + 'opportunity.probability': { + readable: true, + editable: false, // Calculated field, read-only + }, + }, + }, + + { + name: 'sales_manager_permissions', + label: 'Sales Manager Permissions', + isProfile: false, + + // Object permissions (record of ObjectPermission) + objects: { + account: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, + allowTransfer: true, + allowRestore: true, + allowPurge: false, + viewAllRecords: true, // Can view all accounts + modifyAllRecords: true, // Can modify all accounts + }, + opportunity: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, + allowTransfer: true, + allowRestore: true, + allowPurge: false, + viewAllRecords: true, + modifyAllRecords: true, + }, + forecast: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: true, + modifyAllRecords: true, + }, + }, + + // Field-level permissions (Field-Level Security, record of FieldPermission) + fields: { + 'account.annual_revenue': { + readable: true, + editable: true, // Managers can edit revenue + }, + 'opportunity.discount_percent': { + readable: true, + editable: true, // Managers can approve discounts + }, + }, + }, +]; + +/** + * Example 4: Row-Level Security (RLS) + * + * Fine-grained data access control based on record ownership + */ +export const rowLevelSecurityRules: Permission.RowLevelSecurityPolicy[] = [ + { + name: 'opportunity_owner_access', + label: 'Opportunity Owner Access', + object: 'opportunity', + description: 'Users can only access their own opportunities', + operation: 'select', + priority: 100, + + // USING clause - Filter condition + using: `owner_id = current_user.id OR territory IN (SELECT id FROM territories WHERE user_id = current_user.id) OR owner_manager_id = current_user.id`, + + // Apply to these roles + roles: ['sales_rep'], + enabled: true, + }, + + { + name: 'account_territory_access', + label: 'Account Territory Access', + object: 'account', + description: 'Territory-based account access', + operation: 'select', + priority: 100, + + using: `territory IN (SELECT id FROM territories WHERE user_id = current_user.id) AND status = 'active'`, + + roles: ['sales_rep'], + enabled: true, + }, +]; + +/** + * Example 5: Sharing Rules + * + * Grant additional access beyond RLS + */ +export const sharingRules: Permission.SharingRule[] = [ + { + name: 'share_opportunities_with_team', + object: 'opportunity', + description: 'Share opportunities with entire sales team for visibility', + + // Who gets access + sharedWith: { + type: 'role', + roles: ['sales_rep', 'sales_manager'], + }, + + // What records to share + criteria: { + operator: 'AND', + conditions: [ + { + field: 'stage', + operator: 'notEquals', + value: 'closed_lost', + }, + { + field: 'confidential', + operator: 'equals', + value: false, + }, + ], + }, + + // Access level granted + accessLevel: 'read', + }, + + { + name: 'share_accounts_with_partners', + object: 'account', + description: 'Share partner accounts with partner users', + + sharedWith: { + type: 'role', + roles: ['partner_user'], + }, + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'account_type', + operator: 'equals', + value: 'partner', + }, + { + field: 'partner_id', + operator: 'equals', + value: '$CurrentUser.partnerId', + }, + ], + }, + + accessLevel: 'read', + }, +]; + +/** + * Example 6: Territory Management + * + * Geographic or organizational territory assignment + */ +export const territories: Permission.Territory[] = [ + { + name: 'north_america', + label: 'North America', + modelId: 'global_sales_territories', + type: 'geography', + + // Territory assignment rule + assignmentRule: `billing_country IN ('USA', 'Canada', 'Mexico')`, + + // Assigned users + assignedUsers: ['user_002', 'user_003'], + + // Access levels + accountAccess: 'edit', + opportunityAccess: 'edit', + caseAccess: 'read', + }, + + { + name: 'west_coast', + label: 'West Coast', + modelId: 'global_sales_territories', + type: 'geography', + parent: 'north_america', + + assignmentRule: `billing_country = 'USA' AND billing_state IN ('CA', 'OR', 'WA', 'NV', 'AZ')`, + + assignedUsers: ['user_003'], + + accountAccess: 'edit', + opportunityAccess: 'edit', + caseAccess: 'read', + }, +]; + +/** + * Example 7: Permission Checking + * + * Helper functions for checking permissions + */ +export class PermissionChecker { + /** + * Check if user has object permission + */ + hasObjectPermission( + user: Auth.User, + object: string, + operation: 'create' | 'read' | 'update' | 'delete' + ): boolean { + // Find permission sets (example implementation uses all permission sets) + const userPermissionSets = permissionSets; + + // Map operation to permission field + const operationMap = { + create: 'allowCreate', + read: 'allowRead', + update: 'allowEdit', + delete: 'allowDelete', + } as const; + + // Check object permissions + for (const permSet of userPermissionSets) { + const objPerm = permSet.objects[object]; + if (objPerm && objPerm[operationMap[operation]]) { + return true; + } + } + + return false; + } + + /** + * Check if user can access a specific record (RLS) + */ + canAccessRecord(user: Auth.User & { roles?: string[] }, object: string, record: any): boolean { + // Apply RLS rules for user's roles + const userRoles = user.roles || []; + const applicableRules = rowLevelSecurityRules.filter( + (rls) => rls.object === object && rls.roles?.some((r: string) => userRoles.includes(r)) + ); + + // If no RLS rules, check base permissions + if (applicableRules.length === 0) { + return this.hasObjectPermission(user, object, 'read'); + } + + // Evaluate RLS rules + for (const rule of applicableRules) { + if (this.evaluateRule(rule.using, record, user)) { + return true; + } + } + + return false; + } + + /** + * Evaluate a rule against a record + */ + private evaluateRule(rule: any, record: any, user: Auth.User & { roles?: string[] }): boolean { + // Simplified evaluation logic + // In real implementation, evaluate all conditions with operators + return true; + } +} + +/** + * Example 8: Usage Demonstration + */ +export function demonstratePermissions() { + const user = { ...sampleUsers[2], roles: ['sales_rep'] }; // Sales Rep with role + const checker = new PermissionChecker(); + + console.log('=== Permission Check Demo ===\n'); + console.log(`User: ${user.name} (${user.roles?.join(', ')})\n`); + + // Check object permissions + console.log('Object Permissions:'); + console.log('- Can create Account:', checker.hasObjectPermission(user, 'account', 'create')); + console.log('- Can delete Account:', checker.hasObjectPermission(user, 'account', 'delete')); + console.log('- Can update Opportunity:', checker.hasObjectPermission(user, 'opportunity', 'update')); + console.log(''); + + // Check record access + const opportunity = { + id: 'opp_001', + owner: 'user_003', + territory: 'west_coast', + }; + + console.log('Record Access (RLS):'); + console.log('- Can access own opportunity:', checker.canAccessRecord(user, 'opportunity', opportunity)); +} + +// Run demonstration (uncomment to run) +// demonstratePermissions(); + +// Export all examples +export default { + sampleUsers, + roleHierarchy, + permissionSets, + rowLevelSecurityRules, + sharingRules, + territories, +}; diff --git a/examples/basic/automation-example.ts b/examples/basic/automation-example.ts new file mode 100644 index 000000000..7026556ca --- /dev/null +++ b/examples/basic/automation-example.ts @@ -0,0 +1,361 @@ +/** + * Example: Automation Protocol - Workflows & Approvals + * + * This example demonstrates automation capabilities in ObjectStack: + * - Workflow rules (event-driven automation) + * - Approval processes (multi-step approvals) + * - Screen flows (visual automation) + * - ETL processes (data integration) + */ + +import type { Automation } from '@objectstack/spec'; + +/** + * Example 1: Field Update Workflow + * + * Automatically update fields when conditions are met + */ +export const fieldUpdateWorkflows: Automation.WorkflowRule[] = [ + { + name: 'set_opportunity_probability', + objectName: 'opportunity', + active: true, + reevaluateOnChange: false, + + // When to trigger + triggerType: 'on_update', + + // Conditions to check + criteria: 'true', // Run for all records + + // What to do + actions: [ + { + name: 'update_probability', + type: 'field_update', + field: 'probability', + value: ` + CASE stage + WHEN 'prospecting' THEN 10 + WHEN 'qualification' THEN 25 + WHEN 'proposal' THEN 50 + WHEN 'negotiation' THEN 75 + WHEN 'closed_won' THEN 100 + WHEN 'closed_lost' THEN 0 + ELSE 0 + END + `, + }, + ], + }, + + { + name: 'update_account_rating', + objectName: 'account', + active: true, + reevaluateOnChange: false, + + triggerType: 'on_update', + + criteria: 'annual_revenue > 0', + + actions: [ + { + name: 'update_rating', + type: 'field_update', + field: 'rating', + value: ` + CASE + WHEN annual_revenue > 10000000 THEN 'hot' + WHEN annual_revenue > 1000000 THEN 'warm' + ELSE 'cold' + END + `, + }, + ], + }, +]; + +/** + * Example 2: Email Alert Workflows + * + * Send notifications when events occur + */ +export const emailAlertWorkflows: Automation.WorkflowRule[] = [ + { + name: 'notify_manager_large_opportunity', + objectName: 'opportunity', + active: true, + reevaluateOnChange: false, + + triggerType: 'on_create', + + criteria: 'amount > 100000', + + actions: [ + { + name: 'notify_manager', + type: 'email_alert', + template: 'large_opportunity_alert', + recipients: ['owner.manager.email', 'role:sales_director'], + }, + ], + }, + + { + name: 'notify_overdue_tasks', + objectName: 'task', + active: true, + reevaluateOnChange: false, + + triggerType: 'schedule', + + criteria: 'status != "completed" && due_date < $Today', + + actions: [ + { + name: 'send_overdue_reminder', + type: 'email_alert', + template: 'overdue_task_reminder', + recipients: ['assigned_to.email'], + }, + ], + }, +]; + +/** + * Example 3: Task Creation Workflows + * + * Automatically create related records + */ +export const taskCreationWorkflows: Automation.WorkflowRule[] = [ + { + name: 'create_followup_tasks', + objectName: 'lead', + active: true, + reevaluateOnChange: false, + + triggerType: 'on_create', + + criteria: 'status == "new"', + + actions: [ + { + name: 'create_initial_contact_task', + type: 'task_creation', + taskObject: 'task', + subject: 'Initial Contact - {{name}}', + description: 'Make initial contact with lead', + dueDate: '$Today + 1', + priority: 'high', + assignedTo: '{{owner}}', + relatedTo: '{{id}}', + }, + { + name: 'create_qualification_task', + type: 'task_creation', + taskObject: 'task', + subject: 'Qualification Call - {{name}}', + description: 'Schedule qualification call', + dueDate: '$Today + 3', + priority: 'medium', + assignedTo: '{{owner}}', + relatedTo: '{{id}}', + }, + ], + }, +]; + +/** + * Example 4: Approval Process + * + * Multi-step approval workflow for discounts + */ +export const discountApprovalProcess: Automation.ApprovalProcess = { + name: 'opportunity_discount_approval', + label: 'Opportunity Discount Approval', + object: 'opportunity', + description: 'Approval process for opportunity discounts', + active: true, + lockRecord: true, + + // When to trigger approval + entryCriteria: 'discount_percent > 0', + + // Approval steps (sequential) + steps: [ + { + name: 'manager_approval', + label: 'Manager Approval', + description: 'Requires manager approval for discounts up to 20%', + behavior: 'first_response', + rejectionBehavior: 'reject_process', + + // When this step applies + entryCriteria: 'discount_percent <= 20', + + // Who can approve + approvers: [ + { + type: 'field', + value: 'owner.manager', + }, + ], + + // Approval actions + onApprove: [ + { + type: 'field_update', + name: 'set_approved', + config: { + field: 'approval_status', + value: 'approved', + }, + }, + ], + + // Rejection actions + onReject: [ + { + type: 'field_update', + name: 'set_rejected', + config: { + field: 'approval_status', + value: 'rejected', + }, + }, + { + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, + }, + }, + ], + }, + + { + name: 'director_approval', + label: 'Director Approval', + description: 'Requires director approval for discounts over 20%', + behavior: 'first_response', + rejectionBehavior: 'reject_process', + + entryCriteria: 'discount_percent > 20', + + approvers: [ + { + type: 'role', + value: 'sales_director', + }, + ], + + onApprove: [ + { + type: 'field_update', + name: 'set_approved', + config: { + field: 'approval_status', + value: 'approved', + }, + }, + { + type: 'email_alert', + name: 'notify_owner_approval', + config: { + template: 'discount_approved', + recipients: ['owner.email'], + }, + }, + ], + + onReject: [ + { + type: 'field_update', + name: 'set_rejected', + config: { + field: 'approval_status', + value: 'rejected', + }, + }, + { + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, + }, + }, + { + type: 'email_alert', + name: 'notify_owner_rejection', + config: { + template: 'discount_rejected', + recipients: ['owner.email'], + }, + }, + ], + }, + ], + + // Final approval actions + onFinalApprove: [ + { + type: 'field_update', + name: 'save_approved_discount', + config: { + field: 'approved_discount', + value: '{{discount_percent}}', + }, + }, + ], + + // Final rejection actions + onFinalReject: [ + { + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, + }, + }, + ], +}; + + + +/** + * Example 7: Usage Demonstration + */ +export function demonstrateAutomation() { + console.log('=== Automation Examples ===\n'); + + console.log('1. Field Update Workflows:'); + console.log(` - ${fieldUpdateWorkflows.length} rules configured`); + console.log(` - Example: "${fieldUpdateWorkflows[0].name}"`); + console.log(''); + + console.log('2. Email Alert Workflows:'); + console.log(` - ${emailAlertWorkflows.length} alert rules`); + console.log(` - Example: "${emailAlertWorkflows[0].name}"`); + console.log(''); + + console.log('3. Approval Process:'); + console.log(` - Name: ${discountApprovalProcess.name}`); + console.log(` - Steps: ${discountApprovalProcess.steps?.length || 0}`); + console.log(` - Description: "${discountApprovalProcess.description || 'N/A'}"`); + console.log(''); +} + +// Run demonstration (uncomment to run) +// demonstrateAutomation(); + +// Export all examples +export default { + fieldUpdateWorkflows, + emailAlertWorkflows, + taskCreationWorkflows, + discountApprovalProcess, +}; diff --git a/examples/capabilities-example.ts b/examples/basic/capabilities-example.ts similarity index 94% rename from examples/capabilities-example.ts rename to examples/basic/capabilities-example.ts index d25cb73c9..98fe74115 100644 --- a/examples/capabilities-example.ts +++ b/examples/basic/capabilities-example.ts @@ -393,24 +393,24 @@ export function getEnabledCapabilities( } // ============================================================================ -// Usage Examples +// Usage Examples (uncomment to run) // ============================================================================ // Example: Check if vector search is available -if (hasCapability(productionCapabilities, 'data', 'vectorSearch')) { - console.log('โœ… Vector search is available for RAG workflows'); -} +// if (hasCapability(productionCapabilities, 'data', 'vectorSearch')) { +// console.log('โœ… Vector search is available for RAG workflows'); +// } // Example: Get all enabled ObjectUI capabilities -const uiFeatures = getEnabledCapabilities(productionCapabilities, 'ui'); -console.log('Enabled UI features:', uiFeatures); +// const uiFeatures = getEnabledCapabilities(productionCapabilities, 'ui'); +// console.log('Enabled UI features:', uiFeatures); // Example: Compare capabilities between environments -console.log( - 'Production has GraphQL?', - hasCapability(productionCapabilities, 'system', 'graphqlApi') -); -console.log( - 'Development has GraphQL?', - hasCapability(developmentCapabilities, 'system', 'graphqlApi') -); +// console.log( +// 'Production has GraphQL?', +// hasCapability(productionCapabilities, 'system', 'graphqlApi') +// ); +// console.log( +// 'Development has GraphQL?', +// hasCapability(developmentCapabilities, 'system', 'graphqlApi') +// ); diff --git a/examples/basic/package.json b/examples/basic/package.json new file mode 100644 index 000000000..40d11edd1 --- /dev/null +++ b/examples/basic/package.json @@ -0,0 +1,18 @@ +{ + "name": "@objectstack/example-basic", + "version": "0.6.1", + "description": "Basic protocol examples demonstrating ObjectStack core features", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@objectstack/spec": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.0.0", + "tsx": "^4.21.0" + } +} diff --git a/examples/basic/stack-definition-example.ts b/examples/basic/stack-definition-example.ts new file mode 100644 index 000000000..e53630412 --- /dev/null +++ b/examples/basic/stack-definition-example.ts @@ -0,0 +1,408 @@ +/** + * Example: ObjectStack Definition with defineStack() + * + * This example demonstrates the comprehensive stack definition using the defineStack() helper. + * It shows how to define a complete ObjectStack project including: + * - Manifest configuration + * - Objects (Data Protocol) + * - UI components (UI Protocol) + * - Automation (Workflow Protocol) + * - Security (Auth & Permission Protocol) + * - AI Agents (AI Protocol) + */ + +import { defineStack } from '@objectstack/spec'; + +/** + * Example 1: Minimal Stack Definition + * + * The simplest possible ObjectStack configuration. + */ +export const minimalStack = defineStack({ + manifest: { + id: 'com.example.my-app', + type: 'app', + name: 'my-app', + version: '1.0.0', + description: 'Minimal ObjectStack application', + }, +}); + +/** + * Example 2: Task Management Stack + * + * A complete task management application with Objects, UI, and Workflows. + */ +export const taskManagementStack = defineStack({ + // ============================================================================ + // System Configuration + // ============================================================================ + manifest: { + id: 'com.example.task-manager', + type: 'app', + name: 'task-manager', + version: '1.0.0', + description: 'Task management application with ObjectStack', + }, + + // ============================================================================ + // Data Layer: Objects + // ============================================================================ + objects: [ + { + name: 'task', + label: 'Task', + icon: 'check-square', + description: 'Work items and to-dos', + + enable: { + apiEnabled: true, + trackHistory: true, + }, + + fields: { + subject: { + type: 'text', + label: 'Subject', + description: 'Task title', + required: true, + maxLength: 255, + }, + description: { + type: 'textarea', + label: 'Description', + description: 'Detailed description', + maxLength: 5000, + }, + status: { + type: 'select', + label: 'Status', + description: 'Current status', + options: [ + { value: 'todo', label: 'To Do' }, + { value: 'in_progress', label: 'In Progress' }, + { value: 'done', label: 'Done' }, + ], + defaultValue: 'todo', + }, + priority: { + type: 'select', + label: 'Priority', + options: [ + { value: 'low', label: 'Low' }, + { value: 'medium', label: 'Medium' }, + { value: 'high', label: 'High' }, + ], + defaultValue: 'medium', + }, + due_date: { + type: 'date', + label: 'Due Date', + }, + assigned_to: { + type: 'lookup', + label: 'Assigned To', + reference: 'user', + }, + }, + }, + ], + + // ============================================================================ + // UI Layer: Apps, Views, Dashboards + // ============================================================================ + apps: [ + { + name: 'task_manager', + label: 'Task Manager', + description: 'Manage your tasks', + icon: 'clipboard-list', + navigation: [ + { + type: 'object', + object: 'task', + label: 'My Tasks', + defaultView: 'my_tasks', + }, + { + type: 'dashboard', + dashboard: 'task_overview', + label: 'Overview', + }, + ], + }, + ], + + views: [ + { + list: { + type: 'grid', + columns: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], + filter: [ + { field: 'assigned_to', operator: 'equals', value: '$CurrentUser.id' }, + ], + sort: [ + { field: 'priority', order: 'desc' }, + { field: 'due_date', order: 'asc' }, + ], + }, + }, + ], + + dashboards: [ + { + name: 'task_overview', + label: 'Task Overview', + description: 'Task statistics and metrics', + widgets: [ + { + type: 'metric', + layout: { x: 0, y: 0, w: 6, h: 2 }, + title: 'Total Tasks', + object: 'task', + aggregate: 'count', + }, + { + type: 'pie', + layout: { x: 6, y: 0, w: 6, h: 2 }, + title: 'Tasks by Status', + object: 'task', + categoryField: 'status', + aggregate: 'count', + }, + ], + }, + ], + + // ============================================================================ + // Automation Layer: Workflows + // ============================================================================ + workflows: [ + { + name: 'notify_overdue_tasks', + objectName: 'task', + active: true, + reevaluateOnChange: false, + triggerType: 'schedule', + criteria: 'status != "done" && due_date < $Today', + actions: [ + { + name: 'send_overdue_notification', + type: 'email_alert', + recipients: ['assigned_to.email'], + template: 'overdue_task_notification', + }, + ], + }, + ], + + // ============================================================================ + // Security Layer: Roles & Permissions + // ============================================================================ + roles: [ + { + name: 'task_manager', + label: 'Task Manager', + description: 'Can manage all tasks', + }, + { + name: 'task_user', + label: 'Task User', + description: 'Can manage own tasks', + }, + ], +}); + +/** + * Example 3: CRM Stack with AI Agent + * + * A CRM system with an AI sales assistant. + */ +export const crmWithAIStack = defineStack({ + manifest: { + id: 'com.example.ai-crm', + type: 'app', + name: 'ai-crm', + version: '1.0.0', + description: 'CRM with AI-powered sales assistant', + }, + + // Objects + objects: [ + { + name: 'account', + label: 'Account', + icon: 'building', + enable: { + apiEnabled: true, + trackHistory: true, + }, + fields: { + name: { + type: 'text', + label: 'Account Name', + required: true, + }, + industry: { + type: 'select', + label: 'Industry', + options: [ + { value: 'technology', label: 'Technology' }, + { value: 'finance', label: 'Finance' }, + { value: 'healthcare', label: 'Healthcare' }, + ], + }, + annual_revenue: { + type: 'currency', + label: 'Annual Revenue', + }, + }, + }, + { + name: 'opportunity', + label: 'Opportunity', + icon: 'target', + enable: { + apiEnabled: true, + trackHistory: true, + }, + fields: { + name: { + type: 'text', + label: 'Opportunity Name', + required: true, + }, + account: { + type: 'lookup', + label: 'Account', + reference: 'account', + }, + amount: { + type: 'currency', + label: 'Amount', + }, + stage: { + type: 'select', + label: 'Stage', + 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' }, + ], + defaultValue: 'prospecting', + }, + close_date: { + type: 'date', + label: 'Expected Close Date', + }, + }, + }, + ], + + // AI Agent + agents: [ + { + name: 'sales_assistant', + label: 'Sales Assistant', + role: 'Sales Operations Assistant', + + instructions: `You are a helpful sales assistant with access to the CRM system. +You can help users: +- Find and analyze account and opportunity data +- Create new opportunities +- Update deal stages +- Provide sales insights and forecasts + +Always be professional and data-driven in your responses.`, + + tools: [ + { + name: 'search_accounts', + description: 'Search for accounts by name or industry', + type: 'query', + }, + { + name: 'get_opportunity_pipeline', + description: 'Get pipeline statistics by stage', + type: 'query', + }, + { + name: 'create_opportunity', + description: 'Create a new sales opportunity', + type: 'action', + }, + ], + }, + ], + + // Apps + apps: [ + { + name: 'sales_app', + label: 'Sales', + description: 'Sales CRM Application', + icon: 'briefcase', + navigation: [ + { + type: 'object', + object: 'account', + label: 'Accounts', + }, + { + type: 'object', + object: 'opportunity', + label: 'Opportunities', + }, + { + type: 'custom', + label: 'AI Assistant', + component: 'ChatInterface', + componentProps: { + agent: 'sales_assistant', + }, + }, + ], + }, + ], +}); + +/** + * Example 4: Using Stack Definition + * + * How to use the stack definition in your application. + */ +export function demonstrateStackUsage() { + // TypeScript knows the exact structure + console.log('App Name:', taskManagementStack.manifest.name); + console.log('Objects:', taskManagementStack.objects?.length); + console.log('Apps:', taskManagementStack.apps?.length); + + // Access specific objects + const taskObject = taskManagementStack.objects?.[0]; + if (taskObject) { + console.log('Task Object:', taskObject.name); + console.log('Task Fields:', Object.keys(taskObject.fields || {}).length); + } + + // Access AI agents + const agent = crmWithAIStack.agents?.[0]; + if (agent) { + console.log('AI Agent:', agent.name); + console.log('Tools:', agent.tools?.length || 0); + } +} + +// Run demonstration (uncomment to run) +// demonstrateStackUsage(); + +// ============================================================================ +// Export for use +// ============================================================================ +export default { + minimalStack, + taskManagementStack, + crmWithAIStack, +}; diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json new file mode 100644 index 000000000..1fc9f3a06 --- /dev/null +++ b/examples/basic/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2022", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": false, + "noEmit": true + }, + "include": [ + "*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/examples/complete-event-driven-example.ts b/examples/complete-event-driven-example.ts deleted file mode 100644 index 73d906d72..000000000 --- a/examples/complete-event-driven-example.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Complete Event-Driven Example - * - * Demonstrates all three requested features: - * 1. Event-driven mechanism (Data Engine + Flow Engine) - * 2. UI Engine mounting (Dynamic routes) - * 3. Configuration-driven loading (JSON config) - */ - -import { ObjectKernel } from '@objectstack/runtime'; -import { DataEnginePlugin } from './data-engine-plugin'; -import { FlowEnginePlugin } from './flow-engine-plugin'; -import { UiEnginePlugin } from './ui-engine-plugin'; -import { HonoServerPlugin } from '@objectstack/plugin-hono-server'; -import { ObjectQLPlugin } from '@objectstack/objectql'; -import { PluginRegistry, createKernelFromConfig } from './config-loader'; - -/** - * Example 1: Manual Plugin Loading (Programmatic) - */ -async function example1_ManualLoading() { - console.log('\n=== Example 1: Manual Plugin Loading ===\n'); - - const kernel = new ObjectKernel(); - - // Load plugins in any order - kernel will resolve dependencies - kernel - .use(new UiEnginePlugin()) // Depends on server - .use(new FlowEnginePlugin()) // Listens to data events - .use(new DataEnginePlugin()) // Emits data events - .use(new HonoServerPlugin({ port: 3000 })) // HTTP server - .use(new ObjectQLPlugin()); // Data engine - - await kernel.bootstrap(); - - // Test event-driven flow - console.log('\n--- Testing Event-Driven Flow ---\n'); - const db = kernel.getService('db'); - - // This will trigger Flow Engine automatically - await db.insert('orders', { - customer: 'John Doe', - total: 299.99, - status: 'pending' - }); - - await db.insert('contacts', { - name: 'Jane Smith', - email: 'jane@example.com' - }); - - // Update will also trigger flow - await db.update('orders', '12345', { - status: 'shipped' - }); - - console.log('\nโœ… Example 1 Complete'); - await kernel.shutdown(); -} - -/** - * Example 2: Configuration-Driven Loading - */ -async function example2_ConfigDrivenLoading() { - console.log('\n=== Example 2: Configuration-Driven Loading ===\n'); - - // Register plugins in the registry - PluginRegistry.register('objectstack-objectql', () => new ObjectQLPlugin()); - PluginRegistry.register('objectstack-data', () => new DataEnginePlugin()); - PluginRegistry.register('objectstack-server', () => new HonoServerPlugin({ port: 3000 })); - PluginRegistry.register('objectstack-flow', () => new FlowEnginePlugin()); - PluginRegistry.register('objectstack-ui', () => new UiEnginePlugin()); - - // Load from config file - const kernel = await createKernelFromConfig('./examples/objectstack.config.json'); - - await kernel.bootstrap(); - - console.log('\nโœ… Example 2 Complete'); - await kernel.shutdown(); -} - -/** - * Example 3: Custom Event Hooks - */ -async function example3_CustomEventHooks() { - console.log('\n=== Example 3: Custom Event Hooks ===\n'); - - const kernel = new ObjectKernel(); - - // Add plugins - kernel - .use(new ObjectQLPlugin()) - .use(new DataEnginePlugin()) - .use(new FlowEnginePlugin()) - .use(new HonoServerPlugin({ port: 3000 })); - - // Register custom hook before bootstrap - const context = (kernel as any).context; - - context.hook('data:record:beforeCreate', async ({ table, data }: any) => { - console.log(`[Custom Hook] ๐Ÿ” Validating ${table} record:`, data); - - // Add custom validation - if (table === 'orders' && !data.customer) { - throw new Error('Customer is required for orders'); - } - - // Add timestamps automatically - data.createdAt = new Date().toISOString(); - }); - - context.hook('data:record:afterCreate', async ({ table, data }: any) => { - console.log(`[Custom Hook] ๐Ÿ“Š Logging ${table} creation for analytics`); - // Send to analytics service - }); - - await kernel.bootstrap(); - - // Test with validation - const db = kernel.getService('db'); - - try { - await db.insert('orders', { total: 100 }); - } catch (e: any) { - console.log('โŒ Validation failed:', e.message); - } - - // This should work - await db.insert('orders', { - customer: 'Alice', - total: 150 - }); - - console.log('\nโœ… Example 3 Complete'); - await kernel.shutdown(); -} - -/** - * Run all examples - */ -async function main() { - try { - await example1_ManualLoading(); - // await example2_ConfigDrivenLoading(); // Uncomment to test - // await example3_CustomEventHooks(); // Uncomment to test - } catch (error) { - console.error('Error:', error); - process.exit(1); - } -} - -// Run if executed directly -if (require.main === module) { - main(); -} diff --git a/examples/config-loader.ts b/examples/config-loader.ts deleted file mode 100644 index 7f5b2b6d2..000000000 --- a/examples/config-loader.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Configuration-Driven Plugin Loader - * - * Demonstrates metadata-driven plugin loading: - * - Loads plugins from JSON configuration - * - Supports enable/disable flags - * - Supports plugin-specific options - * - Makes the platform truly low-code - */ - -import { ObjectKernel, Plugin } from '@objectstack/runtime'; -import { readFileSync } from 'fs'; -import { resolve } from 'path'; - -/** - * Plugin configuration schema - */ -export interface PluginConfig { - name: string; - enabled: boolean; - module?: string; // Module path for dynamic loading - options?: Record; -} - -/** - * Kernel configuration schema - */ -export interface KernelConfig { - version: string; - plugins: PluginConfig[]; -} - -/** - * Plugin registry - maps plugin names to plugin classes/factories - */ -export class PluginRegistry { - private static registry: Map Plugin> = new Map(); - - /** - * Register a plugin factory - */ - static register(name: string, factory: () => Plugin) { - this.registry.set(name, factory); - } - - /** - * Get a plugin factory by name - */ - static get(name: string): (() => Plugin) | undefined { - return this.registry.get(name); - } - - /** - * Check if a plugin is registered - */ - static has(name: string): boolean { - return this.registry.has(name); - } -} - -/** - * Load kernel configuration from JSON file - */ -export function loadKernelConfig(configPath: string): KernelConfig { - const absolutePath = resolve(configPath); - const content = readFileSync(absolutePath, 'utf-8'); - return JSON.parse(content) as KernelConfig; -} - -/** - * Create kernel from configuration file - */ -export async function createKernelFromConfig(configPath: string): Promise { - const config = loadKernelConfig(configPath); - const kernel = new ObjectKernel(); - - console.log(`[Config] Loading ObjectStack v${config.version}`); - console.log(`[Config] Found ${config.plugins.length} plugin(s) in configuration`); - - for (const pluginConfig of config.plugins) { - if (!pluginConfig.enabled) { - console.log(`[Config] โญ๏ธ Skipping disabled plugin: ${pluginConfig.name}`); - continue; - } - - // Get plugin factory from registry - const factory = PluginRegistry.get(pluginConfig.name); - - if (!factory) { - console.warn(`[Config] โš ๏ธ Plugin not found in registry: ${pluginConfig.name}`); - continue; - } - - // Create plugin instance - const plugin = factory(); - - // Apply options if provided (this would need plugin-specific handling) - if (pluginConfig.options) { - console.log(`[Config] ๐Ÿ”ง Applying options to ${pluginConfig.name}:`, pluginConfig.options); - // In a real implementation, pass options to plugin constructor - } - - kernel.use(plugin); - console.log(`[Config] โœ… Loaded plugin: ${pluginConfig.name}`); - } - - return kernel; -} - -/** - * Example usage: - * - * // 1. Register plugins - * PluginRegistry.register('objectstack-data', () => new DataEnginePlugin()); - * PluginRegistry.register('objectstack-flow', () => new FlowEnginePlugin()); - * PluginRegistry.register('objectstack-ui', () => new UiEnginePlugin()); - * - * // 2. Create kernel from config - * const kernel = await createKernelFromConfig('./objectstack.config.json'); - * - * // 3. Bootstrap - * await kernel.bootstrap(); - */ diff --git a/examples/crm/README.md b/examples/crm/README.md index c7b0dfdc0..c2b5905ae 100644 --- a/examples/crm/README.md +++ b/examples/crm/README.md @@ -166,15 +166,34 @@ Demonstrates: This package is part of the `examples` workspace. To build it and verify types: ```bash -# Build the example -pnpm build +# From monorepo root +pnpm install + +# Build the spec package first +pnpm --filter @objectstack/spec build + +# Build this example +pnpm --filter @objectstack/example-crm build # Run type checking -pnpm typecheck +pnpm --filter @objectstack/example-crm typecheck ``` ## ๐Ÿ“– Learning Resources +**For Beginners:** +1. Start with [Todo Example](../todo/) for basics +2. Review [Basic Protocol Examples](../basic/) to understand individual protocols +3. Then explore this CRM example for comprehensive implementation + +**Protocol References in Basic Examples:** +- [Stack Definition](../basic/stack-definition-example.ts) - How to use `defineStack()` +- [Capabilities](../basic/capabilities-example.ts) - Runtime capabilities configuration +- [Auth & Permissions](../basic/auth-permission-example.ts) - RBAC and RLS patterns +- [Automation](../basic/automation-example.ts) - Workflows and approvals +- [AI & RAG](../basic/ai-rag-example.ts) - AI integration patterns + +**In This Example:** Each object file contains detailed comments explaining: - Field configuration options - View setup patterns diff --git a/examples/custom-objectql-example.ts b/examples/custom-objectql-example.ts deleted file mode 100644 index 2d1cd2718..000000000 --- a/examples/custom-objectql-example.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Example: Custom ObjectQL Instance - * - * This demonstrates how to use a custom ObjectQL instance with the kernel. - * This is useful when you have a separate ObjectQL implementation or need - * custom configuration. - */ - -import { ObjectKernel, DriverPlugin } from '@objectstack/runtime'; -import { ObjectQLPlugin, ObjectQL } from '@objectstack/objectql'; -import { InMemoryDriver } from '@objectstack/driver-memory'; - -(async () => { - console.log('๐Ÿš€ Example: Custom ObjectQL Instance...'); - - // Create a custom ObjectQL instance with specific configuration - const customQL = new ObjectQL({ - env: 'development', - // Add any custom host context here - customFeature: true, - debug: true - }); - - // You can also pre-configure the ObjectQL instance - // For example, register custom hooks - customQL.registerHook('beforeInsert', async (ctx) => { - console.log(`[Custom Hook] Before inserting into ${ctx.object}`); - }); - - // Create kernel with the custom ObjectQL instance - const kernel = new ObjectKernel(); - - kernel - // Register your custom ObjectQL instance - .use(new ObjectQLPlugin(customQL)) - - // Add your driver - .use(new DriverPlugin(new InMemoryDriver(), 'memory')); - - // Add other plugins and app configs as needed - - await kernel.bootstrap(); - - console.log('โœ… Kernel started with custom ObjectQL instance'); - - // Access ObjectQL via service registry - const objectql = kernel.getService('objectql'); - console.log('ObjectQL instance:', objectql); -})(); diff --git a/examples/data-engine-plugin.ts b/examples/data-engine-plugin.ts deleted file mode 100644 index 663901ac7..000000000 --- a/examples/data-engine-plugin.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Enhanced Data Engine Plugin Example - * - * Demonstrates event emission: - * - Triggers hooks before/after data operations - * - Allows other plugins (like Flow Engine) to react - * - Implements the event producer pattern - */ - -import { Plugin, PluginContext } from '@objectstack/runtime'; - -export class DataEnginePlugin implements Plugin { - name = 'com.objectstack.engine.data'; - version = '1.0.0'; - - async init(ctx: PluginContext) { - // Create enhanced database service with event hooks - const db = { - // Insert with event hooks - insert: async (table: string, data: any) => { - ctx.logger.log(`[DB] Preparing to insert into ${table}...`); - - // 1. Trigger "before" hook - allows modification - await ctx.trigger('data:record:beforeCreate', { table, data }); - - // Simulate database insertion - const record = { id: Math.random().toString(36).substr(2, 9), ...data }; - ctx.logger.log(`[DB] โœ… Inserted:`, record); - - // 2. Trigger "after" hook - for automation (non-blocking) - // Use fire-and-forget to avoid blocking the main flow - ctx.trigger('data:record:afterCreate', { table, data: record }) - .catch(err => ctx.logger.error('[DB] Error in afterCreate hook:', err)); - - return record; - }, - - // Update with event hooks - update: async (table: string, id: string, data: any) => { - ctx.logger.log(`[DB] Updating ${table}/${id}...`); - - // Simulate getting old record - const oldRecord = { id, ...data }; - - // Trigger "before" hook - await ctx.trigger('data:record:beforeUpdate', { table, id, data, oldRecord }); - - // Simulate update - const updatedRecord = { ...oldRecord, ...data }; - ctx.logger.log(`[DB] โœ… Updated:`, updatedRecord); - - // Trigger "after" hook with changes - const changes = data; // In real implementation, compute actual changes - ctx.trigger('data:record:afterUpdate', { table, id, data: updatedRecord, changes }) - .catch(err => ctx.logger.error('[DB] Error in afterUpdate hook:', err)); - - return updatedRecord; - }, - - // Delete with event hooks - delete: async (table: string, id: string) => { - ctx.logger.log(`[DB] Deleting ${table}/${id}...`); - - // Trigger "before" hook - await ctx.trigger('data:record:beforeDelete', { table, id }); - - // Simulate deletion - ctx.logger.log(`[DB] โœ… Deleted ${table}/${id}`); - - // Trigger "after" hook - ctx.trigger('data:record:afterDelete', { table, id }) - .catch(err => ctx.logger.error('[DB] Error in afterDelete hook:', err)); - - return { id }; - }, - - // Query method (without events for now) - query: (sql: string) => { - ctx.logger.log(`[DB] Executing query: ${sql}`); - return []; - } - }; - - ctx.registerService('db', db); - ctx.logger.log('[DB] Enhanced Data Engine service registered with event hooks'); - } - - async start(ctx: PluginContext) { - const db = ctx.getService('db'); - ctx.logger.log('[DB] Data Engine ready'); - } -} diff --git a/examples/flow-engine-plugin.ts b/examples/flow-engine-plugin.ts deleted file mode 100644 index 2460c3338..000000000 --- a/examples/flow-engine-plugin.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Flow Engine Plugin Example - * - * Demonstrates event-driven architecture: - * - Listens to data:record:afterCreate events - * - Triggers automated workflows based on data changes - * - Completely decoupled from Data Engine - */ - -import { Plugin, PluginContext } from '@objectstack/runtime'; - -export class FlowEnginePlugin implements Plugin { - name = 'com.objectstack.engine.flow'; - version = '1.0.0'; - - async init(ctx: PluginContext) { - // Register Flow Engine service (for programmatic access) - const flowEngine = { - executeFlow: async (flowName: string, data: any) => { - ctx.logger.log(`[Flow] Executing flow: ${flowName}`, data); - // Flow execution logic here - } - }; - - ctx.registerService('flow-engine', flowEngine); - ctx.logger.log('[Flow] Flow Engine service registered'); - } - - async start(ctx: PluginContext) { - // Listen to data creation events - ctx.hook('data:record:afterCreate', async ({ table, data }) => { - ctx.logger.log(`[Flow] ๐Ÿ“จ Detected new record in ${table}`); - - // Example: Trigger order processing workflow - if (table === 'orders' && data.status === 'pending') { - ctx.logger.log(`[Flow] โšก๏ธ Triggering 'Order Processing' flow for Order #${data.id}`); - - // Get flow engine service - const flowEngine = ctx.getService('flow-engine'); - await flowEngine.executeFlow('process_order', data); - } - - // Example: Trigger notification workflow - if (table === 'contacts' && data.email) { - ctx.logger.log(`[Flow] ๐Ÿ“ง Triggering 'Welcome Email' flow for ${data.email}`); - const flowEngine = ctx.getService('flow-engine'); - await flowEngine.executeFlow('send_welcome_email', data); - } - }); - - // Listen to data update events - ctx.hook('data:record:afterUpdate', async ({ table, data, changes }) => { - ctx.logger.log(`[Flow] ๐Ÿ“ Detected update in ${table}`, changes); - - if (table === 'orders' && changes.status === 'shipped') { - ctx.logger.log(`[Flow] ๐Ÿ“ฆ Triggering 'Shipping Notification' flow`); - const flowEngine = ctx.getService('flow-engine'); - await flowEngine.executeFlow('notify_shipping', data); - } - }); - - ctx.logger.log('[Flow] โœ… Flow Engine initialized and listening to data events'); - } - - async destroy() { - console.log('[Flow] Flow Engine stopped'); - } -} diff --git a/examples/mini-kernel-example.ts b/examples/mini-kernel-example.ts deleted file mode 100644 index 225bb9f22..000000000 --- a/examples/mini-kernel-example.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * MiniKernel Architecture Example - * - * This example demonstrates the new ObjectKernel (MiniKernel) architecture - * where ObjectQL, Drivers, and HTTP Server are all equal plugins. - */ - -import { ObjectKernel, DriverPlugin } from '@objectstack/runtime'; -import { ObjectQLPlugin } from '@objectstack/objectql'; - -// Mock driver for demonstration -const mockDriver = { - name: 'memory-driver', - version: '1.0.0', - capabilities: { - crud: true, - query: true, - }, - async connect() { - console.log('[MockDriver] Connected'); - }, - async disconnect() { - console.log('[MockDriver] Disconnected'); - }, - async find(objectName: string, query: any) { - console.log(`[MockDriver] Finding in ${objectName}:`, query); - return []; - }, - async findOne(objectName: string, id: string) { - console.log(`[MockDriver] Finding one in ${objectName}:`, id); - return null; - }, - async create(objectName: string, data: any) { - console.log(`[MockDriver] Creating in ${objectName}:`, data); - return { id: 'mock-id', ...data }; - }, - async update(objectName: string, id: string, data: any) { - console.log(`[MockDriver] Updating ${objectName}/${id}:`, data); - return { id, ...data }; - }, - async delete(objectName: string, id: string) { - console.log(`[MockDriver] Deleting ${objectName}/${id}`); - return { id }; - }, - registerDriver(driver: any) { - // Mock implementation - }, -}; - -async function main() { - console.log('๐Ÿš€ Starting MiniKernel Example\n'); - - // Create kernel instance - const kernel = new ObjectKernel(); - - // Register plugins in any order - kernel will resolve dependencies - kernel - .use(new DriverPlugin(mockDriver, 'memory')) // Depends on ObjectQL - .use(new ObjectQLPlugin()); // No dependencies - - // Bootstrap the kernel - await kernel.bootstrap(); - - // Access services - console.log('\n๐Ÿ“ฆ Accessing Services:'); - const objectql = kernel.getService('objectql'); - console.log('โœ… ObjectQL service available:', !!objectql); - - console.log('\nโœ… MiniKernel example completed successfully!\n'); - - // Shutdown - await kernel.shutdown(); -} - -// Run example -main().catch(console.error); diff --git a/examples/objectstack.config.json b/examples/objectstack.config.json deleted file mode 100644 index 0e03ee3af..000000000 --- a/examples/objectstack.config.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "version": "1.0.0", - "plugins": [ - { - "name": "objectstack-objectql", - "enabled": true, - "options": { - "env": "production", - "debug": false - } - }, - { - "name": "objectstack-driver-memory", - "enabled": true, - "options": { - "maxRecords": 10000 - } - }, - { - "name": "objectstack-data", - "enabled": true, - "options": { - "enableHooks": true, - "enableValidation": true - } - }, - { - "name": "objectstack-server", - "enabled": true, - "options": { - "port": 3000, - "cors": true - } - }, - { - "name": "objectstack-api", - "enabled": true, - "options": { - "prefix": "/api/v1" - } - }, - { - "name": "objectstack-flow", - "enabled": false, - "options": { - "maxConcurrentFlows": 10 - } - }, - { - "name": "objectstack-ui", - "enabled": true, - "options": { - "theme": "default", - "enableDarkMode": true - } - } - ] -} diff --git a/examples/todo/README.md b/examples/todo/README.md index ad11a6ff2..51b98ddae 100644 --- a/examples/todo/README.md +++ b/examples/todo/README.md @@ -62,12 +62,14 @@ pnpm --filter @objectstack/example-todo typecheck ## ๐Ÿ“– Learning Path 1. **Start Here** - Simple task object, basic configuration -2. **Next Step** - [CRM Example](../crm/) - Advanced features, workflows, validations, UI components -3. **Then** - [Official Documentation](../../content/docs/) - Complete protocol reference +2. **Learn Protocols** - [Basic Examples](../basic/) - Protocol examples and patterns +3. **Next Step** - [CRM Example](../crm/) - Advanced features, workflows, validations, UI components +4. **Then** - [Official Documentation](../../content/docs/) - Complete protocol reference ## ๐Ÿ”— Related Resources - [Getting Started Guide](../../content/docs/guides/getting-started.mdx) +- [Basic Protocol Examples](../basic/) - Learn individual protocols - [Object Schema Reference](../../packages/spec/src/data/object.zod.ts) - [Field Types Reference](../../packages/spec/src/data/field.zod.ts) - [CRM Example](../crm/README.md) - Full-featured reference implementation diff --git a/examples/ui-engine-plugin.ts b/examples/ui-engine-plugin.ts deleted file mode 100644 index 2f5995212..000000000 --- a/examples/ui-engine-plugin.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * UI Engine Plugin Example - * - * Demonstrates dynamic route mounting: - * - Depends on HTTP server plugin - * - Dynamically registers routes for UI rendering - * - Can serve static assets and SPA routes - */ - -import { Plugin, PluginContext } from '@objectstack/runtime'; - -export class UiEnginePlugin implements Plugin { - name = 'com.objectstack.engine.ui'; - version = '1.0.0'; - dependencies = ['com.objectstack.server.hono']; // Depends on HTTP server - - async init(ctx: PluginContext) { - // Register UI Engine service - const uiEngine = { - renderPage: (route: string, data: any) => { - return ` - - - - ObjectStack UI - ${route} - - - -
-

ObjectStack Application

-

Current Route: ${route}

-
-
-
-

UI Engine Loaded

-

This page is dynamically rendered by the UI Engine plugin

-
${JSON.stringify(data, null, 2)}
-
-
- - - `; - } - }; - - ctx.registerService('ui-engine', uiEngine); - ctx.logger.log('[UI] UI Engine service registered'); - } - - async start(ctx: PluginContext) { - // Get HTTP server service - const app = ctx.getService('http-server'); - const uiEngine = ctx.getService('ui-engine'); - - // Register UI routes - - // 1. Main app route - app.get('/app', (c: any) => { - const html = uiEngine.renderPage('/app', { message: 'Welcome to ObjectStack' }); - return c.html(html); - }); - - // 2. Dynamic app routes (SPA-style) - app.get('/app/*', (c: any) => { - const path = c.req.path; - const html = uiEngine.renderPage(path, { - route: path, - timestamp: new Date().toISOString() - }); - return c.html(html); - }); - - // 3. List view route (example) - app.get('/ui/list/:object', (c: any) => { - const objectName = c.req.param('object'); - const html = uiEngine.renderPage(`/ui/list/${objectName}`, { - object: objectName, - view: 'list' - }); - return c.html(html); - }); - - // 4. Form view route (example) - app.get('/ui/form/:object/:id?', (c: any) => { - const objectName = c.req.param('object'); - const id = c.req.param('id'); - const html = uiEngine.renderPage(`/ui/form/${objectName}/${id || 'new'}`, { - object: objectName, - id: id || null, - view: 'form' - }); - return c.html(html); - }); - - ctx.logger.log('[UI] โœ… UI Engine routes mounted:'); - ctx.logger.log('[UI] - /app/* (Main application)'); - ctx.logger.log('[UI] - /ui/list/:object (List views)'); - ctx.logger.log('[UI] - /ui/form/:object/:id? (Form views)'); - } - - async destroy() { - console.log('[UI] UI Engine stopped'); - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d25acebb4..423648869 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,19 @@ importers: specifier: ^5.3.0 version: 5.9.3 + examples/basic: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../packages/spec + devDependencies: + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.0.0 + version: 5.9.3 + examples/crm: dependencies: '@objectstack/spec': diff --git a/test-dataengine-interface.ts b/test-dataengine-interface.ts deleted file mode 100644 index d0334a86e..000000000 --- a/test-dataengine-interface.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Test Script for IDataEngine Interface Compliance - * - * This script validates: - * 1. ObjectQL implements IDataEngine interface - * 2. Data engine service registration works - * 3. IDataEngine methods are callable - */ - -import { ObjectKernel } from './packages/runtime/src/mini-kernel.js'; -import { ObjectQLPlugin } from './packages/runtime/src/objectql-plugin.js'; -import { DriverPlugin } from './packages/runtime/src/driver-plugin.js'; -import { ObjectQL } from './packages/objectql/src/index.js'; -import type { IDataEngine } from './packages/spec/src/system/data-engine.zod.js'; - -// Mock driver for testing -class MockDriver { - name = 'mock-driver'; - version = '1.0.0'; - - async connect() { - console.log('[MockDriver] Connected'); - } - - async disconnect() { - console.log('[MockDriver] Disconnected'); - } - - async find(object: string, query: any) { - console.log(`[MockDriver] find(${object})`); - return [{ id: '1', name: 'Test Record' }]; - } - - async findOne(object: string, query: any) { - console.log(`[MockDriver] findOne(${object})`); - return { id: '1', name: 'Test Record' }; - } - - async create(object: string, data: any) { - console.log(`[MockDriver] create(${object})`, data); - return { id: '123', ...data }; - } - - async update(object: string, id: any, data: any) { - console.log(`[MockDriver] update(${object}, ${id})`, data); - return { id, ...data }; - } - - async delete(object: string, id: any) { - console.log(`[MockDriver] delete(${object}, ${id})`); - return true; // Return boolean as per DriverInterface - } -} - -async function testDataEngineService() { - console.log('\n=== Test 1: IDataEngine Service Registration ==='); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin()); - kernel.use(new DriverPlugin(new MockDriver() as any, 'mock')); - - await kernel.bootstrap(); - - // Verify data-engine service is registered - try { - const engine = kernel.getService('data-engine'); - console.log('โœ… data-engine service registered'); - console.log('Service type:', engine.constructor.name); - } catch (e: any) { - throw new Error(`FAILED: data-engine service not found: ${e.message}`); - } - - // Verify objectql service is still available (backward compatibility) - try { - const ql = kernel.getService('objectql'); - console.log('โœ… objectql service still available (backward compatibility)'); - } catch (e: any) { - throw new Error(`FAILED: objectql service not found: ${e.message}`); - } -} - -async function testDataEngineInterface() { - console.log('\n=== Test 2: IDataEngine Interface Methods ==='); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin()); - kernel.use(new DriverPlugin(new MockDriver() as any, 'mock')); - - await kernel.bootstrap(); - - const engine = kernel.getService('data-engine'); - - // Test insert - console.log('\nTesting insert...'); - const created = await engine.insert('test_object', { name: 'John Doe', email: 'john@example.com' }); - console.log('โœ… insert() returned:', created); - - // Test find with QueryOptions - console.log('\nTesting find with QueryOptions...'); - const results = await engine.find('test_object', { - filter: { status: 'active' }, - limit: 10, - sort: { createdAt: -1 } - }); - console.log('โœ… find() returned:', results.length, 'records'); - - // Test find without query (all records) - console.log('\nTesting find without query...'); - const allResults = await engine.find('test_object'); - console.log('โœ… find() without query returned:', allResults.length, 'records'); - - // Test update - console.log('\nTesting update...'); - const updated = await engine.update('test_object', '123', { name: 'Jane Doe' }); - console.log('โœ… update() returned:', updated); - - // Test delete - console.log('\nTesting delete...'); - const deleted = await engine.delete('test_object', '123'); - console.log('โœ… delete() returned boolean:', deleted === true || deleted === false); - - if (typeof deleted !== 'boolean') { - throw new Error(`FAILED: delete() should return boolean, got ${typeof deleted}`); - } -} - -async function testBackwardCompatibility() { - console.log('\n=== Test 3: Backward Compatibility ==='); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin()); - kernel.use(new DriverPlugin(new MockDriver() as any, 'mock')); - - await kernel.bootstrap(); - - // Both services should point to the same instance - const engine = kernel.getService('data-engine'); - const ql = kernel.getService('objectql'); - - if (engine !== ql) { - throw new Error('FAILED: data-engine and objectql should be the same instance'); - } - - console.log('โœ… data-engine and objectql services are the same instance'); -} - -async function runAllTests() { - console.log('๐Ÿงช Starting IDataEngine Interface Tests...\n'); - - try { - await testDataEngineService(); - await testDataEngineInterface(); - await testBackwardCompatibility(); - - console.log('\nโœ… All tests passed!\n'); - } catch (error) { - console.error('\nโŒ Test failed:', error); - process.exit(1); - } -} - -runAllTests(); diff --git a/test-micro-kernel.ts b/test-micro-kernel.ts deleted file mode 100644 index 1151e5092..000000000 --- a/test-micro-kernel.ts +++ /dev/null @@ -1,284 +0,0 @@ -/** - * MicroKernel Test Suite - * - * Tests the new ObjectKernel (MicroKernel) architecture: - * 1. Basic plugin registration and lifecycle - * 2. Service registry (registerService/getService) - * 3. Dependency resolution - * 4. Hook/event system - * 5. ObjectQL as a plugin - * 6. Multiple plugins working together - */ - -import { ObjectKernel, DriverPlugin, Plugin, PluginContext } from './packages/runtime/src'; -import { ObjectQLPlugin } from './packages/objectql/src'; - -// Test 1: Basic Plugin Lifecycle -async function testBasicLifecycle() { - console.log('\n=== Test 1: Basic Plugin Lifecycle ==='); - - const events: string[] = []; - - class TestPlugin implements Plugin { - name = 'test-plugin'; - - async init(ctx: PluginContext) { - events.push('init'); - } - - async start(ctx: PluginContext) { - events.push('start'); - } - - async destroy() { - events.push('destroy'); - } - } - - const kernel = new ObjectKernel(); - kernel.use(new TestPlugin()); - await kernel.bootstrap(); - await kernel.shutdown(); - - const expected = ['init', 'start', 'destroy']; - if (JSON.stringify(events) !== JSON.stringify(expected)) { - throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(events)}`); - } - - console.log('โœ… Plugin lifecycle works correctly'); -} - -// Test 2: Service Registry -async function testServiceRegistry() { - console.log('\n=== Test 2: Service Registry ==='); - - class ServicePlugin implements Plugin { - name = 'service-plugin'; - - async init(ctx: PluginContext) { - ctx.registerService('test-service', { value: 42 }); - } - } - - const kernel = new ObjectKernel(); - kernel.use(new ServicePlugin()); - await kernel.bootstrap(); - - const service = kernel.getService('test-service'); - if (service.value !== 42) { - throw new Error('Service not registered correctly'); - } - - await kernel.shutdown(); - console.log('โœ… Service registry works correctly'); -} - -// Test 3: Dependency Resolution -async function testDependencyResolution() { - console.log('\n=== Test 3: Dependency Resolution ==='); - - const initOrder: string[] = []; - - class PluginA implements Plugin { - name = 'plugin-a'; - async init(ctx: PluginContext) { - initOrder.push('A'); - } - } - - class PluginB implements Plugin { - name = 'plugin-b'; - dependencies = ['plugin-a']; - async init(ctx: PluginContext) { - initOrder.push('B'); - } - } - - class PluginC implements Plugin { - name = 'plugin-c'; - dependencies = ['plugin-b']; - async init(ctx: PluginContext) { - initOrder.push('C'); - } - } - - const kernel = new ObjectKernel(); - // Register in reverse order to test dependency resolution - kernel.use(new PluginC()); - kernel.use(new PluginB()); - kernel.use(new PluginA()); - - await kernel.bootstrap(); - - if (JSON.stringify(initOrder) !== JSON.stringify(['A', 'B', 'C'])) { - throw new Error(`Expected ['A', 'B', 'C'], got ${JSON.stringify(initOrder)}`); - } - - await kernel.shutdown(); - console.log('โœ… Dependency resolution works correctly'); -} - -// Test 4: Hook System -async function testHookSystem() { - console.log('\n=== Test 4: Hook System ==='); - - const hookCalls: string[] = []; - - class HookPlugin implements Plugin { - name = 'hook-plugin'; - - async init(ctx: PluginContext) { - ctx.hook('test-event', () => { - hookCalls.push('hook1'); - }); - ctx.hook('test-event', () => { - hookCalls.push('hook2'); - }); - } - - async start(ctx: PluginContext) { - await ctx.trigger('test-event'); - } - } - - const kernel = new ObjectKernel(); - kernel.use(new HookPlugin()); - await kernel.bootstrap(); - - if (JSON.stringify(hookCalls) !== JSON.stringify(['hook1', 'hook2'])) { - throw new Error(`Expected ['hook1', 'hook2'], got ${JSON.stringify(hookCalls)}`); - } - - await kernel.shutdown(); - console.log('โœ… Hook system works correctly'); -} - -// Test 5: ObjectQL as Plugin -async function testObjectQLPlugin() { - console.log('\n=== Test 5: ObjectQL as Plugin ==='); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin()); - await kernel.bootstrap(); - - const objectql = kernel.getService('objectql'); - if (!objectql) { - throw new Error('ObjectQL service not registered'); - } - - await kernel.shutdown(); - console.log('โœ… ObjectQL plugin works correctly'); -} - -// Test 6: Multiple Plugins -async function testMultiplePlugins() { - console.log('\n=== Test 6: Multiple Plugins with Dependencies ==='); - - // Mock driver - const mockDriver = { - name: 'mock-driver', - registerDriver(driver: any) { - // Mock implementation - }, - }; - - class DataPlugin implements Plugin { - name = 'data-plugin'; - - async init(ctx: PluginContext) { - ctx.registerService('data', { query: () => 'data' }); - } - } - - class ApiPlugin implements Plugin { - name = 'api-plugin'; - dependencies = ['data-plugin']; - - async init(ctx: PluginContext) { - const data = ctx.getService('data'); - ctx.registerService('api', { getData: () => data.query() }); - } - } - - const kernel = new ObjectKernel(); - kernel.use(new ApiPlugin()); - kernel.use(new DataPlugin()); - await kernel.bootstrap(); - - const api = kernel.getService('api'); - if (api.getData() !== 'data') { - throw new Error('Plugin dependencies not working'); - } - - await kernel.shutdown(); - console.log('โœ… Multiple plugins with dependencies work correctly'); -} - -// Test 7: Error Handling -async function testErrorHandling() { - console.log('\n=== Test 7: Error Handling ==='); - - // Test duplicate service registration - try { - class DuplicatePlugin implements Plugin { - name = 'dup-plugin'; - async init(ctx: PluginContext) { - ctx.registerService('dup', {}); - ctx.registerService('dup', {}); // Should throw - } - } - - const kernel = new ObjectKernel(); - kernel.use(new DuplicatePlugin()); - await kernel.bootstrap(); - throw new Error('Should have thrown on duplicate service'); - } catch (e: unknown) { - const error = e as Error; - if (!error.message.includes('already registered')) { - throw new Error('Wrong error message'); - } - } - - // Test missing dependency - try { - class MissingDepPlugin implements Plugin { - name = 'missing-dep'; - dependencies = ['non-existent']; - async init(ctx: PluginContext) {} - } - - const kernel = new ObjectKernel(); - kernel.use(new MissingDepPlugin()); - await kernel.bootstrap(); - throw new Error('Should have thrown on missing dependency'); - } catch (e: unknown) { - const error = e as Error; - if (!error.message.includes('not found')) { - throw new Error('Wrong error message'); - } - } - - console.log('โœ… Error handling works correctly'); -} - -// Run all tests -async function runAllTests() { - console.log('๐Ÿงช Starting MicroKernel Test Suite...\n'); - - try { - await testBasicLifecycle(); - await testServiceRegistry(); - await testDependencyResolution(); - await testHookSystem(); - await testObjectQLPlugin(); - await testMultiplePlugins(); - await testErrorHandling(); - - console.log('\nโœ… All MicroKernel tests passed!\n'); - } catch (error) { - console.error('\nโŒ Test failed:', error); - process.exit(1); - } -} - -runAllTests(); diff --git a/test-objectql-plugin.ts b/test-objectql-plugin.ts deleted file mode 100644 index 9ce00ff96..000000000 --- a/test-objectql-plugin.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Validation Script for ObjectQL Plugin - * - * This script validates: - * 1. Plugin-based ObjectQL registration works - * 2. Custom ObjectQL instance works - * 3. Multiple plugins with ObjectQL work - */ - -import { ObjectKernel } from './packages/runtime/src/index.js'; -import { ObjectQLPlugin, ObjectQL, SchemaRegistry } from './packages/objectql/src/index.js'; - -async function testPluginBasedRegistration() { - console.log('\n=== Test 1: Plugin-based ObjectQL Registration ==='); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin()); - - await kernel.bootstrap(); - - // Verify ObjectQL is available as a service - try { - const ql = kernel.getService('objectql'); - console.log('โœ… ObjectQL registered via plugin'); - console.log('ObjectQL instance:', ql.constructor.name); - } catch (e) { - throw new Error('FAILED: ObjectQL service not found'); - } -} - -async function testMissingObjectQL() { - console.log('\n=== Test 2: Missing ObjectQL Service ==='); - - const kernel = new ObjectKernel(); - - await kernel.bootstrap(); - - // Verify ObjectQL throws error when not registered - try { - kernel.getService('objectql'); - throw new Error('FAILED: Should have thrown error for missing ObjectQL'); - } catch (e: any) { - if (e.message.includes('not found')) { - console.log('โœ… Correctly throws error when ObjectQL service is not registered'); - } else { - throw e; - } - } -} - -async function testCustomObjectQL() { - console.log('\n=== Test 3: Custom ObjectQL Instance ==='); - - // Create a WeakMap to track custom instances in a type-safe way - const customInstances = new WeakMap(); - - const customQL = new ObjectQL({ - env: 'test', - customProperty: 'test-value' - }); - - // Mark this as a custom instance - customInstances.set(customQL, 'custom-instance'); - - const kernel = new ObjectKernel(); - kernel.use(new ObjectQLPlugin(customQL)); - - await kernel.bootstrap(); - - // Verify the custom instance is used - const ql = kernel.getService('objectql'); - - if (!customInstances.has(ql)) { - throw new Error('FAILED: Custom ObjectQL instance not used'); - } - - const marker = customInstances.get(ql); - if (marker !== 'custom-instance') { - throw new Error('FAILED: Custom ObjectQL instance marker mismatch'); - } - - console.log('โœ… Custom ObjectQL instance registered correctly'); - console.log('Custom marker:', marker); -} - -async function testMultiplePlugins() { - console.log('\n=== Test 4: Multiple Plugins with ObjectQL ==='); - - // Mock plugin - const mockPlugin = { - name: 'mock-plugin', - async init(ctx: any) { - console.log(' Mock plugin initialized'); - } - }; - - const kernel = new ObjectKernel(); - kernel - .use(new ObjectQLPlugin()) - .use(mockPlugin); - - await kernel.bootstrap(); - - const ql = kernel.getService('objectql'); - if (!ql) { - throw new Error('FAILED: ObjectQL not set'); - } - - console.log('โœ… Multiple plugins work correctly with ObjectQLPlugin'); -} - -async function runAllTests() { - console.log('๐Ÿงช Starting ObjectQL Plugin Validation Tests...\n'); - - try { - await testPluginBasedRegistration(); - await testMissingObjectQL(); - await testCustomObjectQL(); - await testMultiplePlugins(); - - console.log('\nโœ… All tests passed!\n'); - } catch (error) { - console.error('\nโŒ Test failed:', error); - process.exit(1); - } -} - -runAllTests();