From 330e28274326396a43698552b173a75d2d68a0be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:48:07 +0000 Subject: [PATCH 01/14] Initial plan From 99de3bb3e65ed0cc49e3d99ce9098a476666b4ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:53:44 +0000 Subject: [PATCH 02/14] Add basic protocol examples and cleanup outdated files Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/README.md | 120 +++++ examples/basic/README.md | 101 ++++ examples/basic/ai-rag-example.ts | 393 +++++++++++++++ examples/basic/api-discovery-example.ts | 453 +++++++++++++++++ examples/{ => basic}/capabilities-example.ts | 0 examples/basic/stack-definition-example.ts | 499 +++++++++++++++++++ examples/complete-event-driven-example.ts | 156 ------ examples/config-loader.ts | 123 ----- examples/custom-objectql-example.ts | 49 -- examples/data-engine-plugin.ts | 92 ---- examples/flow-engine-plugin.ts | 68 --- examples/mini-kernel-example.ts | 76 --- examples/objectstack.config.json | 58 --- examples/ui-engine-plugin.ts | 108 ---- test-dataengine-interface.ts | 162 ------ test-micro-kernel.ts | 284 ----------- test-objectql-plugin.ts | 128 ----- 17 files changed, 1566 insertions(+), 1304 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/basic/README.md create mode 100644 examples/basic/ai-rag-example.ts create mode 100644 examples/basic/api-discovery-example.ts rename examples/{ => basic}/capabilities-example.ts (100%) create mode 100644 examples/basic/stack-definition-example.ts delete mode 100644 examples/complete-event-driven-example.ts delete mode 100644 examples/config-loader.ts delete mode 100644 examples/custom-objectql-example.ts delete mode 100644 examples/data-engine-plugin.ts delete mode 100644 examples/flow-engine-plugin.ts delete mode 100644 examples/mini-kernel-example.ts delete mode 100644 examples/objectstack.config.json delete mode 100644 examples/ui-engine-plugin.ts delete mode 100644 test-dataengine-interface.ts delete mode 100644 test-micro-kernel.ts delete mode 100644 test-objectql-plugin.ts diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..39a48de58 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,120 @@ +# ObjectStack Examples + +This directory contains examples demonstrating the ObjectStack Protocol and its ecosystem. + +## šŸ“š Directory Structure + +### šŸŽ“ Learning Path + +**Start Here:** +1. **[basic/](./basic/)** - Simple protocol examples + - Stack definition with `defineStack()` + - Basic Object and Field definitions + - Capabilities configuration + +2. **[todo/](./todo/)** - Minimal task management app + - Simple object definition + - Basic configuration + +3. **[crm/](./crm/)** - Full-featured CRM application + - All field types and features + - Workflows, validations, and permissions + - UI components, dashboards, and reports + +### šŸ¤– AI & Intelligence + +**AI Protocol Examples:** +- **[ai-sales/](./ai-sales/)** - AI-powered sales assistant +- **[ai-analyst/](./ai-analyst/)** - AI data analyst +- **[ai-codegen/](./ai-codegen/)** - AI code generation +- **[ai-support/](./ai-support/)** - AI customer support + +### šŸ”§ Integration & Plugins + +**Runtime Integration:** +- **[msw-react-crud/](./msw-react-crud/)** - React CRUD with Mock Service Worker +- **[plugin-bi/](./plugin-bi/)** - Business Intelligence plugin +- **[host/](./host/)** - Plugin host environment + +## šŸš€ Quick Start + +### Run an Example + +```bash +# Install dependencies (from monorepo root) +pnpm install + +# Build an example +pnpm --filter @objectstack/example-todo build + +# Run type checking +pnpm --filter @objectstack/example-todo typecheck +``` + +### Example Structure + +Each example follows this structure: + +``` +example-name/ +ā”œā”€ā”€ src/ +│ ā”œā”€ā”€ domains/ # Object definitions +│ ā”œā”€ā”€ ui/ # UI configurations (optional) +│ └── server/ # Server setup (optional) +ā”œā”€ā”€ objectstack.config.ts # Stack definition +ā”œā”€ā”€ package.json +ā”œā”€ā”€ README.md +└── tsconfig.json +``` + +## šŸ“– Protocol Coverage + +### Data Protocol (ObjectQL) +- āœ… **Objects & Fields** - [crm](./crm/), [todo](./todo/) +- āœ… **Validation Rules** - [crm](./crm/) +- āœ… **Workflows** - [crm](./crm/) +- āœ… **Hooks** - [crm](./crm/) + +### UI Protocol (ObjectUI) +- āœ… **Apps & Navigation** - [crm](./crm/) +- āœ… **Views** (Grid, Kanban, Calendar, Gantt) - [crm](./crm/) +- āœ… **Dashboards** - [crm](./crm/) +- āœ… **Reports** - [crm](./crm/) +- āœ… **Actions** - [crm](./crm/) + +### AI Protocol +- āœ… **Agents** - [ai-sales](./ai-sales/), [ai-analyst](./ai-analyst/) +- āœ… **RAG Pipeline** - [basic/ai-rag-example.ts](./basic/) +- āœ… **Orchestration** - [ai-codegen](./ai-codegen/) + +### System Protocol (ObjectOS) +- āœ… **Manifest** - All examples +- āœ… **Capabilities** - [basic/capabilities-example.ts](./basic/) +- āœ… **Stack Definition** - [basic/stack-definition-example.ts](./basic/) + +### API Protocol +- āœ… **REST API** - [crm](./crm/) +- āœ… **Discovery** - [basic/api-discovery-example.ts](./basic/) + +### Auth Protocol +- ā³ **Identity & Policy** - Coming soon +- ā³ **RBAC** - Coming soon + +### Automation Protocol +- āœ… **Workflows** - [crm](./crm/) +- āœ… **Flows** - [crm](./crm/) +- ā³ **Approvals** - Coming soon + +### Hub Protocol +- ā³ **Marketplace** - Coming soon +- ā³ **Multi-tenancy** - Coming soon + +## šŸ”— Related Resources + +- [ObjectStack Documentation](../content/docs/) +- [Protocol Reference](../packages/spec/) +- [Contributing Guide](../CONTRIBUTING.md) + +## šŸ“ License + +Apache 2.0 Ā© ObjectStack diff --git a/examples/basic/README.md b/examples/basic/README.md new file mode 100644 index 000000000..3415c9171 --- /dev/null +++ b/examples/basic/README.md @@ -0,0 +1,101 @@ +# Basic Protocol Examples + +This directory contains standalone examples demonstrating core ObjectStack protocols and features. + +## šŸ“š 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 + +## šŸŽÆ Usage + +These examples are TypeScript files that can be: + +1. **Imported as references:** + ```typescript + import { taskManagementStack } from './examples/basic/stack-definition-example'; + ``` + +2. **Run directly (if configured):** + ```bash + tsx examples/basic/stack-definition-example.ts + ``` + +3. **Used as templates:** + Copy and modify for 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..be47eaaec --- /dev/null +++ b/examples/basic/ai-rag-example.ts @@ -0,0 +1,393 @@ +/** + * 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 { + RAGPipeline, + RAGDocument, + RAGQuery, + RAGResult, + Agent, +} from '@objectstack/spec'; + +/** + * Example 1: RAG Pipeline Configuration + * + * Complete RAG pipeline setup for a knowledge base system + */ +export const knowledgeBaseRAG: RAGPipeline = { + name: 'knowledge_base_rag', + description: 'RAG pipeline for company knowledge base', + + // Document Processing + documentProcessing: { + // Chunking strategy + chunkSize: 1000, + chunkOverlap: 200, + chunkingStrategy: 'recursive', + + // Text cleaning + preprocessing: { + removeHtml: true, + normalizeWhitespace: true, + removePunctuation: false, + }, + }, + + // Embedding Configuration + embedding: { + model: 'text-embedding-ada-002', + provider: 'openai', + dimensions: 1536, + + // Batching for performance + batchSize: 100, + maxRetries: 3, + }, + + // Vector Store + vectorStore: { + type: 'pinecone', + index: 'knowledge-base', + namespace: 'production', + + // Metadata filtering + metadataFields: ['category', 'author', 'created_at', 'source'], + }, + + // Retrieval Configuration + retrieval: { + // How many chunks to retrieve + topK: 5, + + // Similarity threshold (0-1) + similarityThreshold: 0.7, + + // Reranking for better results + reranking: { + enabled: true, + model: 'cohere-rerank', + topN: 3, + }, + + // Hybrid search (combine vector + keyword) + hybridSearch: { + enabled: true, + alpha: 0.7, // 0 = pure keyword, 1 = pure vector + }, + }, + + // Context Assembly + contextAssembly: { + // How to combine retrieved chunks + strategy: 'concatenate', + + // Maximum context length + maxTokens: 3000, + + // Include metadata in context + includeMetadata: true, + + // Template for formatting context + template: `Context from knowledge base: + +{{#each chunks}} +[Source: {{metadata.source}}] +{{content}} + +{{/each}}`, + }, +}; + +/** + * Example 2: Document Ingestion + * + * How to index documents into the RAG pipeline + */ +export const sampleDocuments: RAGDocument[] = [ + { + id: 'doc_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: { + source: 'Product Documentation', + category: 'Architecture', + author: 'Technical Team', + created_at: '2024-01-15', + url: 'https://docs.objectstack.dev/architecture', + }, + }, + { + id: 'doc_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: { + source: 'Developer Guide', + category: 'Tutorial', + author: 'DevRel Team', + created_at: '2024-01-20', + url: 'https://docs.objectstack.dev/guides/objects', + }, + }, + { + id: 'doc_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: { + source: 'AI Documentation', + category: 'AI Features', + author: 'AI Team', + created_at: '2024-02-01', + url: 'https://docs.objectstack.dev/ai/bridge', + }, + }, +]; + +/** + * Example 3: RAG Query + * + * Performing a RAG query with filters and options + */ +export const sampleQueries: RAGQuery[] = [ + { + // Simple question + query: 'What is ObjectStack?', + topK: 5, + }, + { + // Question with metadata filtering + query: 'How do I create objects?', + topK: 3, + filter: { + category: 'Tutorial', + }, + }, + { + // Advanced query with reranking + query: 'Tell me about AI integration', + topK: 10, + rerank: true, + rerankTopN: 3, + filter: { + category: 'AI Features', + }, + }, +]; + +/** + * Example 4: RAG Results + * + * What the pipeline returns + */ +export const sampleResults: RAGResult = { + query: 'What is ObjectStack?', + + // Retrieved chunks + chunks: [ + { + id: 'chunk_001', + documentId: 'doc_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.`, + + score: 0.92, + + metadata: { + source: 'Product Documentation', + category: 'Architecture', + }, + }, + { + id: 'chunk_002', + documentId: 'doc_001', + content: `The platform supports multiple databases and provides built-in AI capabilities.`, + + score: 0.85, + + metadata: { + source: 'Product Documentation', + category: 'Architecture', + }, + }, + ], + + // Assembled context for LLM + context: `Context from knowledge base: + +[Source: Product Documentation] +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: Product Documentation] +The platform supports multiple databases and provides built-in AI capabilities.`, + + // Metadata + retrievalTime: 150, // milliseconds + totalChunks: 2, + averageScore: 0.885, +}; + +/** + * Example 5: AI Agent with RAG + * + * Integrating RAG into an AI agent + */ +export const ragEnabledAgent: Agent = { + name: 'documentation_assistant', + type: 'conversational', + label: 'Documentation Assistant', + description: 'AI assistant powered by RAG for answering questions about ObjectStack', + + // Agent capabilities + capabilities: { + objectAccess: [], + canCreate: false, + canUpdate: false, + canAnalyze: true, + }, + + // System prompt with RAG instructions + systemPrompt: `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 + rag: { + pipeline: 'knowledge_base_rag', + enabled: true, + + // When to trigger RAG + trigger: 'always', // or 'auto', 'manual' + + // Number of chunks to retrieve + topK: 5, + + // Include source attribution + includeSources: true, + }, + + // Model configuration + model: { + provider: 'openai', + model: 'gpt-4-turbo', + temperature: 0.7, + maxTokens: 2000, + }, + + // Tools (optional - for function calling) + tools: [ + { + name: 'search_documentation', + description: 'Search the ObjectStack documentation for specific topics', + parameters: { + query: 'string', + category: 'string?', + }, + }, + ], +}; + +/** + * 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.chunks.length} relevant chunks`); + console.log(`- Average relevance score: ${sampleResults.averageScore}\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: Product Documentation - 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 +demonstrateRAGUsage(); + +// Export all examples +export default { + knowledgeBaseRAG, + sampleDocuments, + 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..35cff86ba --- /dev/null +++ b/examples/basic/api-discovery-example.ts @@ -0,0 +1,453 @@ +/** + * 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 { + ApiDiscoveryResponse, + ApiCapabilities, +} from '@objectstack/spec'; + +/** + * Example 1: Complete Discovery Response + * + * This is what a client receives when calling /api/discovery + */ +export const fullDiscoveryResponse: ApiDiscoveryResponse = { + // System Identity + system: { + name: 'ObjectStack CRM', + version: '2.1.0', + environment: 'production', + vendor: 'Acme Corporation', + }, + + // Available API Surfaces + endpoints: { + rest: { + baseUrl: 'https://api.example.com/v1', + version: 'v1', + documentation: 'https://api.example.com/docs', + }, + graphql: { + endpoint: 'https://api.example.com/graphql', + introspection: true, + playground: 'https://api.example.com/playground', + }, + odata: { + baseUrl: 'https://api.example.com/odata', + version: '4.0', + }, + realtime: { + websocket: 'wss://api.example.com/ws', + sse: 'https://api.example.com/events', + }, + }, + + // Runtime Capabilities (from capabilities-example.ts) + capabilities: { + data: { + queryFilters: true, + queryAggregations: true, + querySorting: true, + queryPagination: true, + queryWindowFunctions: true, + querySubqueries: true, + queryDistinct: true, + queryHaving: true, + queryJoins: true, + + fullTextSearch: true, + vectorSearch: true, + geoSpatial: true, + + jsonFields: true, + arrayFields: true, + + validationRules: true, + workflows: true, + triggers: true, + formulas: true, + + transactions: true, + bulkOperations: true, + + supportedDrivers: ['postgresql', 'mongodb'], + }, + + ui: { + listView: true, + formView: true, + kanbanView: true, + calendarView: true, + ganttView: true, + + dashboards: true, + reports: true, + charts: true, + + customPages: true, + customThemes: true, + customComponents: true, + + customActions: true, + screenFlows: true, + + mobileOptimized: true, + accessibility: true, + }, + + system: { + version: '2.1.0', + environment: 'production', + + restApi: true, + graphqlApi: true, + odataApi: true, + + websockets: true, + serverSentEvents: true, + eventBus: true, + + webhooks: true, + apiContracts: true, + + authentication: true, + rbac: true, + fieldLevelSecurity: true, + rowLevelSecurity: true, + + multiTenant: true, + + backgroundJobs: true, + auditLogging: true, + fileStorage: true, + + i18n: true, + + pluginSystem: true, + + systemObjects: ['user', 'role', 'permission', 'object', 'field'], + + limits: { + maxObjects: 1000, + maxFieldsPerObject: 500, + maxRecordsPerQuery: 10000, + apiRateLimit: 1000, + fileUploadSizeLimit: 10485760, + }, + }, + }, + + // Authentication Configuration + auth: { + required: true, + methods: ['oauth2', 'apiKey', 'jwt'], + oauth2: { + authorizationUrl: 'https://auth.example.com/oauth/authorize', + tokenUrl: 'https://auth.example.com/oauth/token', + scopes: ['read', 'write', 'admin'], + }, + }, + + // Available Objects (Schema Registry) + objects: [ + { + name: 'account', + label: 'Account', + labelPlural: 'Accounts', + apiEnabled: true, + endpoints: { + list: '/api/v1/objects/account', + get: '/api/v1/objects/account/{id}', + create: '/api/v1/objects/account', + update: '/api/v1/objects/account/{id}', + delete: '/api/v1/objects/account/{id}', + }, + }, + { + name: 'contact', + label: 'Contact', + labelPlural: 'Contacts', + apiEnabled: true, + endpoints: { + list: '/api/v1/objects/contact', + get: '/api/v1/objects/contact/{id}', + create: '/api/v1/objects/contact', + update: '/api/v1/objects/contact/{id}', + delete: '/api/v1/objects/contact/{id}', + }, + }, + ], + + // Feature Flags + features: { + aiAssistant: true, + advancedAnalytics: true, + customBranding: true, + apiAccess: true, + webhooks: true, + auditLogs: true, + }, + + // Links + links: { + documentation: 'https://docs.example.com', + support: 'https://support.example.com', + status: 'https://status.example.com', + portal: 'https://app.example.com', + }, +}; + +/** + * Example 2: Minimal Discovery Response (Development Mode) + * + * A simplified response for local development + */ +export const devDiscoveryResponse: ApiDiscoveryResponse = { + system: { + name: 'ObjectStack Dev', + version: '0.1.0', + environment: 'development', + }, + + endpoints: { + rest: { + baseUrl: 'http://localhost:3000/api', + version: 'v1', + }, + }, + + capabilities: { + data: { + queryFilters: true, + queryAggregations: true, + querySorting: true, + queryPagination: true, + queryWindowFunctions: false, + querySubqueries: false, + queryDistinct: true, + queryHaving: false, + queryJoins: false, + + fullTextSearch: false, + vectorSearch: false, + geoSpatial: false, + + jsonFields: true, + arrayFields: false, + + validationRules: true, + workflows: false, + triggers: false, + formulas: true, + + transactions: true, + bulkOperations: true, + + supportedDrivers: ['memory', 'sqlite'], + }, + + ui: { + listView: true, + formView: true, + kanbanView: false, + calendarView: false, + ganttView: false, + + dashboards: true, + reports: true, + charts: true, + + customPages: true, + customThemes: false, + customComponents: false, + + customActions: true, + screenFlows: false, + + mobileOptimized: false, + accessibility: false, + }, + + system: { + version: '0.1.0', + environment: 'development', + + restApi: true, + graphqlApi: false, + odataApi: false, + + websockets: false, + serverSentEvents: false, + eventBus: false, + + webhooks: false, + apiContracts: false, + + authentication: true, + rbac: true, + fieldLevelSecurity: false, + rowLevelSecurity: false, + + multiTenant: false, + + backgroundJobs: false, + auditLogging: false, + fileStorage: true, + + i18n: true, + + pluginSystem: false, + + systemObjects: ['user', 'role', 'object'], + + limits: { + maxObjects: 100, + maxFieldsPerObject: 200, + maxRecordsPerQuery: 1000, + apiRateLimit: 100, + fileUploadSizeLimit: 5242880, + }, + }, + }, + + auth: { + required: false, + }, + + objects: [], + + features: {}, + + links: {}, +}; + +/** + * Example 3: Client Usage - Adaptive Behavior + * + * How a client can use the discovery API to adapt its behavior + */ +export class AdaptiveClient { + private capabilities: ApiCapabilities | null = null; + + async initialize(baseUrl: string) { + // Fetch discovery information + const response = await fetch(`${baseUrl}/api/discovery`); + const discovery: ApiDiscoveryResponse = await response.json(); + + this.capabilities = discovery.capabilities; + + console.log(`Connected to: ${discovery.system.name} v${discovery.system.version}`); + console.log(`Environment: ${discovery.system.environment}`); + } + + /** + * Check if a specific feature is available + */ + hasFeature(subsystem: 'data' | 'ui' | 'system', feature: string): boolean { + if (!this.capabilities) return false; + + const subsystemCaps = this.capabilities[subsystem] as any; + return subsystemCaps?.[feature] === true; + } + + /** + * Build query based on available capabilities + */ + buildQuery(object: string, options: any) { + const query: any = { object }; + + // Only use subqueries if supported + if (this.hasFeature('data', 'querySubqueries') && options.includeRelated) { + query.subqueries = options.includeRelated; + } + + // Only use window functions if supported + if (this.hasFeature('data', 'queryWindowFunctions') && options.ranking) { + query.windowFunctions = options.ranking; + } + + // Basic filtering is usually always available + if (options.filter) { + query.filter = options.filter; + } + + return query; + } + + /** + * Choose the best available API endpoint + */ + getApiEndpoint(discovery: ApiDiscoveryResponse, preference: 'rest' | 'graphql' | 'odata' = 'rest') { + const endpoints = discovery.endpoints; + + // Try preferred endpoint first + if (preference === 'graphql' && endpoints.graphql) { + return { type: 'graphql', url: endpoints.graphql.endpoint }; + } + + if (preference === 'odata' && endpoints.odata) { + return { type: 'odata', url: endpoints.odata.baseUrl }; + } + + // Fallback to REST (usually always available) + if (endpoints.rest) { + return { type: 'rest', url: endpoints.rest.baseUrl }; + } + + 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: ApiDiscoveryResponse): string { + const { system, capabilities, objects } = discovery; + + const prompt = `You are an AI assistant for ${system.name} (v${system.version}). + +SYSTEM CAPABILITIES: +${capabilities.data.vectorSearch ? '- Vector search available (can use RAG)' : ''} +${capabilities.data.fullTextSearch ? '- Full-text search available' : ''} +${capabilities.ui.dashboards ? '- Can create and manage dashboards' : ''} +${capabilities.system.webhooks ? '- Can configure webhooks' : ''} + +AVAILABLE OBJECTS: +${objects.map(obj => `- ${obj.label} (${obj.name})`).join('\n')} + +RATE LIMITS: +- API Rate Limit: ${capabilities.system.limits?.apiRateLimit || 'N/A'} requests/minute +- Max Records per Query: ${capabilities.system.limits?.maxRecordsPerQuery || 'N/A'} + +When helping users, respect these capabilities and limits.`; + + return prompt; +} + +// ============================================================================ +// Usage Examples +// ============================================================================ + +// Example: Initialize adaptive client +const client = new AdaptiveClient(); +// await client.initialize('https://api.example.com'); + +// Example: Check features before using them +if (client.hasFeature('data', 'vectorSearch')) { + console.log('āœ… Vector search is available - AI/RAG features enabled'); +} + +// Example: Generate AI prompt +const systemPrompt = generateSystemPromptFromDiscovery(fullDiscoveryResponse); +console.log('AI System Prompt:\n', systemPrompt); diff --git a/examples/capabilities-example.ts b/examples/basic/capabilities-example.ts similarity index 100% rename from examples/capabilities-example.ts rename to examples/basic/capabilities-example.ts diff --git a/examples/basic/stack-definition-example.ts b/examples/basic/stack-definition-example.ts new file mode 100644 index 000000000..9f4df343b --- /dev/null +++ b/examples/basic/stack-definition-example.ts @@ -0,0 +1,499 @@ +/** + * 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: { + 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: { + name: 'task-manager', + version: '1.0.0', + description: 'Task management application with ObjectStack', + author: 'Your Name', + license: 'MIT', + }, + + // ============================================================================ + // Data Layer: Objects + // ============================================================================ + objects: [ + { + name: 'task', + label: 'Task', + labelPlural: 'Tasks', + icon: 'check-square', + description: 'Work items and to-dos', + nameField: 'subject', + + enable: { + apiEnabled: true, + trackHistory: true, + }, + + fields: [ + { + name: 'subject', + type: 'text', + label: 'Subject', + description: 'Task title', + required: true, + maxLength: 255, + }, + { + name: 'description', + type: 'textarea', + label: 'Description', + description: 'Detailed description', + maxLength: 5000, + }, + { + name: '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', + }, + { + name: 'priority', + type: 'select', + label: 'Priority', + options: [ + { value: 'low', label: 'Low' }, + { value: 'medium', label: 'Medium' }, + { value: 'high', label: 'High' }, + ], + defaultValue: 'medium', + }, + { + name: 'due_date', + type: 'date', + label: 'Due Date', + }, + { + name: 'assigned_to', + type: 'lookup', + label: 'Assigned To', + reference: { + object: 'user', + field: 'id', + }, + }, + ], + }, + ], + + // ============================================================================ + // 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: [ + { + name: 'my_tasks', + object: 'task', + type: 'list', + label: 'My Tasks', + listType: 'grid', + fields: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], + filter: { + operator: 'AND', + conditions: [ + { + field: 'assigned_to', + operator: 'equals', + value: '$CurrentUser.id', + }, + ], + }, + sort: [ + { field: 'priority', direction: 'desc' }, + { field: 'due_date', direction: 'asc' }, + ], + }, + ], + + dashboards: [ + { + name: 'task_overview', + label: 'Task Overview', + description: 'Task statistics and metrics', + layout: { + rows: 2, + columns: 2, + }, + widgets: [ + { + type: 'metric', + position: { row: 0, col: 0 }, + title: 'Total Tasks', + dataSource: { + type: 'aggregation', + object: 'task', + aggregation: 'count', + }, + }, + { + type: 'chart', + position: { row: 0, col: 1 }, + title: 'Tasks by Status', + chartType: 'pie', + dataSource: { + type: 'groupBy', + object: 'task', + groupBy: 'status', + aggregation: 'count', + }, + }, + ], + }, + ], + + // ============================================================================ + // Automation Layer: Workflows + // ============================================================================ + workflows: [ + { + name: 'notify_overdue_tasks', + object: 'task', + description: 'Notify users of overdue tasks', + trigger: { + type: 'scheduled', + schedule: '0 9 * * *', // Daily at 9 AM + }, + criteria: { + operator: 'AND', + conditions: [ + { + field: 'status', + operator: 'notEquals', + value: 'done', + }, + { + field: 'due_date', + operator: 'lessThan', + value: '$Today', + }, + ], + }, + actions: [ + { + type: 'emailAlert', + recipient: 'assigned_to', + template: 'overdue_task_notification', + }, + ], + }, + ], + + // ============================================================================ + // Security Layer: Roles & Permissions + // ============================================================================ + roles: [ + { + name: 'task_manager', + label: 'Task Manager', + description: 'Can manage all tasks', + permissions: { + task: { + create: true, + read: true, + update: true, + delete: true, + }, + }, + }, + { + name: 'task_user', + label: 'Task User', + description: 'Can manage own tasks', + permissions: { + task: { + create: true, + read: true, + update: { + condition: { + field: 'assigned_to', + operator: 'equals', + value: '$CurrentUser.id', + }, + }, + delete: false, + }, + }, + }, + ], +}); + +/** + * Example 3: CRM Stack with AI Agent + * + * A CRM system with an AI sales assistant. + */ +export const crmWithAIStack = defineStack({ + manifest: { + name: 'ai-crm', + version: '1.0.0', + description: 'CRM with AI-powered sales assistant', + }, + + // Objects + objects: [ + { + name: 'account', + label: 'Account', + labelPlural: 'Accounts', + icon: 'building', + nameField: 'name', + enable: { + apiEnabled: true, + trackHistory: true, + }, + fields: [ + { + name: 'name', + type: 'text', + label: 'Account Name', + required: true, + }, + { + name: 'industry', + type: 'select', + label: 'Industry', + options: [ + { value: 'technology', label: 'Technology' }, + { value: 'finance', label: 'Finance' }, + { value: 'healthcare', label: 'Healthcare' }, + ], + }, + { + name: 'annual_revenue', + type: 'currency', + label: 'Annual Revenue', + }, + ], + }, + { + name: 'opportunity', + label: 'Opportunity', + labelPlural: 'Opportunities', + icon: 'target', + nameField: 'name', + enable: { + apiEnabled: true, + trackHistory: true, + }, + fields: [ + { + name: 'name', + type: 'text', + label: 'Opportunity Name', + required: true, + }, + { + name: 'account', + type: 'lookup', + label: 'Account', + reference: { + object: 'account', + field: 'id', + }, + }, + { + name: 'amount', + type: 'currency', + label: 'Amount', + }, + { + name: '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', + }, + { + name: 'close_date', + type: 'date', + label: 'Expected Close Date', + }, + ], + }, + ], + + // AI Agent + agents: [ + { + name: 'sales_assistant', + type: 'conversational', + label: 'Sales Assistant', + description: 'AI assistant for sales operations', + + capabilities: { + objectAccess: ['account', 'opportunity'], + canCreate: true, + canUpdate: true, + canAnalyze: true, + }, + + systemPrompt: `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', + parameters: { + query: 'string', + industry: 'string?', + }, + }, + { + name: 'get_opportunity_pipeline', + description: 'Get pipeline statistics by stage', + }, + { + name: 'create_opportunity', + description: 'Create a new sales opportunity', + parameters: { + name: 'string', + account: 'string', + amount: 'number', + stage: 'string', + }, + }, + ], + }, + ], + + // 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:', taskObject.fields.length); + } + + // Access AI agents + const agent = crmWithAIStack.agents?.[0]; + if (agent) { + console.log('AI Agent:', agent.name); + console.log('Capabilities:', agent.capabilities); + } +} + +// ============================================================================ +// Export for use +// ============================================================================ +export default { + minimalStack, + taskManagementStack, + crmWithAIStack, +}; 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/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/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/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(); From 4439d4baa88eb37839eb43335c095b20eff561ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:56:05 +0000 Subject: [PATCH 03/14] Add auth, permission, and automation protocol examples Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/basic/README.md | 38 ++ examples/basic/auth-permission-example.ts | 566 +++++++++++++++++ examples/basic/automation-example.ts | 718 ++++++++++++++++++++++ 3 files changed, 1322 insertions(+) create mode 100644 examples/basic/auth-permission-example.ts create mode 100644 examples/basic/automation-example.ts diff --git a/examples/basic/README.md b/examples/basic/README.md index 3415c9171..9c49cd171 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -70,6 +70,44 @@ Shows Retrieval-Augmented Generation (RAG) pipeline configuration: - 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 that can be: diff --git a/examples/basic/auth-permission-example.ts b/examples/basic/auth-permission-example.ts new file mode 100644 index 000000000..3f7d93c98 --- /dev/null +++ b/examples/basic/auth-permission-example.ts @@ -0,0 +1,566 @@ +/** + * 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 { + User, + Role, + PermissionSet, + RowLevelSecurity, + SharingRule, + Territory, +} from '@objectstack/spec'; + +/** + * Example 1: User Identity + * + * User accounts with authentication methods + */ +export const sampleUsers: User[] = [ + { + id: 'user_001', + email: 'admin@example.com', + name: 'Admin User', + + // Authentication + authProvider: 'local', + emailVerified: true, + + // Profile + avatar: 'https://example.com/avatars/admin.jpg', + timezone: 'America/New_York', + locale: 'en-US', + + // Status + active: true, + lastLogin: '2024-01-29T10:30:00Z', + + // Roles + roles: ['system_administrator'], + }, + { + id: 'user_002', + email: 'sales@example.com', + name: 'Sales Manager', + + authProvider: 'oauth2', + emailVerified: true, + + avatar: 'https://example.com/avatars/sales.jpg', + timezone: 'America/Los_Angeles', + locale: 'en-US', + + active: true, + lastLogin: '2024-01-29T09:15:00Z', + + roles: ['sales_manager'], + }, + { + id: 'user_003', + email: 'rep@example.com', + name: 'Sales Rep', + + authProvider: 'oauth2', + emailVerified: true, + + timezone: 'America/Chicago', + locale: 'en-US', + + active: true, + lastLogin: '2024-01-29T08:45:00Z', + + roles: ['sales_rep'], + }, +]; + +/** + * Example 2: Role Hierarchy + * + * Organizational role structure with inheritance + */ +export const roleHierarchy: Role[] = [ + // Top-level admin role + { + name: 'system_administrator', + label: 'System Administrator', + description: 'Full system access', + + // No parent = top of hierarchy + parentRole: undefined, + + // Full permissions + permissions: { + manageUsers: true, + manageRoles: true, + manageObjects: true, + manageSystem: true, + viewAllData: true, + modifyAllData: true, + }, + }, + + // Sales hierarchy + { + name: 'sales_manager', + label: 'Sales Manager', + description: 'Manages sales team and data', + + parentRole: 'system_administrator', + + permissions: { + manageUsers: false, + manageRoles: false, + manageObjects: false, + manageSystem: false, + viewAllData: true, // Can view all sales data + modifyAllData: true, // Can modify all sales data + }, + }, + { + name: 'sales_rep', + label: 'Sales Representative', + description: 'Standard sales user', + + parentRole: 'sales_manager', // Inherits from manager + + permissions: { + manageUsers: false, + manageRoles: false, + manageObjects: false, + manageSystem: false, + viewAllData: false, // Can only view own data + modifyAllData: false, // Can only modify own data + }, + }, +]; + +/** + * Example 3: Permission Sets + * + * Granular object-level permissions + */ +export const permissionSets: PermissionSet[] = [ + { + name: 'sales_user_permissions', + label: 'Sales User Permissions', + description: 'Standard permissions for sales users', + + // Object permissions + objectPermissions: [ + { + object: 'account', + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + { + object: 'contact', + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + { + object: 'opportunity', + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + { + object: 'lead', + create: true, + read: true, + update: true, + delete: true, // Can delete own leads + viewAll: false, + modifyAll: false, + }, + ], + + // Field-level permissions (Field-Level Security) + fieldPermissions: [ + { + object: 'account', + field: 'annual_revenue', + read: true, + update: false, // Can see but not edit revenue + }, + { + object: 'opportunity', + field: 'probability', + read: true, + update: false, // Calculated field, read-only + }, + ], + }, + + { + name: 'sales_manager_permissions', + label: 'Sales Manager Permissions', + description: 'Extended permissions for sales managers', + + objectPermissions: [ + { + object: 'account', + create: true, + read: true, + update: true, + delete: true, + viewAll: true, // Can view all accounts + modifyAll: true, // Can modify all accounts + }, + { + object: 'opportunity', + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + { + object: 'forecast', + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + ], + + fieldPermissions: [ + { + object: 'account', + field: 'annual_revenue', + read: true, + update: true, // Managers can edit revenue + }, + { + object: 'opportunity', + field: 'discount_percent', + read: true, + update: true, // Managers can approve discounts + }, + ], + }, +]; + +/** + * Example 4: Row-Level Security (RLS) + * + * Fine-grained data access control based on record ownership + */ +export const rowLevelSecurityRules: RowLevelSecurity[] = [ + { + name: 'opportunity_owner_access', + object: 'opportunity', + description: 'Users can only access their own opportunities', + + // Rule definition + rule: { + operator: 'OR', + conditions: [ + // Can access if owner + { + field: 'owner', + operator: 'equals', + value: '$CurrentUser.id', + }, + // Can access if in their territory + { + field: 'territory', + operator: 'in', + value: '$CurrentUser.territories', + }, + // Managers can access team records + { + field: 'owner.manager', + operator: 'equals', + value: '$CurrentUser.id', + }, + ], + }, + + // Apply to these roles + roles: ['sales_rep'], + + // Operations affected + operations: ['read', 'update', 'delete'], + }, + + { + name: 'account_territory_access', + object: 'account', + description: 'Territory-based account access', + + rule: { + operator: 'AND', + conditions: [ + { + field: 'territory', + operator: 'in', + value: '$CurrentUser.territories', + }, + { + field: 'active', + operator: 'equals', + value: true, + }, + ], + }, + + roles: ['sales_rep'], + operations: ['read', 'update'], + }, +]; + +/** + * Example 5: Sharing Rules + * + * Grant additional access beyond RLS + */ +export const sharingRules: 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: Territory[] = [ + { + name: 'north_america', + label: 'North America', + description: 'North American sales territory', + + // Territory definition + criteria: { + operator: 'OR', + conditions: [ + { + field: 'billing_country', + operator: 'in', + value: ['USA', 'Canada', 'Mexico'], + }, + ], + }, + + // Assigned users + members: ['user_002', 'user_003'], + + // Parent territory (for hierarchy) + parentTerritory: undefined, + }, + + { + name: 'west_coast', + label: 'West Coast', + description: 'US West Coast territory', + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'billing_country', + operator: 'equals', + value: 'USA', + }, + { + field: 'billing_state', + operator: 'in', + value: ['CA', 'OR', 'WA', 'NV', 'AZ'], + }, + ], + }, + + members: ['user_003'], + parentTerritory: 'north_america', + }, +]; + +/** + * Example 7: Permission Checking + * + * Helper functions for checking permissions + */ +export class PermissionChecker { + /** + * Check if user has object permission + */ + hasObjectPermission( + user: User, + object: string, + operation: 'create' | 'read' | 'update' | 'delete' + ): boolean { + // Get user's roles + const userRoles = user.roles || []; + + // Find permission sets for user's roles + const userPermissionSets = permissionSets.filter((ps) => + // In real implementation, map roles to permission sets + true + ); + + // Check object permissions + for (const permSet of userPermissionSets) { + const objPerm = permSet.objectPermissions?.find((op) => op.object === object); + if (objPerm && objPerm[operation]) { + return true; + } + } + + return false; + } + + /** + * Check if user can access a specific record (RLS) + */ + canAccessRecord(user: User, 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) => 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.rule, record, user)) { + return true; + } + } + + return false; + } + + /** + * Evaluate a rule against a record + */ + private evaluateRule(rule: any, record: any, user: User): 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]; // Sales Rep + 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 +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..cc2532cf8 --- /dev/null +++ b/examples/basic/automation-example.ts @@ -0,0 +1,718 @@ +/** + * 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 { + WorkflowRule, + ApprovalProcess, + Flow, + ETLProcess, +} from '@objectstack/spec'; + +/** + * Example 1: Field Update Workflow + * + * Automatically update fields when conditions are met + */ +export const fieldUpdateWorkflows: WorkflowRule[] = [ + { + name: 'set_opportunity_probability', + object: 'opportunity', + description: 'Automatically set probability based on stage', + + // When to trigger + trigger: { + type: 'fieldChange', + fields: ['stage'], + }, + + // Conditions to check + criteria: { + operator: 'ALWAYS', // Run for all records + }, + + // What to do + actions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'probability', + value: { + formula: ` + 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', + object: 'account', + description: 'Update account rating based on revenue', + + trigger: { + type: 'fieldChange', + fields: ['annual_revenue'], + }, + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'annual_revenue', + operator: 'greaterThan', + value: 0, + }, + ], + }, + + actions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'rating', + value: { + formula: ` + 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: WorkflowRule[] = [ + { + name: 'notify_manager_large_opportunity', + object: 'opportunity', + description: 'Notify manager when large opportunity is created', + + trigger: { + type: 'onCreate', + }, + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'amount', + operator: 'greaterThan', + value: 100000, + }, + ], + }, + + actions: [ + { + type: 'emailAlert', + template: 'large_opportunity_alert', + recipients: [ + { + type: 'field', + field: 'owner.manager.email', + }, + { + type: 'role', + role: 'sales_director', + }, + ], + subject: 'Large Opportunity Created: {{name}}', + body: ` + A large opportunity has been created: + + Name: {{name}} + Amount: {{amount | currency}} + Owner: {{owner.name}} + Expected Close: {{close_date | date}} + + Please review and approve. + `, + }, + ], + }, + + { + name: 'notify_overdue_tasks', + object: 'task', + description: 'Daily notification for overdue tasks', + + trigger: { + type: 'scheduled', + schedule: '0 9 * * *', // Daily at 9 AM (cron format) + }, + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'status', + operator: 'notEquals', + value: 'completed', + }, + { + field: 'due_date', + operator: 'lessThan', + value: '$Today', + }, + ], + }, + + actions: [ + { + type: 'emailAlert', + template: 'overdue_task_reminder', + recipients: [ + { + type: 'field', + field: 'assigned_to.email', + }, + ], + subject: 'Overdue Task: {{subject}}', + body: ` + You have an overdue task: + + Subject: {{subject}} + Due Date: {{due_date | date}} + Priority: {{priority}} + + Please update the status or due date. + `, + }, + ], + }, +]; + +/** + * Example 3: Task Creation Workflows + * + * Automatically create related records + */ +export const taskCreationWorkflows: WorkflowRule[] = [ + { + name: 'create_followup_tasks', + object: 'lead', + description: 'Create follow-up tasks when lead is created', + + trigger: { + type: 'onCreate', + }, + + criteria: { + operator: 'AND', + conditions: [ + { + field: 'status', + operator: 'equals', + value: 'new', + }, + ], + }, + + actions: [ + { + type: 'createRecord', + object: 'task', + values: { + subject: 'Initial Contact - {{name}}', + description: 'Make initial contact with lead', + due_date: '$Today + 1', + priority: 'high', + assigned_to: '{{owner}}', + related_to: '{{id}}', + }, + }, + { + type: 'createRecord', + object: 'task', + values: { + subject: 'Qualification Call - {{name}}', + description: 'Schedule qualification call', + due_date: '$Today + 3', + priority: 'medium', + assigned_to: '{{owner}}', + related_to: '{{id}}', + }, + }, + ], + }, +]; + +/** + * Example 4: Approval Process + * + * Multi-step approval workflow for discounts + */ +export const discountApprovalProcess: ApprovalProcess = { + name: 'opportunity_discount_approval', + object: 'opportunity', + description: 'Approval process for opportunity discounts', + + // When to trigger approval + entryCriteria: { + operator: 'AND', + conditions: [ + { + field: 'discount_percent', + operator: 'greaterThan', + value: 0, + }, + ], + }, + + // Approval steps (sequential) + steps: [ + { + name: 'manager_approval', + label: 'Manager Approval', + description: 'Requires manager approval for discounts up to 20%', + + // When this step applies + stepCriteria: { + operator: 'AND', + conditions: [ + { + field: 'discount_percent', + operator: 'lessOrEqual', + value: 20, + }, + ], + }, + + // Who can approve + approvers: [ + { + type: 'field', + field: 'owner.manager', + }, + ], + + // Approval actions + approvalActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'approval_status', + value: 'approved', + }, + ], + }, + ], + + // Rejection actions + rejectionActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'approval_status', + value: 'rejected', + }, + { + field: 'discount_percent', + value: 0, + }, + ], + }, + ], + }, + + { + name: 'director_approval', + label: 'Director Approval', + description: 'Requires director approval for discounts over 20%', + + stepCriteria: { + operator: 'AND', + conditions: [ + { + field: 'discount_percent', + operator: 'greaterThan', + value: 20, + }, + ], + }, + + approvers: [ + { + type: 'role', + role: 'sales_director', + }, + ], + + approvalActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'approval_status', + value: 'approved', + }, + ], + }, + { + type: 'emailAlert', + template: 'discount_approved', + recipients: [ + { + type: 'field', + field: 'owner.email', + }, + ], + }, + ], + + rejectionActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'approval_status', + value: 'rejected', + }, + { + field: 'discount_percent', + value: 0, + }, + ], + }, + { + type: 'emailAlert', + template: 'discount_rejected', + recipients: [ + { + type: 'field', + field: 'owner.email', + }, + ], + }, + ], + }, + ], + + // Final approval actions + finalApprovalActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'approved_discount', + value: '{{discount_percent}}', + }, + ], + }, + ], + + // Final rejection actions + finalRejectionActions: [ + { + type: 'fieldUpdate', + updates: [ + { + field: 'discount_percent', + value: 0, + }, + ], + }, + ], +}; + +/** + * Example 5: Screen Flow + * + * Visual automation with user interaction + */ +export const leadConversionFlow: Flow = { + name: 'lead_conversion', + type: 'screen', + label: 'Convert Lead', + description: 'Guide users through lead conversion process', + + // Entry point + startElement: 'welcome_screen', + + // Flow elements + elements: [ + { + name: 'welcome_screen', + type: 'screen', + label: 'Convert Lead to Opportunity', + + fields: [ + { + name: 'opportunity_name', + type: 'text', + label: 'Opportunity Name', + required: true, + defaultValue: '{{Lead.company}} - {{Lead.product_interest}}', + }, + { + name: 'amount', + type: 'currency', + label: 'Expected Amount', + required: true, + }, + { + name: 'close_date', + type: 'date', + label: 'Expected Close Date', + required: true, + }, + ], + + nextElement: 'create_records', + }, + + { + name: 'create_records', + type: 'recordCreate', + label: 'Create Account and Opportunity', + + creates: [ + { + object: 'account', + values: { + name: '{{Lead.company}}', + phone: '{{Lead.phone}}', + website: '{{Lead.website}}', + }, + assignTo: 'accountId', + }, + { + object: 'contact', + values: { + first_name: '{{Lead.first_name}}', + last_name: '{{Lead.last_name}}', + email: '{{Lead.email}}', + account: '{{accountId}}', + }, + assignTo: 'contactId', + }, + { + object: 'opportunity', + values: { + name: '{{opportunity_name}}', + amount: '{{amount}}', + close_date: '{{close_date}}', + account: '{{accountId}}', + stage: 'qualification', + }, + assignTo: 'opportunityId', + }, + ], + + nextElement: 'update_lead', + }, + + { + name: 'update_lead', + type: 'recordUpdate', + label: 'Mark Lead as Converted', + + object: 'lead', + recordId: '{{Lead.id}}', + values: { + status: 'converted', + converted_account: '{{accountId}}', + converted_opportunity: '{{opportunityId}}', + converted_date: '$Now', + }, + + nextElement: 'success_screen', + }, + + { + name: 'success_screen', + type: 'screen', + label: 'Conversion Successful', + + message: ` + Lead converted successfully! + + Created: + - Account: {{Account.name}} + - Contact: {{Contact.name}} + - Opportunity: {{Opportunity.name}} + `, + + buttons: [ + { + label: 'View Opportunity', + action: 'navigate', + target: '/opportunities/{{opportunityId}}', + }, + { + label: 'Close', + action: 'finish', + }, + ], + }, + ], +}; + +/** + * Example 6: ETL Process + * + * Data integration and transformation + */ +export const dailyLeadImportETL: ETLProcess = { + name: 'daily_lead_import', + description: 'Import leads from external system daily', + + // Schedule + schedule: '0 2 * * *', // Daily at 2 AM + + // Extract + extract: { + source: { + type: 'api', + endpoint: 'https://marketing.example.com/api/leads', + method: 'GET', + authentication: { + type: 'apiKey', + header: 'X-API-Key', + key: '$Env.MARKETING_API_KEY', + }, + }, + + // Incremental load + incrementalField: 'created_at', + lastRunTimestamp: true, + }, + + // Transform + transform: [ + { + type: 'fieldMapping', + mappings: [ + { source: 'firstName', target: 'first_name' }, + { source: 'lastName', target: 'last_name' }, + { source: 'emailAddress', target: 'email' }, + { source: 'phoneNumber', target: 'phone' }, + { source: 'companyName', target: 'company' }, + ], + }, + { + type: 'fieldTransform', + transforms: [ + { + field: 'status', + value: { + formula: ` + CASE source + WHEN 'web' THEN 'new' + WHEN 'event' THEN 'contacted' + ELSE 'unqualified' + END + `, + }, + }, + ], + }, + { + type: 'deduplication', + matchFields: ['email'], + strategy: 'skip', // or 'update', 'merge' + }, + ], + + // Load + load: { + target: { + object: 'lead', + }, + + // Error handling + onError: { + strategy: 'continue', // or 'stop' + logErrors: true, + notifyOnFailure: true, + notificationRecipients: ['admin@example.com'], + }, + }, + + // Post-processing + postProcess: [ + { + type: 'workflow', + workflow: 'assign_leads_to_reps', + }, + ], +}; + +/** + * 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].description}"`); + console.log(''); + + console.log('2. Email Alert Workflows:'); + console.log(` - ${emailAlertWorkflows.length} alert rules`); + console.log(` - Example: "${emailAlertWorkflows[0].description}"`); + console.log(''); + + console.log('3. Approval Process:'); + console.log(` - Name: ${discountApprovalProcess.name}`); + console.log(` - Steps: ${discountApprovalProcess.steps?.length || 0}`); + console.log(` - Description: "${discountApprovalProcess.description}"`); + console.log(''); + + console.log('4. Screen Flow:'); + console.log(` - Name: ${leadConversionFlow.name}`); + console.log(` - Elements: ${leadConversionFlow.elements?.length || 0}`); + console.log(` - Type: ${leadConversionFlow.type}`); + console.log(''); + + console.log('5. ETL Process:'); + console.log(` - Name: ${dailyLeadImportETL.name}`); + console.log(` - Schedule: ${dailyLeadImportETL.schedule}`); + console.log(` - Source: ${dailyLeadImportETL.extract?.source.type}`); +} + +// Run demonstration +demonstrateAutomation(); + +// Export all examples +export default { + fieldUpdateWorkflows, + emailAlertWorkflows, + taskCreationWorkflows, + discountApprovalProcess, + leadConversionFlow, + dailyLeadImportETL, +}; From 78c92b2eef79eefad455efba4e933842683af15a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:57:24 +0000 Subject: [PATCH 04/14] Update example READMEs with cross-references and navigation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/README.md | 39 +++++++++++++++++++++++++++++++++------ examples/crm/README.md | 25 ++++++++++++++++++++++--- examples/todo/README.md | 6 ++++-- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/examples/README.md b/examples/README.md index 39a48de58..4946ceaf4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -38,19 +38,35 @@ This directory contains examples demonstrating the ObjectStack Protocol and its ## šŸš€ Quick Start -### Run an Example +### Prerequisites + +Examples require the `@objectstack/spec` package to be built: ```bash -# Install dependencies (from monorepo root) +# From monorepo root pnpm install +pnpm --filter @objectstack/spec build +``` -# Build an example +### Run an Example + +```bash +# Build a complete example project pnpm --filter @objectstack/example-todo build # Run type checking pnpm --filter @objectstack/example-todo typecheck ``` +### Use Basic Examples + +The [basic/](./basic/) examples are TypeScript files that demonstrate protocols: + +```bash +# Type check a basic example (requires @objectstack/spec built) +npx tsx examples/basic/stack-definition-example.ts +``` + ### Example Structure Each example follows this structure: @@ -111,9 +127,20 @@ example-name/ ## šŸ”— Related Resources -- [ObjectStack Documentation](../content/docs/) -- [Protocol Reference](../packages/spec/) -- [Contributing Guide](../CONTRIBUTING.md) +- **Documentation:** + - [ObjectStack Documentation](../content/docs/) + - [Protocol Reference](../packages/spec/) + - [Architecture Overview](../content/docs/introduction/architecture.mdx) + +- **Development:** + - [Contributing Guide](../CONTRIBUTING.md) + - [Development Setup](../CONTRIBUTING.md#development-setup) + +- **Examples Navigation:** + - Start: [Basic Examples](./basic/) → Learn protocols + - Next: [Todo Example](./todo/) → Simple application + - Advanced: [CRM Example](./crm/) → Full-featured app + - AI: [AI Examples](./ai-sales/) → Intelligent features ## šŸ“ License 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/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 From 81ec69e86778e0b771265a98d42b5a39e191b833 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:59:42 +0000 Subject: [PATCH 05/14] Fix examples README protocol coverage status --- examples/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index 4946ceaf4..0ffa1b9b5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -113,13 +113,13 @@ example-name/ - āœ… **Discovery** - [basic/api-discovery-example.ts](./basic/) ### Auth Protocol -- ā³ **Identity & Policy** - Coming soon -- ā³ **RBAC** - Coming soon +- āœ… **Identity & Policy** - [basic/auth-permission-example.ts](./basic/) +- āœ… **RBAC** - [basic/auth-permission-example.ts](./basic/) ### Automation Protocol -- āœ… **Workflows** - [crm](./crm/) -- āœ… **Flows** - [crm](./crm/) -- ā³ **Approvals** - Coming soon +- āœ… **Workflows** - [crm](./crm/), [basic/automation-example.ts](./basic/) +- āœ… **Flows** - [crm](./crm/), [basic/automation-example.ts](./basic/) +- āœ… **Approvals** - [basic/automation-example.ts](./basic/) ### Hub Protocol - ā³ **Marketplace** - Coming soon From a38c977bb4f7d2b5ca81a94c67b1cadd9812e126 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:07:05 +0000 Subject: [PATCH 06/14] Convert basic examples to proper package with typecheck support Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/README.md | 7 ++- examples/basic/README.md | 53 ++++++++++++++++++---- examples/basic/ai-rag-example.ts | 4 +- examples/basic/api-discovery-example.ts | 18 ++++---- examples/basic/auth-permission-example.ts | 4 +- examples/basic/automation-example.ts | 4 +- examples/basic/capabilities-example.ts | 28 ++++++------ examples/basic/package.json | 18 ++++++++ examples/basic/stack-definition-example.ts | 3 ++ examples/basic/tsconfig.json | 25 ++++++++++ 10 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 examples/basic/package.json create mode 100644 examples/basic/tsconfig.json diff --git a/examples/README.md b/examples/README.md index 0ffa1b9b5..ca0d4fc46 100644 --- a/examples/README.md +++ b/examples/README.md @@ -60,10 +60,13 @@ pnpm --filter @objectstack/example-todo typecheck ### Use Basic Examples -The [basic/](./basic/) examples are TypeScript files that demonstrate protocols: +The [basic/](./basic/) examples are now a proper package for type checking: ```bash -# Type check a basic example (requires @objectstack/spec built) +# Type check basic examples +pnpm --filter @objectstack/example-basic typecheck + +# Run a specific example with tsx npx tsx examples/basic/stack-definition-example.ts ``` diff --git a/examples/basic/README.md b/examples/basic/README.md index 9c49cd171..84bc86783 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -1,6 +1,41 @@ # Basic Protocol Examples -This directory contains standalone examples demonstrating core ObjectStack protocols and features. +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 @@ -110,20 +145,20 @@ Shows automation capabilities in ObjectStack: ## šŸŽÆ Usage -These examples are TypeScript files that can be: +These examples are TypeScript files in a proper package that can be: -1. **Imported as references:** - ```typescript - import { taskManagementStack } from './examples/basic/stack-definition-example'; +1. **Type checked:** + ```bash + pnpm --filter @objectstack/example-basic typecheck ``` -2. **Run directly (if configured):** +2. **Run directly:** ```bash - tsx examples/basic/stack-definition-example.ts + npx tsx examples/basic/stack-definition-example.ts ``` -3. **Used as templates:** - Copy and modify for your own projects +3. **Used as references:** + Import types and patterns in your own projects ## šŸ”— Related Resources diff --git a/examples/basic/ai-rag-example.ts b/examples/basic/ai-rag-example.ts index be47eaaec..88d190161 100644 --- a/examples/basic/ai-rag-example.ts +++ b/examples/basic/ai-rag-example.ts @@ -379,8 +379,8 @@ export const advancedRAGPatterns = { // Usage // ============================================================================ -// Demonstrate RAG usage -demonstrateRAGUsage(); +// Demonstrate RAG usage (uncomment to run) +// demonstrateRAGUsage(); // Export all examples export default { diff --git a/examples/basic/api-discovery-example.ts b/examples/basic/api-discovery-example.ts index 35cff86ba..1299d3539 100644 --- a/examples/basic/api-discovery-example.ts +++ b/examples/basic/api-discovery-example.ts @@ -439,15 +439,13 @@ When helping users, respect these capabilities and limits.`; // Usage Examples // ============================================================================ -// Example: Initialize adaptive client -const client = new AdaptiveClient(); +// 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: Check features before using them -if (client.hasFeature('data', 'vectorSearch')) { - console.log('āœ… Vector search is available - AI/RAG features enabled'); -} - -// Example: Generate AI prompt -const systemPrompt = generateSystemPromptFromDiscovery(fullDiscoveryResponse); -console.log('AI System Prompt:\n', systemPrompt); +// 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 index 3f7d93c98..ddcf54c93 100644 --- a/examples/basic/auth-permission-example.ts +++ b/examples/basic/auth-permission-example.ts @@ -552,8 +552,8 @@ export function demonstratePermissions() { console.log('- Can access own opportunity:', checker.canAccessRecord(user, 'opportunity', opportunity)); } -// Run demonstration -demonstratePermissions(); +// Run demonstration (uncomment to run) +// demonstratePermissions(); // Export all examples export default { diff --git a/examples/basic/automation-example.ts b/examples/basic/automation-example.ts index cc2532cf8..87b788600 100644 --- a/examples/basic/automation-example.ts +++ b/examples/basic/automation-example.ts @@ -704,8 +704,8 @@ export function demonstrateAutomation() { console.log(` - Source: ${dailyLeadImportETL.extract?.source.type}`); } -// Run demonstration -demonstrateAutomation(); +// Run demonstration (uncomment to run) +// demonstrateAutomation(); // Export all examples export default { diff --git a/examples/basic/capabilities-example.ts b/examples/basic/capabilities-example.ts index d25cb73c9..98fe74115 100644 --- a/examples/basic/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 index 9f4df343b..4ab8a4f3c 100644 --- a/examples/basic/stack-definition-example.ts +++ b/examples/basic/stack-definition-example.ts @@ -489,6 +489,9 @@ export function demonstrateStackUsage() { } } +// Run demonstration (uncomment to run) +// demonstrateStackUsage(); + // ============================================================================ // Export for use // ============================================================================ diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json new file mode 100644 index 000000000..5da2a3260 --- /dev/null +++ b/examples/basic/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.json", + "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" + ] +} From 3d70a9583f3e9691a610a2bc974dd6254decb0fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:19:11 +0000 Subject: [PATCH 07/14] Fix TypeScript type errors in basic examples to match actual spec schemas Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/basic/ai-rag-example.ts | 217 +++++------ examples/basic/api-discovery-example.ts | 422 ++++------------------ examples/basic/auth-permission-example.ts | 353 +++++++----------- examples/basic/automation-example.ts | 6 +- 4 files changed, 313 insertions(+), 685 deletions(-) diff --git a/examples/basic/ai-rag-example.ts b/examples/basic/ai-rag-example.ts index 88d190161..9c257fc50 100644 --- a/examples/basic/ai-rag-example.ts +++ b/examples/basic/ai-rag-example.ts @@ -10,10 +10,11 @@ */ import type { - RAGPipeline, - RAGDocument, - RAGQuery, - RAGResult, + RAGPipelineConfig, + DocumentChunk, + DocumentMetadata, + RAGQueryRequest, + RAGQueryResponse, Agent, } from '@objectstack/spec'; @@ -22,87 +23,58 @@ import type { * * Complete RAG pipeline setup for a knowledge base system */ -export const knowledgeBaseRAG: RAGPipeline = { +export const knowledgeBaseRAG: RAGPipelineConfig = { name: 'knowledge_base_rag', + label: 'Knowledge Base RAG Pipeline', description: 'RAG pipeline for company knowledge base', - // Document Processing - documentProcessing: { - // Chunking strategy - chunkSize: 1000, - chunkOverlap: 200, - chunkingStrategy: 'recursive', - - // Text cleaning - preprocessing: { - removeHtml: true, - normalizeWhitespace: true, - removePunctuation: false, - }, - }, - // Embedding Configuration embedding: { - model: 'text-embedding-ada-002', provider: 'openai', + model: 'text-embedding-ada-002', dimensions: 1536, - - // Batching for performance batchSize: 100, - maxRetries: 3, }, // Vector Store vectorStore: { - type: 'pinecone', - index: 'knowledge-base', + provider: 'pinecone', + indexName: 'knowledge-base', namespace: 'production', + dimensions: 1536, + metric: 'cosine', + }, - // Metadata filtering - metadataFields: ['category', 'author', 'created_at', 'source'], + // Chunking strategy + chunking: { + type: 'recursive', + chunkSize: 1000, + chunkOverlap: 200, + separators: ['\n\n', '\n', ' ', ''], }, // Retrieval Configuration retrieval: { - // How many chunks to retrieve + type: 'hybrid', topK: 5, - - // Similarity threshold (0-1) - similarityThreshold: 0.7, - - // Reranking for better results - reranking: { - enabled: true, - model: 'cohere-rerank', - topN: 3, - }, - - // Hybrid search (combine vector + keyword) - hybridSearch: { - enabled: true, - alpha: 0.7, // 0 = pure keyword, 1 = pure vector - }, + vectorWeight: 0.7, + keywordWeight: 0.3, }, - // Context Assembly - contextAssembly: { - // How to combine retrieved chunks - strategy: 'concatenate', - - // Maximum context length - maxTokens: 3000, - - // Include metadata in context - includeMetadata: true, - - // Template for formatting context - template: `Context from knowledge base: + // Reranking for better results + reranking: { + enabled: true, + model: 'cohere-rerank-v3', + provider: 'cohere', + topK: 3, + }, -{{#each chunks}} -[Source: {{metadata.source}}] -{{content}} + // Context Management + maxContextTokens: 3000, -{{/each}}`, + // Metadata filtering + metadataFilters: { + status: 'published', }, }; @@ -111,48 +83,60 @@ export const knowledgeBaseRAG: RAGPipeline = { * * How to index documents into the RAG pipeline */ -export const sampleDocuments: RAGDocument[] = [ +export const sampleDocumentMetadata: 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: DocumentChunk[] = [ { - id: 'doc_001', + 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: { - source: 'Product Documentation', - category: 'Architecture', - author: 'Technical Team', - created_at: '2024-01-15', - url: 'https://docs.objectstack.dev/architecture', - }, + metadata: sampleDocumentMetadata[0], + chunkIndex: 0, + tokens: 45, }, { - id: 'doc_002', + 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: { - source: 'Developer Guide', - category: 'Tutorial', - author: 'DevRel Team', - created_at: '2024-01-20', - url: 'https://docs.objectstack.dev/guides/objects', - }, + metadata: sampleDocumentMetadata[1], + chunkIndex: 0, + tokens: 38, }, { - id: 'doc_003', + 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: { - source: 'AI Documentation', - category: 'AI Features', - author: 'AI Team', - created_at: '2024-02-01', - url: 'https://docs.objectstack.dev/ai/bridge', - }, + metadata: sampleDocumentMetadata[2], + chunkIndex: 0, + tokens: 42, }, ]; @@ -161,29 +145,32 @@ Use the Agent protocol to define AI assistants with specific capabilities.`, * * Performing a RAG query with filters and options */ -export const sampleQueries: RAGQuery[] = [ +export const sampleQueries: RAGQueryRequest[] = [ { // Simple question query: 'What is ObjectStack?', + pipelineName: 'knowledge_base_rag', topK: 5, }, { // Question with metadata filtering query: 'How do I create objects?', + pipelineName: 'knowledge_base_rag', topK: 3, - filter: { + metadataFilters: { category: 'Tutorial', }, }, { - // Advanced query with reranking + // Advanced query query: 'Tell me about AI integration', + pipelineName: 'knowledge_base_rag', topK: 10, - rerank: true, - rerankTopN: 3, - filter: { + metadataFilters: { category: 'AI Features', }, + includeMetadata: true, + includeSources: true, }, ]; @@ -192,52 +179,45 @@ export const sampleQueries: RAGQuery[] = [ * * What the pipeline returns */ -export const sampleResults: RAGResult = { +export const sampleResults: RAGQueryResponse = { query: 'What is ObjectStack?', // Retrieved chunks - chunks: [ + results: [ { - id: 'chunk_001', - documentId: 'doc_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.`, - score: 0.92, - metadata: { - source: 'Product Documentation', + source: 'https://docs.objectstack.dev/architecture', + sourceType: 'url', category: 'Architecture', }, }, { - id: 'chunk_002', - documentId: 'doc_001', content: `The platform supports multiple databases and provides built-in AI capabilities.`, - score: 0.85, - metadata: { - source: 'Product Documentation', + source: 'https://docs.objectstack.dev/architecture', + sourceType: 'url', category: 'Architecture', }, }, ], - // Assembled context for LLM + // Context assembled for LLM context: `Context from knowledge base: -[Source: Product Documentation] +[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: Product Documentation] +[Source: https://docs.objectstack.dev/architecture] The platform supports multiple databases and provides built-in AI capabilities.`, // Metadata - retrievalTime: 150, // milliseconds - totalChunks: 2, - averageScore: 0.885, + processingTimeMs: 150, + totalResults: 2, }; /** @@ -323,8 +303,8 @@ export function demonstrateRAGUsage() { console.log('RAG Pipeline:'); console.log('- Embedding query...'); console.log('- Searching vector database...'); - console.log(`- Retrieved ${sampleResults.chunks.length} relevant chunks`); - console.log(`- Average relevance score: ${sampleResults.averageScore}\n`); + console.log(`- Retrieved ${sampleResults.results.length} relevant chunks`); + console.log(`- Processing time: ${sampleResults.processingTimeMs}ms\n`); // Step 3: Context is assembled console.log('Assembled Context:'); @@ -341,7 +321,7 @@ It has three main architectural layers: The platform supports multiple databases and includes built-in AI capabilities, making it suitable for creating intelligent applications. -*Source: Product Documentation - Architecture*`; +*Source: https://docs.objectstack.dev/architecture*`; console.log('Assistant:'); console.log(assistantResponse); @@ -385,7 +365,8 @@ export const advancedRAGPatterns = { // Export all examples export default { knowledgeBaseRAG, - sampleDocuments, + sampleDocumentMetadata, + sampleDocumentChunks, sampleQueries, sampleResults, ragEnabledAgent, diff --git a/examples/basic/api-discovery-example.ts b/examples/basic/api-discovery-example.ts index 1299d3539..f11749726 100644 --- a/examples/basic/api-discovery-example.ts +++ b/examples/basic/api-discovery-example.ts @@ -11,7 +11,7 @@ */ import type { - ApiDiscoveryResponse, + DiscoveryResponse, ApiCapabilities, } from '@objectstack/spec'; @@ -20,189 +20,35 @@ import type { * * This is what a client receives when calling /api/discovery */ -export const fullDiscoveryResponse: ApiDiscoveryResponse = { +export const fullDiscoveryResponse: DiscoveryResponse = { // System Identity - system: { - name: 'ObjectStack CRM', - version: '2.1.0', - environment: 'production', - vendor: 'Acme Corporation', + 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', }, - // Available API Surfaces - endpoints: { - rest: { - baseUrl: 'https://api.example.com/v1', - version: 'v1', - documentation: 'https://api.example.com/docs', - }, - graphql: { - endpoint: 'https://api.example.com/graphql', - introspection: true, - playground: 'https://api.example.com/playground', - }, - odata: { - baseUrl: 'https://api.example.com/odata', - version: '4.0', - }, - realtime: { - websocket: 'wss://api.example.com/ws', - sse: 'https://api.example.com/events', - }, - }, - - // Runtime Capabilities (from capabilities-example.ts) - capabilities: { - data: { - queryFilters: true, - queryAggregations: true, - querySorting: true, - queryPagination: true, - queryWindowFunctions: true, - querySubqueries: true, - queryDistinct: true, - queryHaving: true, - queryJoins: true, - - fullTextSearch: true, - vectorSearch: true, - geoSpatial: true, - - jsonFields: true, - arrayFields: true, - - validationRules: true, - workflows: true, - triggers: true, - formulas: true, - - transactions: true, - bulkOperations: true, - - supportedDrivers: ['postgresql', 'mongodb'], - }, - - ui: { - listView: true, - formView: true, - kanbanView: true, - calendarView: true, - ganttView: true, - - dashboards: true, - reports: true, - charts: true, - - customPages: true, - customThemes: true, - customComponents: true, - - customActions: true, - screenFlows: true, - - mobileOptimized: true, - accessibility: true, - }, - - system: { - version: '2.1.0', - environment: 'production', - - restApi: true, - graphqlApi: true, - odataApi: true, - - websockets: true, - serverSentEvents: true, - eventBus: true, - - webhooks: true, - apiContracts: true, - - authentication: true, - rbac: true, - fieldLevelSecurity: true, - rowLevelSecurity: true, - - multiTenant: true, - - backgroundJobs: true, - auditLogging: true, - fileStorage: true, - - i18n: true, - - pluginSystem: true, - - systemObjects: ['user', 'role', 'permission', 'object', 'field'], - - limits: { - maxObjects: 1000, - maxFieldsPerObject: 500, - maxRecordsPerQuery: 10000, - apiRateLimit: 1000, - fileUploadSizeLimit: 10485760, - }, - }, - }, - - // Authentication Configuration - auth: { - required: true, - methods: ['oauth2', 'apiKey', 'jwt'], - oauth2: { - authorizationUrl: 'https://auth.example.com/oauth/authorize', - tokenUrl: 'https://auth.example.com/oauth/token', - scopes: ['read', 'write', 'admin'], - }, - }, - - // Available Objects (Schema Registry) - objects: [ - { - name: 'account', - label: 'Account', - labelPlural: 'Accounts', - apiEnabled: true, - endpoints: { - list: '/api/v1/objects/account', - get: '/api/v1/objects/account/{id}', - create: '/api/v1/objects/account', - update: '/api/v1/objects/account/{id}', - delete: '/api/v1/objects/account/{id}', - }, - }, - { - name: 'contact', - label: 'Contact', - labelPlural: 'Contacts', - apiEnabled: true, - endpoints: { - list: '/api/v1/objects/contact', - get: '/api/v1/objects/contact/{id}', - create: '/api/v1/objects/contact', - update: '/api/v1/objects/contact/{id}', - delete: '/api/v1/objects/contact/{id}', - }, - }, - ], - - // Feature Flags + // Feature Flags - what capabilities are enabled features: { - aiAssistant: true, - advancedAnalytics: true, - customBranding: true, - apiAccess: true, - webhooks: true, - auditLogs: true, + graphql: true, + search: true, + websockets: true, + files: true, }, - // Links - links: { - documentation: 'https://docs.example.com', - support: 'https://support.example.com', - status: 'https://status.example.com', - portal: 'https://app.example.com', + // Localization Info + locale: { + default: 'en-US', + supported: ['en-US', 'zh-CN', 'es-ES', 'fr-FR'], + timezone: 'UTC', }, }; @@ -211,123 +57,29 @@ export const fullDiscoveryResponse: ApiDiscoveryResponse = { * * A simplified response for local development */ -export const devDiscoveryResponse: ApiDiscoveryResponse = { - system: { - name: 'ObjectStack Dev', - version: '0.1.0', - environment: 'development', - }, - - endpoints: { - rest: { - baseUrl: 'http://localhost:3000/api', - version: 'v1', - }, +export const devDiscoveryResponse: DiscoveryResponse = { + name: 'ObjectStack Dev', + version: '0.1.0', + environment: 'development', + + routes: { + data: '/api/data', + metadata: '/api/meta', + auth: '/api/auth', }, - capabilities: { - data: { - queryFilters: true, - queryAggregations: true, - querySorting: true, - queryPagination: true, - queryWindowFunctions: false, - querySubqueries: false, - queryDistinct: true, - queryHaving: false, - queryJoins: false, - - fullTextSearch: false, - vectorSearch: false, - geoSpatial: false, - - jsonFields: true, - arrayFields: false, - - validationRules: true, - workflows: false, - triggers: false, - formulas: true, - - transactions: true, - bulkOperations: true, - - supportedDrivers: ['memory', 'sqlite'], - }, - - ui: { - listView: true, - formView: true, - kanbanView: false, - calendarView: false, - ganttView: false, - - dashboards: true, - reports: true, - charts: true, - - customPages: true, - customThemes: false, - customComponents: false, - - customActions: true, - screenFlows: false, - - mobileOptimized: false, - accessibility: false, - }, - - system: { - version: '0.1.0', - environment: 'development', - - restApi: true, - graphqlApi: false, - odataApi: false, - - websockets: false, - serverSentEvents: false, - eventBus: false, - - webhooks: false, - apiContracts: false, - - authentication: true, - rbac: true, - fieldLevelSecurity: false, - rowLevelSecurity: false, - - multiTenant: false, - - backgroundJobs: false, - auditLogging: false, - fileStorage: true, - - i18n: true, - - pluginSystem: false, - - systemObjects: ['user', 'role', 'object'], - - limits: { - maxObjects: 100, - maxFieldsPerObject: 200, - maxRecordsPerQuery: 1000, - apiRateLimit: 100, - fileUploadSizeLimit: 5242880, - }, - }, + features: { + graphql: false, + search: true, + websockets: false, + files: true, }, - auth: { - required: false, + locale: { + default: 'en-US', + supported: ['en-US'], + timezone: 'America/New_York', }, - - objects: [], - - features: {}, - - links: {}, }; /** @@ -336,71 +88,49 @@ export const devDiscoveryResponse: ApiDiscoveryResponse = { * How a client can use the discovery API to adapt its behavior */ export class AdaptiveClient { - private capabilities: ApiCapabilities | null = null; + private discovery: DiscoveryResponse | null = null; async initialize(baseUrl: string) { // Fetch discovery information const response = await fetch(`${baseUrl}/api/discovery`); - const discovery: ApiDiscoveryResponse = await response.json(); - - this.capabilities = discovery.capabilities; + this.discovery = await response.json() as DiscoveryResponse; - console.log(`Connected to: ${discovery.system.name} v${discovery.system.version}`); - console.log(`Environment: ${discovery.system.environment}`); + 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(subsystem: 'data' | 'ui' | 'system', feature: string): boolean { - if (!this.capabilities) return false; - - const subsystemCaps = this.capabilities[subsystem] as any; - return subsystemCaps?.[feature] === true; + hasFeature(feature: keyof ApiCapabilities): boolean { + if (!this.discovery) return false; + return this.discovery.features[feature] === true; } /** - * Build query based on available capabilities + * Get the route for a specific API */ - buildQuery(object: string, options: any) { - const query: any = { object }; - - // Only use subqueries if supported - if (this.hasFeature('data', 'querySubqueries') && options.includeRelated) { - query.subqueries = options.includeRelated; - } - - // Only use window functions if supported - if (this.hasFeature('data', 'queryWindowFunctions') && options.ranking) { - query.windowFunctions = options.ranking; - } - - // Basic filtering is usually always available - if (options.filter) { - query.filter = options.filter; - } - - return query; + 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(discovery: ApiDiscoveryResponse, preference: 'rest' | 'graphql' | 'odata' = 'rest') { - const endpoints = discovery.endpoints; - - // Try preferred endpoint first - if (preference === 'graphql' && endpoints.graphql) { - return { type: 'graphql', url: endpoints.graphql.endpoint }; + getApiEndpoint(preference: 'rest' | 'graphql' = 'rest') { + if (!this.discovery) { + throw new Error('Client not initialized'); } - - if (preference === 'odata' && endpoints.odata) { - return { type: 'odata', url: endpoints.odata.baseUrl }; + + // 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 (usually always available) - if (endpoints.rest) { - return { type: 'rest', url: endpoints.rest.baseUrl }; + // 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'); @@ -412,25 +142,23 @@ export class AdaptiveClient { * * How an AI agent can use discovery to understand the system */ -export function generateSystemPromptFromDiscovery(discovery: ApiDiscoveryResponse): string { - const { system, capabilities, objects } = discovery; +export function generateSystemPromptFromDiscovery(discovery: DiscoveryResponse): string { + const { name, version, features, locale } = discovery; - const prompt = `You are an AI assistant for ${system.name} (v${system.version}). + const prompt = `You are an AI assistant for ${name} (v${version}). SYSTEM CAPABILITIES: -${capabilities.data.vectorSearch ? '- Vector search available (can use RAG)' : ''} -${capabilities.data.fullTextSearch ? '- Full-text search available' : ''} -${capabilities.ui.dashboards ? '- Can create and manage dashboards' : ''} -${capabilities.system.webhooks ? '- Can configure webhooks' : ''} - -AVAILABLE OBJECTS: -${objects.map(obj => `- ${obj.label} (${obj.name})`).join('\n')} +${features.graphql ? '- GraphQL API available' : ''} +${features.search ? '- Search functionality available' : ''} +${features.websockets ? '- Real-time updates via WebSockets' : ''} +${features.files ? '- File upload/download supported' : ''} -RATE LIMITS: -- API Rate Limit: ${capabilities.system.limits?.apiRateLimit || 'N/A'} requests/minute -- Max Records per Query: ${capabilities.system.limits?.maxRecordsPerQuery || 'N/A'} +LOCALIZATION: +- Default Locale: ${locale.default} +- Supported Languages: ${locale.supported.join(', ')} +- Timezone: ${locale.timezone} -When helping users, respect these capabilities and limits.`; +When helping users, respect these capabilities and localization settings.`; return prompt; } diff --git a/examples/basic/auth-permission-example.ts b/examples/basic/auth-permission-example.ts index ddcf54c93..bffe115f8 100644 --- a/examples/basic/auth-permission-example.ts +++ b/examples/basic/auth-permission-example.ts @@ -14,7 +14,7 @@ import type { User, Role, PermissionSet, - RowLevelSecurity, + RowLevelSecurityPolicy, SharingRule, Territory, } from '@objectstack/spec'; @@ -29,55 +29,27 @@ export const sampleUsers: User[] = [ id: 'user_001', email: 'admin@example.com', name: 'Admin User', - - // Authentication - authProvider: 'local', emailVerified: true, - - // Profile - avatar: 'https://example.com/avatars/admin.jpg', - timezone: 'America/New_York', - locale: 'en-US', - - // Status - active: true, - lastLogin: '2024-01-29T10:30:00Z', - - // Roles - roles: ['system_administrator'], + 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', - - authProvider: 'oauth2', emailVerified: true, - - avatar: 'https://example.com/avatars/sales.jpg', - timezone: 'America/Los_Angeles', - locale: 'en-US', - - active: true, - lastLogin: '2024-01-29T09:15:00Z', - - roles: ['sales_manager'], + 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', - - authProvider: 'oauth2', emailVerified: true, - - timezone: 'America/Chicago', - locale: 'en-US', - - active: true, - lastLogin: '2024-01-29T08:45:00Z', - - roles: ['sales_rep'], + createdAt: new Date('2024-01-10T00:00:00Z'), + updatedAt: new Date('2024-01-29T08:45:00Z'), }, ]; @@ -85,26 +57,17 @@ export const sampleUsers: User[] = [ * 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: Role[] = [ // Top-level admin role { name: 'system_administrator', label: 'System Administrator', - description: 'Full system access', - + description: 'Top-level system administrator', // No parent = top of hierarchy - parentRole: undefined, - - // Full permissions - permissions: { - manageUsers: true, - manageRoles: true, - manageObjects: true, - manageSystem: true, - viewAllData: true, - modifyAllData: true, - }, + parent: undefined, }, // Sales hierarchy @@ -112,33 +75,13 @@ export const roleHierarchy: Role[] = [ name: 'sales_manager', label: 'Sales Manager', description: 'Manages sales team and data', - - parentRole: 'system_administrator', - - permissions: { - manageUsers: false, - manageRoles: false, - manageObjects: false, - manageSystem: false, - viewAllData: true, // Can view all sales data - modifyAllData: true, // Can modify all sales data - }, + parent: 'system_administrator', }, { name: 'sales_rep', label: 'Sales Representative', description: 'Standard sales user', - - parentRole: 'sales_manager', // Inherits from manager - - permissions: { - manageUsers: false, - manageRoles: false, - manageObjects: false, - manageSystem: false, - viewAllData: false, // Can only view own data - modifyAllData: false, // Can only modify own data - }, + parent: 'sales_manager', // Inherits from manager }, ]; @@ -151,114 +94,122 @@ export const permissionSets: PermissionSet[] = [ { name: 'sales_user_permissions', label: 'Sales User Permissions', - description: 'Standard permissions for sales users', - - // Object permissions - objectPermissions: [ - { - object: 'account', - create: true, - read: true, - update: true, - delete: false, - viewAll: false, - modifyAll: false, + 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, }, - { - object: 'contact', - create: true, - read: true, - update: true, - delete: false, - viewAll: false, - modifyAll: false, + contact: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: false, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, }, - { - object: 'opportunity', - create: true, - read: true, - update: true, - delete: false, - viewAll: false, - modifyAll: false, + opportunity: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: false, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: false, + modifyAllRecords: false, }, - { - object: 'lead', - create: true, - read: true, - update: true, - delete: true, // Can delete own leads - viewAll: false, - modifyAll: 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) - fieldPermissions: [ - { - object: 'account', - field: 'annual_revenue', - read: true, - update: false, // Can see but not edit revenue + }, + + // Field-level permissions (Field-Level Security, record of FieldPermission) + fields: { + 'account.annual_revenue': { + readable: true, + editable: false, // Can see but not edit revenue }, - { - object: 'opportunity', - field: 'probability', - read: true, - update: false, // Calculated field, read-only + 'opportunity.probability': { + readable: true, + editable: false, // Calculated field, read-only }, - ], + }, }, { name: 'sales_manager_permissions', label: 'Sales Manager Permissions', - description: 'Extended permissions for sales managers', - - objectPermissions: [ - { - object: 'account', - create: true, - read: true, - update: true, - delete: true, - viewAll: true, // Can view all accounts - modifyAll: true, // Can modify all accounts + 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 }, - { - object: 'opportunity', - create: true, - read: true, - update: true, - delete: true, - viewAll: true, - modifyAll: true, + opportunity: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, + allowTransfer: true, + allowRestore: true, + allowPurge: false, + viewAllRecords: true, + modifyAllRecords: true, }, - { - object: 'forecast', - create: true, - read: true, - update: true, - delete: true, - viewAll: true, - modifyAll: true, + forecast: { + allowCreate: true, + allowRead: true, + allowEdit: true, + allowDelete: true, + allowTransfer: false, + allowRestore: false, + allowPurge: false, + viewAllRecords: true, + modifyAllRecords: true, }, - ], - - fieldPermissions: [ - { - object: 'account', - field: 'annual_revenue', - read: true, - update: true, // Managers can edit revenue + }, + + // Field-level permissions (Field-Level Security, record of FieldPermission) + fields: { + 'account.annual_revenue': { + readable: true, + editable: true, // Managers can edit revenue }, - { - object: 'opportunity', - field: 'discount_percent', - read: true, - update: true, // Managers can approve discounts + 'opportunity.discount_percent': { + readable: true, + editable: true, // Managers can approve discounts }, - ], + }, }, ]; @@ -267,67 +218,33 @@ export const permissionSets: PermissionSet[] = [ * * Fine-grained data access control based on record ownership */ -export const rowLevelSecurityRules: RowLevelSecurity[] = [ +export const rowLevelSecurityRules: RowLevelSecurityPolicy[] = [ { name: 'opportunity_owner_access', + label: 'Opportunity Owner Access', object: 'opportunity', description: 'Users can only access their own opportunities', + operation: 'select', - // Rule definition - rule: { - operator: 'OR', - conditions: [ - // Can access if owner - { - field: 'owner', - operator: 'equals', - value: '$CurrentUser.id', - }, - // Can access if in their territory - { - field: 'territory', - operator: 'in', - value: '$CurrentUser.territories', - }, - // Managers can access team records - { - field: 'owner.manager', - operator: 'equals', - value: '$CurrentUser.id', - }, - ], - }, + // 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'], - - // Operations affected - operations: ['read', 'update', 'delete'], + enabled: true, }, { name: 'account_territory_access', + label: 'Account Territory Access', object: 'account', description: 'Territory-based account access', + operation: 'select', - rule: { - operator: 'AND', - conditions: [ - { - field: 'territory', - operator: 'in', - value: '$CurrentUser.territories', - }, - { - field: 'active', - operator: 'equals', - value: true, - }, - ], - }, + using: `territory IN (SELECT id FROM territories WHERE user_id = current_user.id) AND status = 'active'`, roles: ['sales_rep'], - operations: ['read', 'update'], + enabled: true, }, ]; @@ -469,19 +386,21 @@ export class PermissionChecker { object: string, operation: 'create' | 'read' | 'update' | 'delete' ): boolean { - // Get user's roles - const userRoles = user.roles || []; + // Find permission sets (example implementation uses all permission sets) + const userPermissionSets = permissionSets; - // Find permission sets for user's roles - const userPermissionSets = permissionSets.filter((ps) => - // In real implementation, map roles to permission sets - true - ); + // 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.objectPermissions?.find((op) => op.object === object); - if (objPerm && objPerm[operation]) { + const objPerm = permSet.objects[object]; + if (objPerm && objPerm[operationMap[operation]]) { return true; } } diff --git a/examples/basic/automation-example.ts b/examples/basic/automation-example.ts index 87b788600..be8e0ac67 100644 --- a/examples/basic/automation-example.ts +++ b/examples/basic/automation-example.ts @@ -12,7 +12,7 @@ import type { WorkflowRule, ApprovalProcess, Flow, - ETLProcess, + ETLPipeline, } from '@objectstack/spec'; /** @@ -581,11 +581,11 @@ export const leadConversionFlow: Flow = { }; /** - * Example 6: ETL Process + * Example 6: ETL Pipeline * * Data integration and transformation */ -export const dailyLeadImportETL: ETLProcess = { +export const dailyLeadImportETL: ETLPipeline = { name: 'daily_lead_import', description: 'Import leads from external system daily', From 31a75e45055fa2fa99f43c10aa8343fd7cb3d55c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:19:40 +0000 Subject: [PATCH 08/14] Fix tsconfig.json to not extend from root --- examples/basic/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json index 5da2a3260..1fc9f3a06 100644 --- a/examples/basic/tsconfig.json +++ b/examples/basic/tsconfig.json @@ -1,5 +1,4 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": ".", From 049c7cf1379e9bb220ef6739fb5ce3cdea67d5ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:31:47 +0000 Subject: [PATCH 09/14] Remove README.md from examples root - keep only directories Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- examples/README.md | 150 --------------------------------------------- 1 file changed, 150 deletions(-) delete mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index ca0d4fc46..000000000 --- a/examples/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# ObjectStack Examples - -This directory contains examples demonstrating the ObjectStack Protocol and its ecosystem. - -## šŸ“š Directory Structure - -### šŸŽ“ Learning Path - -**Start Here:** -1. **[basic/](./basic/)** - Simple protocol examples - - Stack definition with `defineStack()` - - Basic Object and Field definitions - - Capabilities configuration - -2. **[todo/](./todo/)** - Minimal task management app - - Simple object definition - - Basic configuration - -3. **[crm/](./crm/)** - Full-featured CRM application - - All field types and features - - Workflows, validations, and permissions - - UI components, dashboards, and reports - -### šŸ¤– AI & Intelligence - -**AI Protocol Examples:** -- **[ai-sales/](./ai-sales/)** - AI-powered sales assistant -- **[ai-analyst/](./ai-analyst/)** - AI data analyst -- **[ai-codegen/](./ai-codegen/)** - AI code generation -- **[ai-support/](./ai-support/)** - AI customer support - -### šŸ”§ Integration & Plugins - -**Runtime Integration:** -- **[msw-react-crud/](./msw-react-crud/)** - React CRUD with Mock Service Worker -- **[plugin-bi/](./plugin-bi/)** - Business Intelligence plugin -- **[host/](./host/)** - Plugin host environment - -## šŸš€ Quick Start - -### Prerequisites - -Examples require the `@objectstack/spec` package to be built: - -```bash -# From monorepo root -pnpm install -pnpm --filter @objectstack/spec build -``` - -### Run an Example - -```bash -# Build a complete example project -pnpm --filter @objectstack/example-todo build - -# Run type checking -pnpm --filter @objectstack/example-todo typecheck -``` - -### Use Basic Examples - -The [basic/](./basic/) examples are now a proper package for type checking: - -```bash -# Type check basic examples -pnpm --filter @objectstack/example-basic typecheck - -# Run a specific example with tsx -npx tsx examples/basic/stack-definition-example.ts -``` - -### Example Structure - -Each example follows this structure: - -``` -example-name/ -ā”œā”€ā”€ src/ -│ ā”œā”€ā”€ domains/ # Object definitions -│ ā”œā”€ā”€ ui/ # UI configurations (optional) -│ └── server/ # Server setup (optional) -ā”œā”€ā”€ objectstack.config.ts # Stack definition -ā”œā”€ā”€ package.json -ā”œā”€ā”€ README.md -└── tsconfig.json -``` - -## šŸ“– Protocol Coverage - -### Data Protocol (ObjectQL) -- āœ… **Objects & Fields** - [crm](./crm/), [todo](./todo/) -- āœ… **Validation Rules** - [crm](./crm/) -- āœ… **Workflows** - [crm](./crm/) -- āœ… **Hooks** - [crm](./crm/) - -### UI Protocol (ObjectUI) -- āœ… **Apps & Navigation** - [crm](./crm/) -- āœ… **Views** (Grid, Kanban, Calendar, Gantt) - [crm](./crm/) -- āœ… **Dashboards** - [crm](./crm/) -- āœ… **Reports** - [crm](./crm/) -- āœ… **Actions** - [crm](./crm/) - -### AI Protocol -- āœ… **Agents** - [ai-sales](./ai-sales/), [ai-analyst](./ai-analyst/) -- āœ… **RAG Pipeline** - [basic/ai-rag-example.ts](./basic/) -- āœ… **Orchestration** - [ai-codegen](./ai-codegen/) - -### System Protocol (ObjectOS) -- āœ… **Manifest** - All examples -- āœ… **Capabilities** - [basic/capabilities-example.ts](./basic/) -- āœ… **Stack Definition** - [basic/stack-definition-example.ts](./basic/) - -### API Protocol -- āœ… **REST API** - [crm](./crm/) -- āœ… **Discovery** - [basic/api-discovery-example.ts](./basic/) - -### Auth Protocol -- āœ… **Identity & Policy** - [basic/auth-permission-example.ts](./basic/) -- āœ… **RBAC** - [basic/auth-permission-example.ts](./basic/) - -### Automation Protocol -- āœ… **Workflows** - [crm](./crm/), [basic/automation-example.ts](./basic/) -- āœ… **Flows** - [crm](./crm/), [basic/automation-example.ts](./basic/) -- āœ… **Approvals** - [basic/automation-example.ts](./basic/) - -### Hub Protocol -- ā³ **Marketplace** - Coming soon -- ā³ **Multi-tenancy** - Coming soon - -## šŸ”— Related Resources - -- **Documentation:** - - [ObjectStack Documentation](../content/docs/) - - [Protocol Reference](../packages/spec/) - - [Architecture Overview](../content/docs/introduction/architecture.mdx) - -- **Development:** - - [Contributing Guide](../CONTRIBUTING.md) - - [Development Setup](../CONTRIBUTING.md#development-setup) - -- **Examples Navigation:** - - Start: [Basic Examples](./basic/) → Learn protocols - - Next: [Todo Example](./todo/) → Simple application - - Advanced: [CRM Example](./crm/) → Full-featured app - - AI: [AI Examples](./ai-sales/) → Intelligent features - -## šŸ“ License - -Apache 2.0 Ā© ObjectStack From 41f4148f2a8dd83d3c6db7823acfe11df130d231 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:50:13 +0000 Subject: [PATCH 10/14] Initial plan From 73db13bd4d1b247c361844efa1cd7c340fa4a2e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:52:50 +0000 Subject: [PATCH 11/14] Update pnpm-lock.yaml to include examples/basic dependencies Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- pnpm-lock.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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': From 465b6ada2f66b29d824e3fef130765ae5d34769f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:01:12 +0000 Subject: [PATCH 12/14] Initial plan From 2493882ec0406fdd767e06d6684ffc714e631ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:09:26 +0000 Subject: [PATCH 13/14] Fix TypeScript errors in example files - namespace imports and type corrections Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/basic/ai-rag-example.ts | 21 +-- examples/basic/api-discovery-example.ts | 17 +- examples/basic/auth-permission-example.ts | 27 ++- examples/basic/automation-example.ts | 19 +- examples/basic/stack-definition-example.ts | 194 +++++++-------------- 5 files changed, 90 insertions(+), 188 deletions(-) diff --git a/examples/basic/ai-rag-example.ts b/examples/basic/ai-rag-example.ts index 9c257fc50..c6a784c8f 100644 --- a/examples/basic/ai-rag-example.ts +++ b/examples/basic/ai-rag-example.ts @@ -9,21 +9,14 @@ * - AI-powered question answering */ -import type { - RAGPipelineConfig, - DocumentChunk, - DocumentMetadata, - RAGQueryRequest, - RAGQueryResponse, - Agent, -} from '@objectstack/spec'; +import type { AI } from '@objectstack/spec'; /** * Example 1: RAG Pipeline Configuration * * Complete RAG pipeline setup for a knowledge base system */ -export const knowledgeBaseRAG: RAGPipelineConfig = { +export const knowledgeBaseRAG: AI.RAGPipelineConfig = { name: 'knowledge_base_rag', label: 'Knowledge Base RAG Pipeline', description: 'RAG pipeline for company knowledge base', @@ -83,7 +76,7 @@ export const knowledgeBaseRAG: RAGPipelineConfig = { * * How to index documents into the RAG pipeline */ -export const sampleDocumentMetadata: DocumentMetadata[] = [ +export const sampleDocumentMetadata: AI.DocumentMetadata[] = [ { source: 'https://docs.objectstack.dev/architecture', sourceType: 'url', @@ -110,7 +103,7 @@ export const sampleDocumentMetadata: DocumentMetadata[] = [ }, ]; -export const sampleDocumentChunks: DocumentChunk[] = [ +export const sampleDocumentChunks: AI.DocumentChunk[] = [ { id: 'chunk_001', content: `ObjectStack is a metadata-driven low-code platform that enables rapid application development. @@ -145,7 +138,7 @@ Use the Agent protocol to define AI assistants with specific capabilities.`, * * Performing a RAG query with filters and options */ -export const sampleQueries: RAGQueryRequest[] = [ +export const sampleQueries: AI.RAGQueryRequest[] = [ { // Simple question query: 'What is ObjectStack?', @@ -179,7 +172,7 @@ export const sampleQueries: RAGQueryRequest[] = [ * * What the pipeline returns */ -export const sampleResults: RAGQueryResponse = { +export const sampleResults: AI.RAGQueryResponse = { query: 'What is ObjectStack?', // Retrieved chunks @@ -225,7 +218,7 @@ The platform supports multiple databases and provides built-in AI capabilities.` * * Integrating RAG into an AI agent */ -export const ragEnabledAgent: Agent = { +export const ragEnabledAgent: AI.Agent = { name: 'documentation_assistant', type: 'conversational', label: 'Documentation Assistant', diff --git a/examples/basic/api-discovery-example.ts b/examples/basic/api-discovery-example.ts index f11749726..9e5f8b88f 100644 --- a/examples/basic/api-discovery-example.ts +++ b/examples/basic/api-discovery-example.ts @@ -10,17 +10,14 @@ * Typically exposed at: GET /api/discovery */ -import type { - DiscoveryResponse, - ApiCapabilities, -} from '@objectstack/spec'; +import type { API } from '@objectstack/spec'; /** * Example 1: Complete Discovery Response * * This is what a client receives when calling /api/discovery */ -export const fullDiscoveryResponse: DiscoveryResponse = { +export const fullDiscoveryResponse: API.DiscoveryResponse = { // System Identity name: 'ObjectStack CRM', version: '2.1.0', @@ -57,7 +54,7 @@ export const fullDiscoveryResponse: DiscoveryResponse = { * * A simplified response for local development */ -export const devDiscoveryResponse: DiscoveryResponse = { +export const devDiscoveryResponse: API.DiscoveryResponse = { name: 'ObjectStack Dev', version: '0.1.0', environment: 'development', @@ -88,12 +85,12 @@ export const devDiscoveryResponse: DiscoveryResponse = { * How a client can use the discovery API to adapt its behavior */ export class AdaptiveClient { - private discovery: DiscoveryResponse | null = null; + 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 DiscoveryResponse; + 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}`); @@ -102,7 +99,7 @@ export class AdaptiveClient { /** * Check if a specific feature is available */ - hasFeature(feature: keyof ApiCapabilities): boolean { + hasFeature(feature: keyof API.ApiCapabilities): boolean { if (!this.discovery) return false; return this.discovery.features[feature] === true; } @@ -142,7 +139,7 @@ export class AdaptiveClient { * * How an AI agent can use discovery to understand the system */ -export function generateSystemPromptFromDiscovery(discovery: DiscoveryResponse): string { +export function generateSystemPromptFromDiscovery(discovery: API.DiscoveryResponse): string { const { name, version, features, locale } = discovery; const prompt = `You are an AI assistant for ${name} (v${version}). diff --git a/examples/basic/auth-permission-example.ts b/examples/basic/auth-permission-example.ts index bffe115f8..6220416b2 100644 --- a/examples/basic/auth-permission-example.ts +++ b/examples/basic/auth-permission-example.ts @@ -10,21 +10,14 @@ * - Sharing and territory management */ -import type { - User, - Role, - PermissionSet, - RowLevelSecurityPolicy, - SharingRule, - Territory, -} from '@objectstack/spec'; +import type { Auth, Permission } from '@objectstack/spec'; /** * Example 1: User Identity * * User accounts with authentication methods */ -export const sampleUsers: User[] = [ +export const sampleUsers: Auth.User[] = [ { id: 'user_001', email: 'admin@example.com', @@ -60,7 +53,7 @@ export const sampleUsers: User[] = [ * Note: Roles define reporting structure and hierarchy, not permissions. * Permissions are defined separately in PermissionSets. */ -export const roleHierarchy: Role[] = [ +export const roleHierarchy: Auth.Role[] = [ // Top-level admin role { name: 'system_administrator', @@ -90,7 +83,7 @@ export const roleHierarchy: Role[] = [ * * Granular object-level permissions */ -export const permissionSets: PermissionSet[] = [ +export const permissionSets: Permission.PermissionSet[] = [ { name: 'sales_user_permissions', label: 'Sales User Permissions', @@ -218,7 +211,7 @@ export const permissionSets: PermissionSet[] = [ * * Fine-grained data access control based on record ownership */ -export const rowLevelSecurityRules: RowLevelSecurityPolicy[] = [ +export const rowLevelSecurityRules: Permission.RowLevelSecurityPolicy[] = [ { name: 'opportunity_owner_access', label: 'Opportunity Owner Access', @@ -253,7 +246,7 @@ export const rowLevelSecurityRules: RowLevelSecurityPolicy[] = [ * * Grant additional access beyond RLS */ -export const sharingRules: SharingRule[] = [ +export const sharingRules: Permission.SharingRule[] = [ { name: 'share_opportunities_with_team', object: 'opportunity', @@ -321,7 +314,7 @@ export const sharingRules: SharingRule[] = [ * * Geographic or organizational territory assignment */ -export const territories: Territory[] = [ +export const territories: Permission.Territory[] = [ { name: 'north_america', label: 'North America', @@ -382,7 +375,7 @@ export class PermissionChecker { * Check if user has object permission */ hasObjectPermission( - user: User, + user: Auth.User, object: string, operation: 'create' | 'read' | 'update' | 'delete' ): boolean { @@ -411,7 +404,7 @@ export class PermissionChecker { /** * Check if user can access a specific record (RLS) */ - canAccessRecord(user: User, object: string, record: any): boolean { + canAccessRecord(user: Auth.User, object: string, record: any): boolean { // Apply RLS rules for user's roles const userRoles = user.roles || []; const applicableRules = rowLevelSecurityRules.filter( @@ -436,7 +429,7 @@ export class PermissionChecker { /** * Evaluate a rule against a record */ - private evaluateRule(rule: any, record: any, user: User): boolean { + private evaluateRule(rule: any, record: any, user: Auth.User): boolean { // Simplified evaluation logic // In real implementation, evaluate all conditions with operators return true; diff --git a/examples/basic/automation-example.ts b/examples/basic/automation-example.ts index be8e0ac67..709153f84 100644 --- a/examples/basic/automation-example.ts +++ b/examples/basic/automation-example.ts @@ -8,19 +8,14 @@ * - ETL processes (data integration) */ -import type { - WorkflowRule, - ApprovalProcess, - Flow, - ETLPipeline, -} from '@objectstack/spec'; +import type { Automation } from '@objectstack/spec'; /** * Example 1: Field Update Workflow * * Automatically update fields when conditions are met */ -export const fieldUpdateWorkflows: WorkflowRule[] = [ +export const fieldUpdateWorkflows: Automation.WorkflowRule[] = [ { name: 'set_opportunity_probability', object: 'opportunity', @@ -111,7 +106,7 @@ export const fieldUpdateWorkflows: WorkflowRule[] = [ * * Send notifications when events occur */ -export const emailAlertWorkflows: WorkflowRule[] = [ +export const emailAlertWorkflows: Automation.WorkflowRule[] = [ { name: 'notify_manager_large_opportunity', object: 'opportunity', @@ -217,7 +212,7 @@ export const emailAlertWorkflows: WorkflowRule[] = [ * * Automatically create related records */ -export const taskCreationWorkflows: WorkflowRule[] = [ +export const taskCreationWorkflows: Automation.WorkflowRule[] = [ { name: 'create_followup_tasks', object: 'lead', @@ -272,7 +267,7 @@ export const taskCreationWorkflows: WorkflowRule[] = [ * * Multi-step approval workflow for discounts */ -export const discountApprovalProcess: ApprovalProcess = { +export const discountApprovalProcess: Automation.ApprovalProcess = { name: 'opportunity_discount_approval', object: 'opportunity', description: 'Approval process for opportunity discounts', @@ -452,7 +447,7 @@ export const discountApprovalProcess: ApprovalProcess = { * * Visual automation with user interaction */ -export const leadConversionFlow: Flow = { +export const leadConversionFlow: Automation.Flow = { name: 'lead_conversion', type: 'screen', label: 'Convert Lead', @@ -585,7 +580,7 @@ export const leadConversionFlow: Flow = { * * Data integration and transformation */ -export const dailyLeadImportETL: ETLPipeline = { +export const dailyLeadImportETL: Automation.ETLPipeline = { name: 'daily_lead_import', description: 'Import leads from external system daily', diff --git a/examples/basic/stack-definition-example.ts b/examples/basic/stack-definition-example.ts index 4ab8a4f3c..fe6dbeb67 100644 --- a/examples/basic/stack-definition-example.ts +++ b/examples/basic/stack-definition-example.ts @@ -20,6 +20,8 @@ import { defineStack } from '@objectstack/spec'; */ export const minimalStack = defineStack({ manifest: { + id: 'com.example.my-app', + type: 'app', name: 'my-app', version: '1.0.0', description: 'Minimal ObjectStack application', @@ -36,11 +38,11 @@ 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', - author: 'Your Name', - license: 'MIT', }, // ============================================================================ @@ -60,24 +62,21 @@ export const taskManagementStack = defineStack({ trackHistory: true, }, - fields: [ - { - name: 'subject', + fields: { + subject: { type: 'text', label: 'Subject', description: 'Task title', required: true, maxLength: 255, }, - { - name: 'description', + description: { type: 'textarea', label: 'Description', description: 'Detailed description', maxLength: 5000, }, - { - name: 'status', + status: { type: 'select', label: 'Status', description: 'Current status', @@ -88,8 +87,7 @@ export const taskManagementStack = defineStack({ ], defaultValue: 'todo', }, - { - name: 'priority', + priority: { type: 'select', label: 'Priority', options: [ @@ -99,21 +97,16 @@ export const taskManagementStack = defineStack({ ], defaultValue: 'medium', }, - { - name: 'due_date', + due_date: { type: 'date', label: 'Due Date', }, - { - name: 'assigned_to', + assigned_to: { type: 'lookup', label: 'Assigned To', - reference: { - object: 'user', - field: 'id', - }, + reference: 'user', }, - ], + }, }, ], @@ -144,26 +137,24 @@ export const taskManagementStack = defineStack({ views: [ { - name: 'my_tasks', - object: 'task', - type: 'list', - label: 'My Tasks', - listType: 'grid', - fields: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], - filter: { - operator: 'AND', - conditions: [ - { - field: 'assigned_to', - operator: 'equals', - value: '$CurrentUser.id', - }, + list: { + type: 'grid', + columns: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], + filter: { + operator: 'AND', + conditions: [ + { + field: 'assigned_to', + operator: 'equals', + value: '$CurrentUser.id', + }, + ], + }, + sort: [ + { field: 'priority', direction: 'desc' }, + { field: 'due_date', direction: 'asc' }, ], }, - sort: [ - { field: 'priority', direction: 'desc' }, - { field: 'due_date', direction: 'asc' }, - ], }, ], @@ -179,25 +170,18 @@ export const taskManagementStack = defineStack({ widgets: [ { type: 'metric', - position: { row: 0, col: 0 }, + layout: { x: 0, y: 0, w: 6, h: 2 }, title: 'Total Tasks', - dataSource: { - type: 'aggregation', - object: 'task', - aggregation: 'count', - }, + object: 'task', + aggregate: 'count', }, { - type: 'chart', - position: { row: 0, col: 1 }, + type: 'pie', + layout: { x: 6, y: 0, w: 6, h: 2 }, title: 'Tasks by Status', - chartType: 'pie', - dataSource: { - type: 'groupBy', - object: 'task', - groupBy: 'status', - aggregation: 'count', - }, + object: 'task', + categoryField: 'status', + aggregate: 'count', }, ], }, @@ -215,24 +199,10 @@ export const taskManagementStack = defineStack({ type: 'scheduled', schedule: '0 9 * * *', // Daily at 9 AM }, - criteria: { - operator: 'AND', - conditions: [ - { - field: 'status', - operator: 'notEquals', - value: 'done', - }, - { - field: 'due_date', - operator: 'lessThan', - value: '$Today', - }, - ], - }, + criteria: 'status != "done" && due_date < $Today', actions: [ { - type: 'emailAlert', + type: 'email_alert', recipient: 'assigned_to', template: 'overdue_task_notification', }, @@ -248,33 +218,11 @@ export const taskManagementStack = defineStack({ name: 'task_manager', label: 'Task Manager', description: 'Can manage all tasks', - permissions: { - task: { - create: true, - read: true, - update: true, - delete: true, - }, - }, }, { name: 'task_user', label: 'Task User', description: 'Can manage own tasks', - permissions: { - task: { - create: true, - read: true, - update: { - condition: { - field: 'assigned_to', - operator: 'equals', - value: '$CurrentUser.id', - }, - }, - delete: false, - }, - }, }, ], }); @@ -286,6 +234,8 @@ export const taskManagementStack = defineStack({ */ 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', @@ -303,15 +253,13 @@ export const crmWithAIStack = defineStack({ apiEnabled: true, trackHistory: true, }, - fields: [ - { - name: 'name', + fields: { + name: { type: 'text', label: 'Account Name', required: true, }, - { - name: 'industry', + industry: { type: 'select', label: 'Industry', options: [ @@ -320,12 +268,11 @@ export const crmWithAIStack = defineStack({ { value: 'healthcare', label: 'Healthcare' }, ], }, - { - name: 'annual_revenue', + annual_revenue: { type: 'currency', label: 'Annual Revenue', }, - ], + }, }, { name: 'opportunity', @@ -337,29 +284,22 @@ export const crmWithAIStack = defineStack({ apiEnabled: true, trackHistory: true, }, - fields: [ - { - name: 'name', + fields: { + name: { type: 'text', label: 'Opportunity Name', required: true, }, - { - name: 'account', + account: { type: 'lookup', label: 'Account', - reference: { - object: 'account', - field: 'id', - }, + reference: 'account', }, - { - name: 'amount', + amount: { type: 'currency', label: 'Amount', }, - { - name: 'stage', + stage: { type: 'select', label: 'Stage', options: [ @@ -372,12 +312,11 @@ export const crmWithAIStack = defineStack({ ], defaultValue: 'prospecting', }, - { - name: 'close_date', + close_date: { type: 'date', label: 'Expected Close Date', }, - ], + }, }, ], @@ -385,18 +324,10 @@ export const crmWithAIStack = defineStack({ agents: [ { name: 'sales_assistant', - type: 'conversational', label: 'Sales Assistant', - description: 'AI assistant for sales operations', + role: 'Sales Operations Assistant', - capabilities: { - objectAccess: ['account', 'opportunity'], - canCreate: true, - canUpdate: true, - canAnalyze: true, - }, - - systemPrompt: `You are a helpful sales assistant with access to the CRM system. + 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 @@ -409,24 +340,17 @@ Always be professional and data-driven in your responses.`, { name: 'search_accounts', description: 'Search for accounts by name or industry', - parameters: { - query: 'string', - industry: 'string?', - }, + type: 'query', }, { name: 'get_opportunity_pipeline', description: 'Get pipeline statistics by stage', + type: 'query', }, { name: 'create_opportunity', description: 'Create a new sales opportunity', - parameters: { - name: 'string', - account: 'string', - amount: 'number', - stage: 'string', - }, + type: 'action', }, ], }, From 5e98567d72a019b4713fac94de677747219e6d48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:20:42 +0000 Subject: [PATCH 14/14] Fix all remaining TypeScript errors in example files Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/basic/ai-rag-example.ts | 53 +- examples/basic/auth-permission-example.ts | 63 +- examples/basic/automation-example.ts | 666 +++++---------------- examples/basic/stack-definition-example.ts | 44 +- 4 files changed, 215 insertions(+), 611 deletions(-) diff --git a/examples/basic/ai-rag-example.ts b/examples/basic/ai-rag-example.ts index c6a784c8f..3a36c18ed 100644 --- a/examples/basic/ai-rag-example.ts +++ b/examples/basic/ai-rag-example.ts @@ -36,6 +36,9 @@ export const knowledgeBaseRAG: AI.RAGPipelineConfig = { namespace: 'production', dimensions: 1536, metric: 'cosine', + batchSize: 100, + connectionPoolSize: 10, + timeout: 30000, }, // Chunking strategy @@ -69,6 +72,10 @@ export const knowledgeBaseRAG: AI.RAGPipelineConfig = { metadataFilters: { status: 'published', }, + + // Cache Configuration + enableCache: true, + cacheTTL: 3600, }; /** @@ -144,6 +151,8 @@ export const sampleQueries: AI.RAGQueryRequest[] = [ query: 'What is ObjectStack?', pipelineName: 'knowledge_base_rag', topK: 5, + includeMetadata: true, + includeSources: true, }, { // Question with metadata filtering @@ -153,6 +162,8 @@ export const sampleQueries: AI.RAGQueryRequest[] = [ metadataFilters: { category: 'Tutorial', }, + includeMetadata: true, + includeSources: true, }, { // Advanced query @@ -207,10 +218,6 @@ It uses a three-layer architecture: ObjectQL for data, ObjectUI for presentation [Source: https://docs.objectstack.dev/architecture] The platform supports multiple databases and provides built-in AI capabilities.`, - - // Metadata - processingTimeMs: 150, - totalResults: 2, }; /** @@ -220,20 +227,12 @@ The platform supports multiple databases and provides built-in AI capabilities.` */ export const ragEnabledAgent: AI.Agent = { name: 'documentation_assistant', - type: 'conversational', label: 'Documentation Assistant', - description: 'AI assistant powered by RAG for answering questions about ObjectStack', - - // Agent capabilities - capabilities: { - objectAccess: [], - canCreate: false, - canUpdate: false, - canAnalyze: true, - }, - + role: 'Documentation Support Specialist', + active: true, + // System prompt with RAG instructions - systemPrompt: `You are a helpful documentation assistant for ObjectStack. + instructions: `You are a helpful documentation assistant for ObjectStack. You have access to the knowledge base through RAG (Retrieval-Augmented Generation). When answering questions: @@ -245,18 +244,9 @@ When answering questions: Always format your responses in a clear, structured way.`, // RAG Configuration - rag: { - pipeline: 'knowledge_base_rag', - enabled: true, - - // When to trigger RAG - trigger: 'always', // or 'auto', 'manual' - - // Number of chunks to retrieve - topK: 5, - - // Include source attribution - includeSources: true, + knowledge: { + indexes: ['knowledge-base'], + topics: ['ObjectStack', 'documentation'], }, // Model configuration @@ -270,12 +260,9 @@ Always format your responses in a clear, structured way.`, // Tools (optional - for function calling) tools: [ { + type: 'query', name: 'search_documentation', description: 'Search the ObjectStack documentation for specific topics', - parameters: { - query: 'string', - category: 'string?', - }, }, ], }; @@ -297,7 +284,7 @@ export function demonstrateRAGUsage() { console.log('- Embedding query...'); console.log('- Searching vector database...'); console.log(`- Retrieved ${sampleResults.results.length} relevant chunks`); - console.log(`- Processing time: ${sampleResults.processingTimeMs}ms\n`); + console.log('- Processing complete\n'); // Step 3: Context is assembled console.log('Assembled Context:'); diff --git a/examples/basic/auth-permission-example.ts b/examples/basic/auth-permission-example.ts index 6220416b2..4918b634c 100644 --- a/examples/basic/auth-permission-example.ts +++ b/examples/basic/auth-permission-example.ts @@ -218,6 +218,7 @@ export const rowLevelSecurityRules: Permission.RowLevelSecurityPolicy[] = [ 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`, @@ -233,6 +234,7 @@ export const rowLevelSecurityRules: Permission.RowLevelSecurityPolicy[] = [ 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'`, @@ -318,50 +320,35 @@ export const territories: Permission.Territory[] = [ { name: 'north_america', label: 'North America', - description: 'North American sales territory', + modelId: 'global_sales_territories', + type: 'geography', - // Territory definition - criteria: { - operator: 'OR', - conditions: [ - { - field: 'billing_country', - operator: 'in', - value: ['USA', 'Canada', 'Mexico'], - }, - ], - }, + // Territory assignment rule + assignmentRule: `billing_country IN ('USA', 'Canada', 'Mexico')`, // Assigned users - members: ['user_002', 'user_003'], + assignedUsers: ['user_002', 'user_003'], - // Parent territory (for hierarchy) - parentTerritory: undefined, + // Access levels + accountAccess: 'edit', + opportunityAccess: 'edit', + caseAccess: 'read', }, { name: 'west_coast', label: 'West Coast', - description: 'US West Coast territory', + modelId: 'global_sales_territories', + type: 'geography', + parent: 'north_america', - criteria: { - operator: 'AND', - conditions: [ - { - field: 'billing_country', - operator: 'equals', - value: 'USA', - }, - { - field: 'billing_state', - operator: 'in', - value: ['CA', 'OR', 'WA', 'NV', 'AZ'], - }, - ], - }, + assignmentRule: `billing_country = 'USA' AND billing_state IN ('CA', 'OR', 'WA', 'NV', 'AZ')`, + + assignedUsers: ['user_003'], - members: ['user_003'], - parentTerritory: 'north_america', + accountAccess: 'edit', + opportunityAccess: 'edit', + caseAccess: 'read', }, ]; @@ -404,11 +391,11 @@ export class PermissionChecker { /** * Check if user can access a specific record (RLS) */ - canAccessRecord(user: Auth.User, object: string, record: any): boolean { + 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) => userRoles.includes(r)) + (rls) => rls.object === object && rls.roles?.some((r: string) => userRoles.includes(r)) ); // If no RLS rules, check base permissions @@ -418,7 +405,7 @@ export class PermissionChecker { // Evaluate RLS rules for (const rule of applicableRules) { - if (this.evaluateRule(rule.rule, record, user)) { + if (this.evaluateRule(rule.using, record, user)) { return true; } } @@ -429,7 +416,7 @@ export class PermissionChecker { /** * Evaluate a rule against a record */ - private evaluateRule(rule: any, record: any, user: Auth.User): boolean { + 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; @@ -440,7 +427,7 @@ export class PermissionChecker { * Example 8: Usage Demonstration */ export function demonstratePermissions() { - const user = sampleUsers[2]; // Sales Rep + const user = { ...sampleUsers[2], roles: ['sales_rep'] }; // Sales Rep with role const checker = new PermissionChecker(); console.log('=== Permission Check Demo ===\n'); diff --git a/examples/basic/automation-example.ts b/examples/basic/automation-example.ts index 709153f84..7026556ca 100644 --- a/examples/basic/automation-example.ts +++ b/examples/basic/automation-example.ts @@ -18,84 +18,59 @@ import type { Automation } from '@objectstack/spec'; export const fieldUpdateWorkflows: Automation.WorkflowRule[] = [ { name: 'set_opportunity_probability', - object: 'opportunity', - description: 'Automatically set probability based on stage', + objectName: 'opportunity', + active: true, + reevaluateOnChange: false, // When to trigger - trigger: { - type: 'fieldChange', - fields: ['stage'], - }, + triggerType: 'on_update', // Conditions to check - criteria: { - operator: 'ALWAYS', // Run for all records - }, + criteria: 'true', // Run for all records // What to do actions: [ { - type: 'fieldUpdate', - updates: [ - { - field: 'probability', - value: { - formula: ` - 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_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', - object: 'account', - description: 'Update account rating based on revenue', + objectName: 'account', + active: true, + reevaluateOnChange: false, - trigger: { - type: 'fieldChange', - fields: ['annual_revenue'], - }, + triggerType: 'on_update', - criteria: { - operator: 'AND', - conditions: [ - { - field: 'annual_revenue', - operator: 'greaterThan', - value: 0, - }, - ], - }, + criteria: 'annual_revenue > 0', actions: [ { - type: 'fieldUpdate', - updates: [ - { - field: 'rating', - value: { - formula: ` - CASE - WHEN annual_revenue > 10000000 THEN 'hot' - WHEN annual_revenue > 1000000 THEN 'warm' - ELSE 'cold' - END - `, - }, - }, - ], + 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 + `, }, ], }, @@ -109,99 +84,40 @@ export const fieldUpdateWorkflows: Automation.WorkflowRule[] = [ export const emailAlertWorkflows: Automation.WorkflowRule[] = [ { name: 'notify_manager_large_opportunity', - object: 'opportunity', - description: 'Notify manager when large opportunity is created', + objectName: 'opportunity', + active: true, + reevaluateOnChange: false, - trigger: { - type: 'onCreate', - }, + triggerType: 'on_create', - criteria: { - operator: 'AND', - conditions: [ - { - field: 'amount', - operator: 'greaterThan', - value: 100000, - }, - ], - }, + criteria: 'amount > 100000', actions: [ { - type: 'emailAlert', + name: 'notify_manager', + type: 'email_alert', template: 'large_opportunity_alert', - recipients: [ - { - type: 'field', - field: 'owner.manager.email', - }, - { - type: 'role', - role: 'sales_director', - }, - ], - subject: 'Large Opportunity Created: {{name}}', - body: ` - A large opportunity has been created: - - Name: {{name}} - Amount: {{amount | currency}} - Owner: {{owner.name}} - Expected Close: {{close_date | date}} - - Please review and approve. - `, + recipients: ['owner.manager.email', 'role:sales_director'], }, ], }, { name: 'notify_overdue_tasks', - object: 'task', - description: 'Daily notification for overdue tasks', + objectName: 'task', + active: true, + reevaluateOnChange: false, - trigger: { - type: 'scheduled', - schedule: '0 9 * * *', // Daily at 9 AM (cron format) - }, + triggerType: 'schedule', - criteria: { - operator: 'AND', - conditions: [ - { - field: 'status', - operator: 'notEquals', - value: 'completed', - }, - { - field: 'due_date', - operator: 'lessThan', - value: '$Today', - }, - ], - }, + criteria: 'status != "completed" && due_date < $Today', actions: [ { - type: 'emailAlert', + name: 'send_overdue_reminder', + type: 'email_alert', template: 'overdue_task_reminder', - recipients: [ - { - type: 'field', - field: 'assigned_to.email', - }, - ], - subject: 'Overdue Task: {{subject}}', - body: ` - You have an overdue task: - - Subject: {{subject}} - Due Date: {{due_date | date}} - Priority: {{priority}} - - Please update the status or due date. - `, + recipients: ['assigned_to.email'], }, ], }, @@ -215,48 +131,36 @@ export const emailAlertWorkflows: Automation.WorkflowRule[] = [ export const taskCreationWorkflows: Automation.WorkflowRule[] = [ { name: 'create_followup_tasks', - object: 'lead', - description: 'Create follow-up tasks when lead is created', + objectName: 'lead', + active: true, + reevaluateOnChange: false, - trigger: { - type: 'onCreate', - }, + triggerType: 'on_create', - criteria: { - operator: 'AND', - conditions: [ - { - field: 'status', - operator: 'equals', - value: 'new', - }, - ], - }, + criteria: 'status == "new"', actions: [ { - type: 'createRecord', - object: 'task', - values: { - subject: 'Initial Contact - {{name}}', - description: 'Make initial contact with lead', - due_date: '$Today + 1', - priority: 'high', - assigned_to: '{{owner}}', - related_to: '{{id}}', - }, + 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}}', }, { - type: 'createRecord', - object: 'task', - values: { - subject: 'Qualification Call - {{name}}', - description: 'Schedule qualification call', - due_date: '$Today + 3', - priority: 'medium', - assigned_to: '{{owner}}', - related_to: '{{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}}', }, ], }, @@ -269,20 +173,14 @@ export const taskCreationWorkflows: Automation.WorkflowRule[] = [ */ 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: { - operator: 'AND', - conditions: [ - { - field: 'discount_percent', - operator: 'greaterThan', - value: 0, - }, - ], - }, + entryCriteria: 'discount_percent > 0', // Approval steps (sequential) steps: [ @@ -290,54 +188,49 @@ export const discountApprovalProcess: Automation.ApprovalProcess = { 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 - stepCriteria: { - operator: 'AND', - conditions: [ - { - field: 'discount_percent', - operator: 'lessOrEqual', - value: 20, - }, - ], - }, + entryCriteria: 'discount_percent <= 20', // Who can approve approvers: [ { type: 'field', - field: 'owner.manager', + value: 'owner.manager', }, ], // Approval actions - approvalActions: [ + onApprove: [ { - type: 'fieldUpdate', - updates: [ - { - field: 'approval_status', - value: 'approved', - }, - ], + type: 'field_update', + name: 'set_approved', + config: { + field: 'approval_status', + value: 'approved', + }, }, ], // Rejection actions - rejectionActions: [ + onReject: [ { - type: 'fieldUpdate', - updates: [ - { - field: 'approval_status', - value: 'rejected', - }, - { - field: 'discount_percent', - value: 0, - }, - ], + type: 'field_update', + name: 'set_rejected', + config: { + field: 'approval_status', + value: 'rejected', + }, + }, + { + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, + }, }, ], }, @@ -346,325 +239,93 @@ export const discountApprovalProcess: Automation.ApprovalProcess = { name: 'director_approval', label: 'Director Approval', description: 'Requires director approval for discounts over 20%', + behavior: 'first_response', + rejectionBehavior: 'reject_process', - stepCriteria: { - operator: 'AND', - conditions: [ - { - field: 'discount_percent', - operator: 'greaterThan', - value: 20, - }, - ], - }, + entryCriteria: 'discount_percent > 20', approvers: [ { type: 'role', - role: 'sales_director', - }, - ], - - approvalActions: [ - { - type: 'fieldUpdate', - updates: [ - { - field: 'approval_status', - value: 'approved', - }, - ], - }, - { - type: 'emailAlert', - template: 'discount_approved', - recipients: [ - { - type: 'field', - field: 'owner.email', - }, - ], + value: 'sales_director', }, ], - rejectionActions: [ - { - type: 'fieldUpdate', - updates: [ - { - field: 'approval_status', - value: 'rejected', - }, - { - field: 'discount_percent', - value: 0, - }, - ], - }, - { - type: 'emailAlert', - template: 'discount_rejected', - recipients: [ - { - type: 'field', - field: 'owner.email', - }, - ], - }, - ], - }, - ], - - // Final approval actions - finalApprovalActions: [ - { - type: 'fieldUpdate', - updates: [ - { - field: 'approved_discount', - value: '{{discount_percent}}', - }, - ], - }, - ], - - // Final rejection actions - finalRejectionActions: [ - { - type: 'fieldUpdate', - updates: [ + onApprove: [ { - field: 'discount_percent', - value: 0, - }, - ], - }, - ], -}; - -/** - * Example 5: Screen Flow - * - * Visual automation with user interaction - */ -export const leadConversionFlow: Automation.Flow = { - name: 'lead_conversion', - type: 'screen', - label: 'Convert Lead', - description: 'Guide users through lead conversion process', - - // Entry point - startElement: 'welcome_screen', - - // Flow elements - elements: [ - { - name: 'welcome_screen', - type: 'screen', - label: 'Convert Lead to Opportunity', - - fields: [ - { - name: 'opportunity_name', - type: 'text', - label: 'Opportunity Name', - required: true, - defaultValue: '{{Lead.company}} - {{Lead.product_interest}}', - }, - { - name: 'amount', - type: 'currency', - label: 'Expected Amount', - required: true, + type: 'field_update', + name: 'set_approved', + config: { + field: 'approval_status', + value: 'approved', + }, }, { - name: 'close_date', - type: 'date', - label: 'Expected Close Date', - required: true, + type: 'email_alert', + name: 'notify_owner_approval', + config: { + template: 'discount_approved', + recipients: ['owner.email'], + }, }, ], - nextElement: 'create_records', - }, - - { - name: 'create_records', - type: 'recordCreate', - label: 'Create Account and Opportunity', - - creates: [ + onReject: [ { - object: 'account', - values: { - name: '{{Lead.company}}', - phone: '{{Lead.phone}}', - website: '{{Lead.website}}', + type: 'field_update', + name: 'set_rejected', + config: { + field: 'approval_status', + value: 'rejected', }, - assignTo: 'accountId', }, { - object: 'contact', - values: { - first_name: '{{Lead.first_name}}', - last_name: '{{Lead.last_name}}', - email: '{{Lead.email}}', - account: '{{accountId}}', + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, }, - assignTo: 'contactId', }, { - object: 'opportunity', - values: { - name: '{{opportunity_name}}', - amount: '{{amount}}', - close_date: '{{close_date}}', - account: '{{accountId}}', - stage: 'qualification', + type: 'email_alert', + name: 'notify_owner_rejection', + config: { + template: 'discount_rejected', + recipients: ['owner.email'], }, - assignTo: 'opportunityId', - }, - ], - - nextElement: 'update_lead', - }, - - { - name: 'update_lead', - type: 'recordUpdate', - label: 'Mark Lead as Converted', - - object: 'lead', - recordId: '{{Lead.id}}', - values: { - status: 'converted', - converted_account: '{{accountId}}', - converted_opportunity: '{{opportunityId}}', - converted_date: '$Now', - }, - - nextElement: 'success_screen', - }, - - { - name: 'success_screen', - type: 'screen', - label: 'Conversion Successful', - - message: ` - Lead converted successfully! - - Created: - - Account: {{Account.name}} - - Contact: {{Contact.name}} - - Opportunity: {{Opportunity.name}} - `, - - buttons: [ - { - label: 'View Opportunity', - action: 'navigate', - target: '/opportunities/{{opportunityId}}', - }, - { - label: 'Close', - action: 'finish', }, ], }, ], -}; - -/** - * Example 6: ETL Pipeline - * - * Data integration and transformation - */ -export const dailyLeadImportETL: Automation.ETLPipeline = { - name: 'daily_lead_import', - description: 'Import leads from external system daily', - - // Schedule - schedule: '0 2 * * *', // Daily at 2 AM - // Extract - extract: { - source: { - type: 'api', - endpoint: 'https://marketing.example.com/api/leads', - method: 'GET', - authentication: { - type: 'apiKey', - header: 'X-API-Key', - key: '$Env.MARKETING_API_KEY', - }, - }, - - // Incremental load - incrementalField: 'created_at', - lastRunTimestamp: true, - }, - - // Transform - transform: [ - { - type: 'fieldMapping', - mappings: [ - { source: 'firstName', target: 'first_name' }, - { source: 'lastName', target: 'last_name' }, - { source: 'emailAddress', target: 'email' }, - { source: 'phoneNumber', target: 'phone' }, - { source: 'companyName', target: 'company' }, - ], - }, - { - type: 'fieldTransform', - transforms: [ - { - field: 'status', - value: { - formula: ` - CASE source - WHEN 'web' THEN 'new' - WHEN 'event' THEN 'contacted' - ELSE 'unqualified' - END - `, - }, - }, - ], - }, + // Final approval actions + onFinalApprove: [ { - type: 'deduplication', - matchFields: ['email'], - strategy: 'skip', // or 'update', 'merge' + type: 'field_update', + name: 'save_approved_discount', + config: { + field: 'approved_discount', + value: '{{discount_percent}}', + }, }, ], - // Load - load: { - target: { - object: 'lead', - }, - - // Error handling - onError: { - strategy: 'continue', // or 'stop' - logErrors: true, - notifyOnFailure: true, - notificationRecipients: ['admin@example.com'], - }, - }, - - // Post-processing - postProcess: [ + // Final rejection actions + onFinalReject: [ { - type: 'workflow', - workflow: 'assign_leads_to_reps', + type: 'field_update', + name: 'clear_discount', + config: { + field: 'discount_percent', + value: 0, + }, }, ], }; + + /** * Example 7: Usage Demonstration */ @@ -673,30 +334,19 @@ export function demonstrateAutomation() { console.log('1. Field Update Workflows:'); console.log(` - ${fieldUpdateWorkflows.length} rules configured`); - console.log(` - Example: "${fieldUpdateWorkflows[0].description}"`); + 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].description}"`); + 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}"`); - console.log(''); - - console.log('4. Screen Flow:'); - console.log(` - Name: ${leadConversionFlow.name}`); - console.log(` - Elements: ${leadConversionFlow.elements?.length || 0}`); - console.log(` - Type: ${leadConversionFlow.type}`); + console.log(` - Description: "${discountApprovalProcess.description || 'N/A'}"`); console.log(''); - - console.log('5. ETL Process:'); - console.log(` - Name: ${dailyLeadImportETL.name}`); - console.log(` - Schedule: ${dailyLeadImportETL.schedule}`); - console.log(` - Source: ${dailyLeadImportETL.extract?.source.type}`); } // Run demonstration (uncomment to run) @@ -708,6 +358,4 @@ export default { emailAlertWorkflows, taskCreationWorkflows, discountApprovalProcess, - leadConversionFlow, - dailyLeadImportETL, }; diff --git a/examples/basic/stack-definition-example.ts b/examples/basic/stack-definition-example.ts index fe6dbeb67..e53630412 100644 --- a/examples/basic/stack-definition-example.ts +++ b/examples/basic/stack-definition-example.ts @@ -52,10 +52,8 @@ export const taskManagementStack = defineStack({ { name: 'task', label: 'Task', - labelPlural: 'Tasks', icon: 'check-square', description: 'Work items and to-dos', - nameField: 'subject', enable: { apiEnabled: true, @@ -140,19 +138,12 @@ export const taskManagementStack = defineStack({ list: { type: 'grid', columns: ['subject', 'status', 'priority', 'due_date', 'assigned_to'], - filter: { - operator: 'AND', - conditions: [ - { - field: 'assigned_to', - operator: 'equals', - value: '$CurrentUser.id', - }, - ], - }, + filter: [ + { field: 'assigned_to', operator: 'equals', value: '$CurrentUser.id' }, + ], sort: [ - { field: 'priority', direction: 'desc' }, - { field: 'due_date', direction: 'asc' }, + { field: 'priority', order: 'desc' }, + { field: 'due_date', order: 'asc' }, ], }, }, @@ -163,10 +154,6 @@ export const taskManagementStack = defineStack({ name: 'task_overview', label: 'Task Overview', description: 'Task statistics and metrics', - layout: { - rows: 2, - columns: 2, - }, widgets: [ { type: 'metric', @@ -193,17 +180,16 @@ export const taskManagementStack = defineStack({ workflows: [ { name: 'notify_overdue_tasks', - object: 'task', - description: 'Notify users of overdue tasks', - trigger: { - type: 'scheduled', - schedule: '0 9 * * *', // Daily at 9 AM - }, + objectName: 'task', + active: true, + reevaluateOnChange: false, + triggerType: 'schedule', criteria: 'status != "done" && due_date < $Today', actions: [ { + name: 'send_overdue_notification', type: 'email_alert', - recipient: 'assigned_to', + recipients: ['assigned_to.email'], template: 'overdue_task_notification', }, ], @@ -246,9 +232,7 @@ export const crmWithAIStack = defineStack({ { name: 'account', label: 'Account', - labelPlural: 'Accounts', icon: 'building', - nameField: 'name', enable: { apiEnabled: true, trackHistory: true, @@ -277,9 +261,7 @@ export const crmWithAIStack = defineStack({ { name: 'opportunity', label: 'Opportunity', - labelPlural: 'Opportunities', icon: 'target', - nameField: 'name', enable: { apiEnabled: true, trackHistory: true, @@ -402,14 +384,14 @@ export function demonstrateStackUsage() { const taskObject = taskManagementStack.objects?.[0]; if (taskObject) { console.log('Task Object:', taskObject.name); - console.log('Task Fields:', taskObject.fields.length); + 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('Capabilities:', agent.capabilities); + console.log('Tools:', agent.tools?.length || 0); } }