From c14bd2f1cd67622d539954e68a70a11a8993f4a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:27:53 +0000 Subject: [PATCH 1/4] Initial plan From 940267dab100711b45f5732f43d09f4401f5ff2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:31:43 +0000 Subject: [PATCH 2/4] Complete UI category examples (add report and widget examples) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/ui/metadata-examples/README.md | 2 + examples/ui/metadata-examples/src/index.ts | 2 + .../metadata-examples/src/report.examples.ts | 339 ++++++++++++ .../metadata-examples/src/widget.examples.ts | 506 ++++++++++++++++++ 4 files changed, 849 insertions(+) create mode 100644 examples/ui/metadata-examples/src/report.examples.ts create mode 100644 examples/ui/metadata-examples/src/widget.examples.ts diff --git a/examples/ui/metadata-examples/README.md b/examples/ui/metadata-examples/README.md index 66337e404..12d0927c5 100644 --- a/examples/ui/metadata-examples/README.md +++ b/examples/ui/metadata-examples/README.md @@ -12,6 +12,8 @@ These are **configuration examples** (not implementations) showing how to define - **page.examples.ts** - 9 page layouts: Record, Home, App, and Utility pages - **app.examples.ts** - 7 applications: Simple to comprehensive apps with hierarchical navigation - **theme.examples.ts** - 7 themes: Light, dark, colorful, minimal, and WCAG AAA compliant +- **report.examples.ts** - 12 examples: Tabular, Summary, Matrix reports with charts and filtering +- **widget.examples.ts** - 20 examples: Field widget props for text, number, date, selection, and custom widgets ## Usage diff --git a/examples/ui/metadata-examples/src/index.ts b/examples/ui/metadata-examples/src/index.ts index d94482f2e..e70e04cb1 100644 --- a/examples/ui/metadata-examples/src/index.ts +++ b/examples/ui/metadata-examples/src/index.ts @@ -12,3 +12,5 @@ export * from './dashboard.examples'; export * from './page.examples'; export * from './app.examples'; export * from './theme.examples'; +export * from './report.examples'; +export * from './widget.examples'; diff --git a/examples/ui/metadata-examples/src/report.examples.ts b/examples/ui/metadata-examples/src/report.examples.ts new file mode 100644 index 000000000..8bc20fb65 --- /dev/null +++ b/examples/ui/metadata-examples/src/report.examples.ts @@ -0,0 +1,339 @@ +// @ts-nocheck +import { Report } from '@objectstack/spec/ui'; + +/** + * Report Examples - Demonstrating ObjectStack Report Protocol + * + * Reports define data analysis and visualization configurations. + * Inspired by Salesforce Reports and ServiceNow Reporting. + */ + +// ============================================================================ +// TABULAR REPORTS (Simple Lists) +// ============================================================================ + +/** + * Example 1: Simple Tabular Report + * Basic list of records with selected columns + * Use Case: Quick data review, export to CSV + */ +export const AllContactsReport: Report = { + name: 'all_contacts', + label: 'All Contacts', + description: 'Complete list of all contacts in the system', + objectName: 'contact', + type: 'tabular', + columns: [ + { field: 'full_name', label: 'Name' }, + { field: 'email' }, + { field: 'phone' }, + { field: 'account_name', label: 'Company' }, + { field: 'created_at', label: 'Created' }, + ], +}; + +/** + * Example 2: Tabular Report with Filtering + * Filtered list showing specific records + * Use Case: Active opportunities, hot leads + */ +export const ActiveOpportunitiesReport: Report = { + name: 'active_opportunities', + label: 'Active Opportunities', + objectName: 'opportunity', + type: 'tabular', + columns: [ + { field: 'name', label: 'Opportunity Name' }, + { field: 'account_name', label: 'Account' }, + { field: 'amount', label: 'Value' }, + { field: 'stage', label: 'Stage' }, + { field: 'close_date', label: 'Close Date' }, + { field: 'probability', label: 'Probability %' }, + ], + filter: { + $and: [ + { field: 'stage', operator: 'ne', value: 'Closed Won' }, + { field: 'stage', operator: 'ne', value: 'Closed Lost' }, + ], + }, +}; + +// ============================================================================ +// SUMMARY REPORTS (Grouped Data) +// ============================================================================ + +/** + * Example 3: Summary Report with Row Grouping + * Data grouped by a single field with aggregations + * Use Case: Sales by rep, cases by status + */ +export const OpportunitiesByStageReport: Report = { + name: 'opportunities_by_stage', + label: 'Opportunities by Stage', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'name', label: 'Opportunity Name' }, + { field: 'amount', label: 'Amount', aggregate: 'sum' }, + { field: 'id', label: 'Count', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'stage', sortOrder: 'asc' }, + ], +}; + +/** + * Example 4: Multi-Level Summary Report + * Data grouped by multiple fields + * Use Case: Sales by region and rep, cases by priority and assigned to + */ +export const SalesByRegionAndRepReport: Report = { + name: 'sales_by_region_rep', + label: 'Sales by Region and Rep', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'amount', label: 'Total Amount', aggregate: 'sum' }, + { field: 'amount', label: 'Average Deal', aggregate: 'avg' }, + { field: 'id', label: 'Count', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'region', sortOrder: 'asc' }, + { field: 'owner_name', sortOrder: 'asc' }, + ], + filter: { + field: 'stage', + operator: 'eq', + value: 'Closed Won', + }, +}; + +/** + * Example 5: Summary Report with Date Grouping + * Data grouped by date fields with granularity + * Use Case: Trends over time, monthly/quarterly analysis + */ +export const MonthlyRevenueReport: Report = { + name: 'monthly_revenue', + label: 'Monthly Revenue Trend', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'amount', label: 'Total Revenue', aggregate: 'sum' }, + { field: 'amount', label: 'Average Deal Size', aggregate: 'avg' }, + { field: 'id', label: 'Deals Closed', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'close_date', sortOrder: 'desc', dateGranularity: 'month' }, + ], + filter: { + field: 'stage', + operator: 'eq', + value: 'Closed Won', + }, +}; + +// ============================================================================ +// MATRIX REPORTS (Two-Dimensional Grouping) +// ============================================================================ + +/** + * Example 6: Matrix Report + * Data grouped by rows AND columns + * Use Case: Sales by product and quarter, cases by status and priority + */ +export const SalesByProductAndQuarterReport: Report = { + name: 'sales_by_product_quarter', + label: 'Sales by Product and Quarter', + objectName: 'opportunity', + type: 'matrix', + columns: [ + { field: 'amount', label: 'Total Revenue', aggregate: 'sum' }, + { field: 'id', label: 'Deals', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'product_name', sortOrder: 'asc' }, + ], + groupingsAcross: [ + { field: 'close_date', sortOrder: 'asc', dateGranularity: 'quarter' }, + ], + filter: { + field: 'stage', + operator: 'eq', + value: 'Closed Won', + }, +}; + +// ============================================================================ +// REPORTS WITH CHARTS +// ============================================================================ + +/** + * Example 7: Report with Bar Chart + * Tabular/summary report with embedded visualization + * Use Case: Visual sales pipeline, case analysis + */ +export const PipelineByStageWithChartReport: Report = { + name: 'pipeline_by_stage_chart', + label: 'Sales Pipeline by Stage', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'amount', label: 'Total Value', aggregate: 'sum' }, + { field: 'id', label: 'Opportunities', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'stage', sortOrder: 'asc' }, + ], + chart: { + type: 'bar', + title: 'Pipeline Value by Stage', + showLegend: true, + xAxis: 'stage', + yAxis: 'amount', + }, +}; + +/** + * Example 8: Report with Pie Chart + * Summary report with pie chart visualization + * Use Case: Distribution analysis, win/loss breakdown + */ +export const LeadSourceDistributionReport: Report = { + name: 'lead_source_distribution', + label: 'Lead Source Distribution', + objectName: 'lead', + type: 'summary', + columns: [ + { field: 'id', label: 'Lead Count', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'lead_source', sortOrder: 'desc' }, + ], + chart: { + type: 'pie', + title: 'Leads by Source', + showLegend: true, + xAxis: 'lead_source', + yAxis: 'id', + }, +}; + +/** + * Example 9: Report with Line Chart + * Time-series trend analysis + * Use Case: Growth trends, historical analysis + */ +export const QuarterlyGrowthTrendReport: Report = { + name: 'quarterly_growth_trend', + label: 'Quarterly Revenue Growth', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'amount', label: 'Revenue', aggregate: 'sum' }, + { field: 'id', label: 'Deals', aggregate: 'count' }, + ], + groupingsDown: [ + { field: 'close_date', sortOrder: 'asc', dateGranularity: 'quarter' }, + ], + filter: { + field: 'stage', + operator: 'eq', + value: 'Closed Won', + }, + chart: { + type: 'line', + title: 'Revenue Trend Over Time', + showLegend: true, + xAxis: 'close_date', + yAxis: 'amount', + }, +}; + +/** + * Example 10: Report with Funnel Chart + * Conversion analysis through stages + * Use Case: Sales funnel, conversion tracking + */ +export const SalesFunnelReport: Report = { + name: 'sales_funnel', + label: 'Sales Funnel Analysis', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'id', label: 'Opportunities', aggregate: 'count' }, + { field: 'amount', label: 'Total Value', aggregate: 'sum' }, + ], + groupingsDown: [ + { field: 'stage', sortOrder: 'asc' }, + ], + chart: { + type: 'funnel', + title: 'Sales Funnel', + showLegend: false, + xAxis: 'stage', + yAxis: 'id', + }, +}; + +// ============================================================================ +// ADVANCED REPORTS +// ============================================================================ + +/** + * Example 11: Complex Filtered Report + * Multiple filter conditions with AND/OR logic + * Use Case: Targeted analysis with complex criteria + */ +export const HighValueOpportunitiesReport: Report = { + name: 'high_value_opportunities', + label: 'High-Value Opportunities (>$100K)', + objectName: 'opportunity', + type: 'tabular', + columns: [ + { field: 'name', label: 'Opportunity' }, + { field: 'account_name', label: 'Account' }, + { field: 'amount', label: 'Value' }, + { field: 'stage', label: 'Stage' }, + { field: 'owner_name', label: 'Owner' }, + { field: 'close_date', label: 'Close Date' }, + ], + filter: { + $and: [ + { field: 'amount', operator: 'gte', value: 100000 }, + { + $or: [ + { field: 'stage', operator: 'eq', value: 'Negotiation' }, + { field: 'stage', operator: 'eq', value: 'Proposal' }, + ], + }, + ], + }, +}; + +/** + * Example 12: Report with Multiple Aggregations + * Various aggregate functions on different columns + * Use Case: Statistical analysis, performance metrics + */ +export const SalesPerformanceReport: Report = { + name: 'sales_performance', + label: 'Sales Rep Performance', + objectName: 'opportunity', + type: 'summary', + columns: [ + { field: 'id', label: 'Total Deals', aggregate: 'count' }, + { field: 'amount', label: 'Total Revenue', aggregate: 'sum' }, + { field: 'amount', label: 'Average Deal', aggregate: 'avg' }, + { field: 'amount', label: 'Largest Deal', aggregate: 'max' }, + { field: 'amount', label: 'Smallest Deal', aggregate: 'min' }, + ], + groupingsDown: [ + { field: 'owner_name', sortOrder: 'desc' }, + ], + filter: { + field: 'stage', + operator: 'eq', + value: 'Closed Won', + }, +}; diff --git a/examples/ui/metadata-examples/src/widget.examples.ts b/examples/ui/metadata-examples/src/widget.examples.ts new file mode 100644 index 000000000..a20809ab9 --- /dev/null +++ b/examples/ui/metadata-examples/src/widget.examples.ts @@ -0,0 +1,506 @@ +// @ts-nocheck +import { FieldWidgetProps } from '@objectstack/spec/ui'; + +/** + * Widget Examples - Demonstrating ObjectStack Field Widget Protocol + * + * Field Widgets define custom UI components for field rendering and editing. + * This file shows example props configurations that widgets receive. + * + * Note: These are TypeScript type examples showing the contract. + * Actual widget implementations would be React/Vue/etc. components. + */ + +// ============================================================================ +// TEXT FIELD WIDGETS +// ============================================================================ + +/** + * Example 1: Basic Text Field Widget Props + * Simple string input field + * Use Case: Name, title, description fields + */ +export const BasicTextFieldProps: FieldWidgetProps = { + value: 'John Doe', + onChange: (newValue: string) => console.log('Updated to:', newValue), + readonly: false, + required: true, + field: { + name: 'full_name', + label: 'Full Name', + type: 'text', + maxLength: 100, + }, + record: { + id: '12345', + full_name: 'John Doe', + email: 'john@example.com', + }, +}; + +/** + * Example 2: Email Field Widget Props + * Email input with validation + * Use Case: Email addresses with format validation + */ +export const EmailFieldProps: FieldWidgetProps = { + value: 'user@example.com', + onChange: (newValue: string) => console.log('Email changed:', newValue), + readonly: false, + required: true, + error: 'Please enter a valid email address', + field: { + name: 'email', + label: 'Email Address', + type: 'email', + unique: true, + }, + record: { email: 'user@example.com' }, +}; + +/** + * Example 3: Rich Text Editor Widget Props + * WYSIWYG editor for formatted content + * Use Case: Descriptions, notes, HTML content + */ +export const RichTextEditorProps: FieldWidgetProps = { + value: '

This is rich text content

', + onChange: (newValue: string) => console.log('Rich text updated'), + readonly: false, + required: false, + field: { + name: 'description', + label: 'Description', + type: 'richtext', + }, + options: { + toolbar: ['bold', 'italic', 'underline', 'link', 'image'], + minHeight: 200, + }, +}; + +// ============================================================================ +// NUMBER FIELD WIDGETS +// ============================================================================ + +/** + * Example 4: Currency Field Widget Props + * Number field formatted as currency + * Use Case: Prices, revenue, financial amounts + */ +export const CurrencyFieldProps: FieldWidgetProps = { + value: 125000.50, + onChange: (newValue: number) => console.log('Amount changed:', newValue), + readonly: false, + required: true, + field: { + name: 'amount', + label: 'Deal Amount', + type: 'currency', + precision: 2, + }, + options: { + currency: 'USD', + locale: 'en-US', + displayStyle: 'symbol', // $125,000.50 + }, +}; + +/** + * Example 5: Percentage Field Widget Props + * Number field displayed as percentage + * Use Case: Probability, discount, completion rate + */ +export const PercentageFieldProps: FieldWidgetProps = { + value: 75, + onChange: (newValue: number) => console.log('Percentage:', newValue), + readonly: false, + required: false, + field: { + name: 'probability', + label: 'Win Probability', + type: 'percent', + min: 0, + max: 100, + }, + options: { + showSlider: true, + step: 5, + }, +}; + +// ============================================================================ +// DATE/TIME WIDGETS +// ============================================================================ + +/** + * Example 6: Date Picker Widget Props + * Date selection field + * Use Case: Due dates, birthdays, close dates + */ +export const DatePickerProps: FieldWidgetProps = { + value: '2024-12-31', + onChange: (newValue: string) => console.log('Date selected:', newValue), + readonly: false, + required: true, + field: { + name: 'close_date', + label: 'Expected Close Date', + type: 'date', + }, + options: { + minDate: '2024-01-01', + maxDate: '2025-12-31', + disableWeekends: false, + }, +}; + +/** + * Example 7: Date-Time Picker Widget Props + * Date and time selection + * Use Case: Appointments, events, timestamps + */ +export const DateTimePickerProps: FieldWidgetProps = { + value: '2024-06-15T14:30:00Z', + onChange: (newValue: string) => console.log('DateTime:', newValue), + readonly: false, + required: true, + field: { + name: 'meeting_time', + label: 'Meeting Time', + type: 'datetime', + }, + options: { + timeZone: 'America/New_York', + format24Hour: false, + minuteStep: 15, + }, +}; + +// ============================================================================ +// SELECTION WIDGETS +// ============================================================================ + +/** + * Example 8: Dropdown Select Widget Props + * Single selection from options + * Use Case: Status, priority, category + */ +export const DropdownSelectProps: FieldWidgetProps = { + value: 'high', + onChange: (newValue: string) => console.log('Selected:', newValue), + readonly: false, + required: true, + field: { + name: 'priority', + label: 'Priority', + type: 'select', + options: [ + { value: 'low', label: 'Low' }, + { value: 'medium', label: 'Medium' }, + { value: 'high', label: 'High' }, + { value: 'critical', label: 'Critical' }, + ], + }, +}; + +/** + * Example 9: Multi-Select Widget Props + * Multiple selection from options + * Use Case: Tags, skills, interests + */ +export const MultiSelectProps: FieldWidgetProps = { + value: ['javascript', 'typescript', 'react'], + onChange: (newValue: string[]) => console.log('Selected:', newValue), + readonly: false, + required: false, + field: { + name: 'skills', + label: 'Skills', + type: 'multiselect', + options: [ + { value: 'javascript', label: 'JavaScript' }, + { value: 'typescript', label: 'TypeScript' }, + { value: 'react', label: 'React' }, + { value: 'vue', label: 'Vue' }, + { value: 'angular', label: 'Angular' }, + ], + }, + options: { + maxSelections: 5, + searchable: true, + }, +}; + +/** + * Example 10: Lookup/Reference Field Widget Props + * Reference to another object + * Use Case: Account on Contact, Parent Account, Related To + */ +export const LookupFieldProps: FieldWidgetProps = { + value: 'acc_12345', + onChange: (newValue: string) => console.log('Lookup changed:', newValue), + readonly: false, + required: true, + field: { + name: 'account_id', + label: 'Account', + type: 'lookup', + reference: 'account', + displayField: 'name', + }, + options: { + searchFields: ['name', 'email', 'phone'], + recentItems: true, + createNew: true, + }, + record: { + account_id: 'acc_12345', + account_name: 'Acme Corporation', + }, +}; + +// ============================================================================ +// BOOLEAN WIDGETS +// ============================================================================ + +/** + * Example 11: Checkbox Widget Props + * Boolean true/false field + * Use Case: Active/Inactive, Opt-in, Flags + */ +export const CheckboxProps: FieldWidgetProps = { + value: true, + onChange: (newValue: boolean) => console.log('Checkbox:', newValue), + readonly: false, + required: false, + field: { + name: 'is_active', + label: 'Active', + type: 'boolean', + }, +}; + +/** + * Example 12: Toggle Switch Widget Props + * Boolean field styled as toggle + * Use Case: Enable/Disable features, preferences + */ +export const ToggleSwitchProps: FieldWidgetProps = { + value: false, + onChange: (newValue: boolean) => console.log('Toggle:', newValue), + readonly: false, + required: false, + field: { + name: 'email_notifications', + label: 'Email Notifications', + type: 'boolean', + }, + options: { + onLabel: 'Enabled', + offLabel: 'Disabled', + size: 'medium', + }, +}; + +// ============================================================================ +// FILE UPLOAD WIDGETS +// ============================================================================ + +/** + * Example 13: File Upload Widget Props + * Single file upload + * Use Case: Documents, attachments, images + */ +export const FileUploadProps: FieldWidgetProps = { + value: { + name: 'contract.pdf', + size: 245678, + url: 'https://storage.example.com/files/contract.pdf', + }, + onChange: (newValue: any) => console.log('File uploaded:', newValue), + readonly: false, + required: false, + field: { + name: 'contract_file', + label: 'Contract Document', + type: 'file', + }, + options: { + maxSize: 10485760, // 10MB + accept: '.pdf,.doc,.docx', + uploadUrl: '/api/upload', + }, +}; + +/** + * Example 14: Image Upload Widget Props + * Image file with preview + * Use Case: Profile pictures, product images + */ +export const ImageUploadProps: FieldWidgetProps = { + value: { + url: 'https://storage.example.com/images/profile.jpg', + thumbnail: 'https://storage.example.com/images/profile_thumb.jpg', + }, + onChange: (newValue: any) => console.log('Image uploaded:', newValue), + readonly: false, + required: false, + field: { + name: 'profile_image', + label: 'Profile Picture', + type: 'image', + }, + options: { + maxSize: 5242880, // 5MB + accept: 'image/*', + cropAspectRatio: 1, // Square + showPreview: true, + }, +}; + +// ============================================================================ +// ADVANCED WIDGETS +// ============================================================================ + +/** + * Example 15: Address Field Widget Props + * Composite address field + * Use Case: Billing address, shipping address + */ +export const AddressFieldProps: FieldWidgetProps = { + value: { + street: '123 Main St', + city: 'San Francisco', + state: 'CA', + postalCode: '94105', + country: 'USA', + }, + onChange: (newValue: any) => console.log('Address updated:', newValue), + readonly: false, + required: true, + field: { + name: 'billing_address', + label: 'Billing Address', + type: 'address', + }, + options: { + autocomplete: true, + validateAddress: true, + countries: ['USA', 'Canada', 'Mexico'], + }, +}; + +/** + * Example 16: JSON Editor Widget Props + * Structured JSON data editing + * Use Case: Metadata, configuration, custom data + */ +export const JsonEditorProps: FieldWidgetProps = { + value: { + customSettings: { + theme: 'dark', + notifications: true, + apiKey: 'abc123', + }, + }, + onChange: (newValue: any) => console.log('JSON updated:', newValue), + readonly: false, + required: false, + field: { + name: 'custom_settings', + label: 'Custom Settings', + type: 'json', + }, + options: { + mode: 'tree', // or 'code' + indentSize: 2, + validateSchema: true, + }, +}; + +/** + * Example 17: Color Picker Widget Props + * Color selection field + * Use Case: Themes, branding, UI customization + */ +export const ColorPickerProps: FieldWidgetProps = { + value: '#3B82F6', + onChange: (newValue: string) => console.log('Color selected:', newValue), + readonly: false, + required: false, + field: { + name: 'brand_color', + label: 'Brand Color', + type: 'color', + }, + options: { + format: 'hex', // or 'rgb', 'hsl' + showAlpha: false, + presetColors: ['#3B82F6', '#EF4444', '#10B981', '#F59E0B'], + }, +}; + +/** + * Example 18: Rating Widget Props + * Star or numeric rating field + * Use Case: Reviews, feedback, satisfaction scores + */ +export const RatingWidgetProps: FieldWidgetProps = { + value: 4, + onChange: (newValue: number) => console.log('Rating:', newValue), + readonly: false, + required: false, + field: { + name: 'satisfaction_rating', + label: 'Customer Satisfaction', + type: 'rating', + min: 1, + max: 5, + }, + options: { + icon: 'star', + allowHalf: true, + showLabel: true, + }, +}; + +/** + * Example 19: Readonly Display Widget Props + * Read-only computed field display + * Use Case: Formula fields, system fields, calculated values + */ +export const ReadonlyDisplayProps: FieldWidgetProps = { + value: '$125,000.50', + onChange: () => {}, // No-op for readonly + readonly: true, + required: false, + field: { + name: 'annual_revenue', + label: 'Annual Revenue', + type: 'currency', + formula: 'monthly_revenue * 12', + }, + record: { + monthly_revenue: 10416.71, + annual_revenue: 125000.52, + }, +}; + +/** + * Example 20: Widget with Validation Error + * Field widget displaying validation error + * Use Case: Form validation, error handling + */ +export const WidgetWithErrorProps: FieldWidgetProps = { + value: 'invalid-email', + onChange: (newValue: string) => console.log('Value:', newValue), + readonly: false, + required: true, + error: 'Please enter a valid email address in the format: user@domain.com', + field: { + name: 'email', + label: 'Email', + type: 'email', + }, +}; From 16bad1f93a2009dc37004df68e8e6f16db1b3f78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:41:08 +0000 Subject: [PATCH 3/4] Add comprehensive Data protocol examples (158 examples across 8 files) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/data/metadata-examples/README.md | 113 +++ examples/data/metadata-examples/package.json | 16 + .../metadata-examples/src/dataset.examples.ts | 403 +++++++++ .../metadata-examples/src/field.examples.ts | 539 ++++++++++++ .../metadata-examples/src/filter.examples.ts | 480 +++++++++++ .../metadata-examples/src/hook.examples.ts | 374 +++++++++ examples/data/metadata-examples/src/index.ts | 33 + .../metadata-examples/src/mapping.examples.ts | 506 ++++++++++++ .../metadata-examples/src/object.examples.ts | 765 ++++++++++++++++++ .../metadata-examples/src/query.examples.ts | 521 ++++++++++++ .../src/validation.examples.ts | 475 +++++++++++ examples/data/metadata-examples/tsconfig.json | 19 + 12 files changed, 4244 insertions(+) create mode 100644 examples/data/metadata-examples/README.md create mode 100644 examples/data/metadata-examples/package.json create mode 100644 examples/data/metadata-examples/src/dataset.examples.ts create mode 100644 examples/data/metadata-examples/src/field.examples.ts create mode 100644 examples/data/metadata-examples/src/filter.examples.ts create mode 100644 examples/data/metadata-examples/src/hook.examples.ts create mode 100644 examples/data/metadata-examples/src/index.ts create mode 100644 examples/data/metadata-examples/src/mapping.examples.ts create mode 100644 examples/data/metadata-examples/src/object.examples.ts create mode 100644 examples/data/metadata-examples/src/query.examples.ts create mode 100644 examples/data/metadata-examples/src/validation.examples.ts create mode 100644 examples/data/metadata-examples/tsconfig.json diff --git a/examples/data/metadata-examples/README.md b/examples/data/metadata-examples/README.md new file mode 100644 index 000000000..ea3c7a7fc --- /dev/null +++ b/examples/data/metadata-examples/README.md @@ -0,0 +1,113 @@ +# ObjectStack Data Protocol Examples + +This package contains comprehensive examples demonstrating all aspects of the ObjectStack Data Protocol. + +## 📚 What's Included + +### Core Examples + +1. **field.examples.ts** - 36 field type examples + - All field types from FieldType enum + - Text, numbers, dates, boolean, select, relationships + - Media fields (image, file, avatar) + - Calculated fields (formula, summary, autonumber) + - Enhanced fields (location, address, code, color, rating, etc.) + +2. **object.examples.ts** - 10 complete object definitions + - Simple and complex objects + - Objects with capabilities, indexes, search configuration + - Real-world CRM, e-commerce, and project management examples + +3. **query.examples.ts** - 25 query examples + - Simple and complex queries + - Filtering, sorting, pagination + - Aggregations and joins + - Window functions + - Real-world business queries + +4. **filter.examples.ts** - 27 filter condition examples + - All filter operators (eq, ne, gt, lt, in, etc.) + - Logical operators (AND, OR, NOT) + - String matching (contains, startsWith, endsWith) + - Complex nested conditions + - Real-world filtering scenarios + +5. **validation.examples.ts** - 20 validation rule examples + - Script validation + - Uniqueness constraints + - State machine validation + - Format validation + - Cross-field validation + - Async validation + - Conditional validation + - Custom validation + +6. **hook.examples.ts** - 20 lifecycle hook examples + - Before/after insert, update, delete + - Read hooks (beforeFind, afterFind) + - Data enrichment and transformation + - External system integration + - Audit trail and notifications + +7. **mapping.examples.ts** - 10 ETL mapping examples + - CSV and JSON imports + - Data transformations (constant, lookup, map, split, join) + - Export configurations + - Complex multi-lookup scenarios + - Migration use cases + +8. **dataset.examples.ts** - 10 seed data examples + - Reference data (countries, currencies) + - System configuration + - Demo and test data + - Environment-specific datasets + +## 🚀 Usage + +```typescript +import { + SimpleTextField, + SimpleObject, + SimpleSelectQuery, + EqualityFilter, + RequiredFieldValidation, + SendNotificationHook, + SimpleCsvImportMapping, + CountryDataset, +} from '@objectstack/example-data'; +``` + +## 🏗️ Building + +```bash +npm run build +``` + +This compiles all TypeScript examples to JavaScript and generates type declarations. + +## 📖 Example Structure + +Each example follows this pattern: +- Descriptive constant name (e.g., `SimpleSalesCrmApp`) +- Comprehensive JSDoc comment explaining the use case +- Complete, valid example using proper schemas +- Realistic, practical scenarios (CRM, e-commerce, project management) + +## 🎯 Use Cases + +These examples are designed for: +- **Learning**: Understand ObjectStack Data Protocol patterns +- **Reference**: Copy-paste starting points for your own metadata +- **Testing**: Validate implementations against standard patterns +- **Documentation**: Illustrate best practices and conventions + +## 📝 Naming Conventions + +- **Configuration Keys**: camelCase (e.g., `maxLength`, `referenceFilters`) +- **Machine Names**: snake_case (e.g., `first_name`, `project_task`) +- **Example Constants**: PascalCase (e.g., `EmailField`, `ContactObject`) + +## 🔗 Related + +- [ObjectStack Spec](../../../packages/spec) - Core schema definitions +- [UI Examples](../../ui/metadata-examples) - UI Protocol examples diff --git a/examples/data/metadata-examples/package.json b/examples/data/metadata-examples/package.json new file mode 100644 index 000000000..f5347bffd --- /dev/null +++ b/examples/data/metadata-examples/package.json @@ -0,0 +1,16 @@ +{ + "name": "@objectstack/example-data", + "version": "1.0.0", + "description": "Comprehensive Data Protocol examples demonstrating Objects, Fields, Queries, Filters, and Validations", + "private": true, + "scripts": { + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@objectstack/spec": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/examples/data/metadata-examples/src/dataset.examples.ts b/examples/data/metadata-examples/src/dataset.examples.ts new file mode 100644 index 000000000..03bd10e02 --- /dev/null +++ b/examples/data/metadata-examples/src/dataset.examples.ts @@ -0,0 +1,403 @@ +// @ts-nocheck +import { Dataset } from '@objectstack/spec/data'; + +/** + * Dataset Examples - Demonstrating ObjectStack Dataset Protocol + * + * Datasets provide seed data and fixtures for bootstrapping systems, + * loading reference data, and creating demo/test environments. + * Inspired by Rails Fixtures and Django Fixtures. + */ + +// ============================================================================ +// SIMPLE DATASETS +// ============================================================================ + +/** + * Example 1: Simple Reference Data + * Static list of countries + * Use Case: Load standard country list + */ +export const CountryDataset: Dataset = { + object: 'country', + externalId: 'code', + mode: 'upsert', + env: ['prod', 'dev', 'test'], + + records: [ + { code: 'US', name: 'United States', continent: 'North America' }, + { code: 'GB', name: 'United Kingdom', continent: 'Europe' }, + { code: 'CA', name: 'Canada', continent: 'North America' }, + { code: 'AU', name: 'Australia', continent: 'Oceania' }, + { code: 'DE', name: 'Germany', continent: 'Europe' }, + { code: 'FR', name: 'France', continent: 'Europe' }, + { code: 'JP', name: 'Japan', continent: 'Asia' }, + { code: 'CN', name: 'China', continent: 'Asia' }, + ], +}; + +/** + * Example 2: Currency Reference Data + * Standard currency codes + * Use Case: Multi-currency support + */ +export const CurrencyDataset: Dataset = { + object: 'currency', + externalId: 'code', + mode: 'upsert', + env: ['prod', 'dev', 'test'], + + records: [ + { code: 'USD', name: 'US Dollar', symbol: '$', decimal_places: 2 }, + { code: 'EUR', name: 'Euro', symbol: '€', decimal_places: 2 }, + { code: 'GBP', name: 'British Pound', symbol: '£', decimal_places: 2 }, + { code: 'JPY', name: 'Japanese Yen', symbol: '¥', decimal_places: 0 }, + { code: 'CNY', name: 'Chinese Yuan', symbol: '¥', decimal_places: 2 }, + { code: 'AUD', name: 'Australian Dollar', symbol: 'A$', decimal_places: 2 }, + { code: 'CAD', name: 'Canadian Dollar', symbol: 'C$', decimal_places: 2 }, + ], +}; + +/** + * Example 3: System Roles Dataset + * Bootstrap user roles + * Use Case: Initial system setup + */ +export const SystemRolesDataset: Dataset = { + object: 'user_role', + externalId: 'name', + mode: 'upsert', + env: ['prod', 'dev', 'test'], + + records: [ + { + name: 'system_admin', + label: 'System Administrator', + description: 'Full system access', + permissions: { + all: true, + }, + }, + { + name: 'sales_manager', + label: 'Sales Manager', + description: 'Sales team management', + permissions: { + objects: ['account', 'contact', 'lead', 'opportunity'], + actions: ['read', 'create', 'update', 'delete'], + }, + }, + { + name: 'sales_rep', + label: 'Sales Representative', + description: 'Sales team member', + permissions: { + objects: ['account', 'contact', 'lead', 'opportunity'], + actions: ['read', 'create', 'update'], + }, + }, + { + name: 'support_agent', + label: 'Support Agent', + description: 'Customer support', + permissions: { + objects: ['case', 'account', 'contact'], + actions: ['read', 'create', 'update'], + }, + }, + ], +}; + +// ============================================================================ +// DEMO DATA DATASETS +// ============================================================================ + +/** + * Example 4: Demo Accounts + * Sample customer accounts for demo + * Use Case: Demo environment, training + */ +export const DemoAccountsDataset: Dataset = { + object: 'account', + externalId: 'account_number', + mode: 'upsert', + env: ['dev', 'test'], + + records: [ + { + account_number: 'ACC-001', + name: 'Acme Corporation', + type: 'customer', + industry: 'Technology', + annual_revenue: 5000000, + employees: 250, + website: 'https://acme.example.com', + is_active: true, + }, + { + account_number: 'ACC-002', + name: 'Global Industries', + type: 'customer', + industry: 'Manufacturing', + annual_revenue: 15000000, + employees: 500, + website: 'https://globalindustries.example.com', + is_active: true, + }, + { + account_number: 'ACC-003', + name: 'Tech Startup Inc', + type: 'customer', + industry: 'Technology', + annual_revenue: 500000, + employees: 25, + website: 'https://techstartup.example.com', + is_active: true, + }, + ], +}; + +/** + * Example 5: Demo Products + * Sample product catalog + * Use Case: E-commerce demo + */ +export const DemoProductsDataset: Dataset = { + object: 'product', + externalId: 'sku', + mode: 'upsert', + env: ['dev', 'test'], + + records: [ + { + sku: 'WIDGET-001', + name: 'Premium Widget', + description: 'High-quality widget for professional use', + price: 99.99, + cost: 45.00, + stock_quantity: 150, + is_active: true, + }, + { + sku: 'GADGET-001', + name: 'Smart Gadget', + description: 'Advanced gadget with AI capabilities', + price: 249.99, + cost: 125.00, + stock_quantity: 75, + is_active: true, + }, + { + sku: 'TOOL-001', + name: 'Professional Tool Set', + description: 'Complete tool set for professionals', + price: 149.99, + cost: 70.00, + stock_quantity: 50, + is_active: true, + }, + ], +}; + +/** + * Example 6: Product Categories + * Product taxonomy + * Use Case: E-commerce categorization + */ +export const ProductCategoriesDataset: Dataset = { + object: 'product_category', + externalId: 'slug', + mode: 'upsert', + env: ['dev', 'test'], + + records: [ + { + slug: 'electronics', + name: 'Electronics', + description: 'Electronic devices and accessories', + is_active: true, + }, + { + slug: 'software', + name: 'Software', + description: 'Software products and licenses', + is_active: true, + }, + { + slug: 'hardware', + name: 'Hardware', + description: 'Computer hardware and components', + is_active: true, + }, + { + slug: 'services', + name: 'Services', + description: 'Professional services and consulting', + is_active: true, + }, + ], +}; + +// ============================================================================ +// CONFIGURATION DATASETS +// ============================================================================ + +/** + * Example 7: System Settings + * Bootstrap system configuration + * Use Case: Initial system setup + */ +export const SystemSettingsDataset: Dataset = { + object: 'system_setting', + externalId: 'key', + mode: 'upsert', + env: ['prod', 'dev', 'test'], + + records: [ + { + key: 'company_name', + value: 'ObjectStack Demo', + category: 'general', + description: 'Company name displayed in UI', + }, + { + key: 'default_currency', + value: 'USD', + category: 'localization', + description: 'Default currency for the system', + }, + { + key: 'default_timezone', + value: 'America/New_York', + category: 'localization', + description: 'Default timezone for the system', + }, + { + key: 'max_file_upload_size', + value: '10485760', + category: 'limits', + description: 'Maximum file upload size in bytes (10MB)', + }, + { + key: 'session_timeout', + value: '3600', + category: 'security', + description: 'Session timeout in seconds (1 hour)', + }, + ], +}; + +/** + * Example 8: Email Templates + * Standard notification templates + * Use Case: Email automation + */ +export const EmailTemplatesDataset: Dataset = { + object: 'email_template', + externalId: 'code', + mode: 'upsert', + env: ['prod', 'dev', 'test'], + + records: [ + { + code: 'welcome_email', + name: 'Welcome Email', + subject: 'Welcome to {{company_name}}', + body: '

Welcome {{user_name}}

Thank you for joining us!

', + is_active: true, + }, + { + code: 'password_reset', + name: 'Password Reset', + subject: 'Reset Your Password', + body: '

Click the link below to reset your password:

Reset Password

', + is_active: true, + }, + { + code: 'order_confirmation', + name: 'Order Confirmation', + subject: 'Order Confirmation - {{order_number}}', + body: '

Order Confirmed

Your order {{order_number}} has been confirmed.

', + is_active: true, + }, + ], +}; + +// ============================================================================ +// TEST DATA DATASETS +// ============================================================================ + +/** + * Example 9: Test Users + * Test user accounts + * Use Case: Automated testing, QA + */ +export const TestUsersDataset: Dataset = { + object: 'user', + externalId: 'username', + mode: 'upsert', + env: ['test'], + + records: [ + { + username: 'admin_test', + email: 'admin@test.example.com', + first_name: 'Admin', + last_name: 'User', + role: 'system_admin', + is_active: true, + }, + { + username: 'sales_test', + email: 'sales@test.example.com', + first_name: 'Sales', + last_name: 'Rep', + role: 'sales_rep', + is_active: true, + }, + { + username: 'support_test', + email: 'support@test.example.com', + first_name: 'Support', + last_name: 'Agent', + role: 'support_agent', + is_active: true, + }, + ], +}; + +/** + * Example 10: Test Orders + * Sample orders for testing + * Use Case: Order processing tests + */ +export const TestOrdersDataset: Dataset = { + object: 'order', + externalId: 'order_number', + mode: 'replace', + env: ['test'], + + records: [ + { + order_number: 'ORD-TEST-001', + customer_email: 'customer1@test.example.com', + status: 'draft', + total_amount: 299.97, + order_date: '2024-01-15', + items: [ + { sku: 'WIDGET-001', quantity: 2, price: 99.99 }, + { sku: 'GADGET-001', quantity: 1, price: 99.99 }, + ], + }, + { + order_number: 'ORD-TEST-002', + customer_email: 'customer2@test.example.com', + status: 'submitted', + total_amount: 149.99, + order_date: '2024-01-16', + items: [ + { sku: 'TOOL-001', quantity: 1, price: 149.99 }, + ], + }, + ], +}; diff --git a/examples/data/metadata-examples/src/field.examples.ts b/examples/data/metadata-examples/src/field.examples.ts new file mode 100644 index 000000000..68f6183bc --- /dev/null +++ b/examples/data/metadata-examples/src/field.examples.ts @@ -0,0 +1,539 @@ +// @ts-nocheck +import { Field } from '@objectstack/spec/data'; + +/** + * Field Examples - Demonstrating ObjectStack Data Protocol + * + * Fields are the atomic building blocks of data models, defining data types, + * constraints, relationships, and validation rules. + * Inspired by Salesforce Fields and ServiceNow Dictionary. + */ + +// ============================================================================ +// TEXT FIELDS +// ============================================================================ + +/** + * Example 1: Basic Text Field + * Simple single-line text input + * Use Case: Names, titles, short descriptions + */ +export const BasicTextField: Field = { + name: 'company_name', + label: 'Company Name', + type: 'text', + required: true, + maxLength: 255, + searchable: true, +}; + +/** + * Example 2: Textarea Field + * Multi-line text input for longer content + * Use Case: Descriptions, notes, comments + */ +export const TextareaField: Field = { + name: 'description', + label: 'Description', + type: 'textarea', + maxLength: 5000, + description: 'Detailed description of the item', +}; + +/** + * Example 3: Email Field + * Email address with built-in validation + * Use Case: Contact information + */ +export const EmailField: Field = { + name: 'email', + label: 'Email Address', + type: 'email', + required: true, + unique: true, + searchable: true, + index: true, +}; + +/** + * Example 4: URL Field + * Website URL with validation + * Use Case: Links, references + */ +export const UrlField: Field = { + name: 'website', + label: 'Website', + type: 'url', + description: 'Company website URL', +}; + +/** + * Example 5: Phone Field + * Phone number with formatting + * Use Case: Contact information + */ +export const PhoneField: Field = { + name: 'phone', + label: 'Phone Number', + type: 'phone', + searchable: true, +}; + +/** + * Example 6: Password Field + * Encrypted password storage + * Use Case: User authentication + */ +export const PasswordField: Field = { + name: 'password', + label: 'Password', + type: 'password', + required: true, + encryption: true, + hidden: true, +}; + +// ============================================================================ +// RICH CONTENT FIELDS +// ============================================================================ + +/** + * Example 7: Markdown Field + * Markdown-formatted text with preview + * Use Case: Documentation, articles, blog posts + */ +export const MarkdownField: Field = { + name: 'content', + label: 'Content', + type: 'markdown', + description: 'Article content in Markdown format', +}; + +/** + * Example 8: HTML Field + * Raw HTML content + * Use Case: Email templates, web content + */ +export const HtmlField: Field = { + name: 'email_template', + label: 'Email Template', + type: 'html', + description: 'HTML email template', +}; + +/** + * Example 9: Rich Text Field + * WYSIWYG editor with formatting + * Use Case: Blog posts, articles, formatted content + */ +export const RichTextField: Field = { + name: 'article_body', + label: 'Article Body', + type: 'richtext', + required: true, +}; + +// ============================================================================ +// NUMBER FIELDS +// ============================================================================ + +/** + * Example 10: Number Field + * Integer or decimal number + * Use Case: Quantities, counts, measurements + */ +export const NumberField: Field = { + name: 'quantity', + label: 'Quantity', + type: 'number', + min: 0, + max: 999999, + defaultValue: 1, +}; + +/** + * Example 11: Currency Field + * Monetary values with precision + * Use Case: Prices, amounts, financial data + */ +export const CurrencyField: Field = { + name: 'amount', + label: 'Amount', + type: 'currency', + required: true, + currencyConfig: { + precision: 2, + currencyMode: 'dynamic', + defaultCurrency: 'USD', + }, +}; + +/** + * Example 12: Percent Field + * Percentage values + * Use Case: Rates, discounts, probabilities + */ +export const PercentField: Field = { + name: 'discount_rate', + label: 'Discount Rate', + type: 'percent', + min: 0, + max: 100, + scale: 2, +}; + +// ============================================================================ +// DATE & TIME FIELDS +// ============================================================================ + +/** + * Example 13: Date Field + * Date without time + * Use Case: Birthdate, due date, start date + */ +export const DateField: Field = { + name: 'due_date', + label: 'Due Date', + type: 'date', + required: true, +}; + +/** + * Example 14: DateTime Field + * Date with time + * Use Case: Timestamps, appointments, events + */ +export const DateTimeField: Field = { + name: 'created_at', + label: 'Created At', + type: 'datetime', + readonly: true, +}; + +/** + * Example 15: Time Field + * Time without date + * Use Case: Business hours, schedules + */ +export const TimeField: Field = { + name: 'business_hours_start', + label: 'Business Hours Start', + type: 'time', +}; + +// ============================================================================ +// BOOLEAN FIELDS +// ============================================================================ + +/** + * Example 16: Boolean Field + * True/false checkbox + * Use Case: Flags, toggles, status + */ +export const BooleanField: Field = { + name: 'is_active', + label: 'Active', + type: 'boolean', + defaultValue: true, + index: true, +}; + +// ============================================================================ +// SELECT FIELDS +// ============================================================================ + +/** + * Example 17: Select Field (Single Choice) + * Dropdown with predefined options + * Use Case: Status, category, priority + */ +export const SelectField: Field = { + name: 'status', + label: 'Status', + type: 'select', + required: true, + options: [ + { label: 'Draft', value: 'draft', color: '#888888', default: true }, + { label: 'In Progress', value: 'in_progress', color: '#0066CC' }, + { label: 'Completed', value: 'completed', color: '#00AA00' }, + { label: 'Cancelled', value: 'cancelled', color: '#CC0000' }, + ], +}; + +/** + * Example 18: Multi-Select Field + * Multiple choice selection + * Use Case: Tags, categories, skills + */ +export const MultiSelectField: Field = { + name: 'tags', + label: 'Tags', + type: 'select', + multiple: true, + options: [ + { label: 'Important', value: 'important', color: '#FF0000' }, + { label: 'Urgent', value: 'urgent', color: '#FFA500' }, + { label: 'Follow-up', value: 'follow_up', color: '#0066CC' }, + { label: 'Review', value: 'review', color: '#9966CC' }, + ], +}; + +// ============================================================================ +// RELATIONSHIP FIELDS +// ============================================================================ + +/** + * Example 19: Lookup Field (Optional Relationship) + * Reference to another object (nullable) + * Use Case: Optional parent, category, owner + */ +export const LookupField: Field = { + name: 'account_id', + label: 'Account', + type: 'lookup', + reference: 'account', + deleteBehavior: 'set_null', + referenceFilters: ['is_active = true'], +}; + +/** + * Example 20: Master-Detail Field (Required Relationship) + * Tight parent-child relationship + * Use Case: Order items, contact to account + */ +export const MasterDetailField: Field = { + name: 'opportunity_id', + label: 'Opportunity', + type: 'master_detail', + reference: 'opportunity', + required: true, + deleteBehavior: 'cascade', + writeRequiresMasterRead: true, +}; + +/** + * Example 21: Multi-Lookup Field + * Many-to-many relationship + * Use Case: Skills, projects, team members + */ +export const MultiLookupField: Field = { + name: 'project_ids', + label: 'Projects', + type: 'lookup', + reference: 'project', + multiple: true, + deleteBehavior: 'set_null', +}; + +// ============================================================================ +// MEDIA FIELDS +// ============================================================================ + +/** + * Example 22: Image Field + * Image upload and storage + * Use Case: Product photos, profile pictures + */ +export const ImageField: Field = { + name: 'product_image', + label: 'Product Image', + type: 'image', + description: 'Main product image', +}; + +/** + * Example 23: Multiple Images Field + * Image gallery + * Use Case: Product gallery, portfolio + */ +export const ImageGalleryField: Field = { + name: 'gallery_images', + label: 'Gallery', + type: 'image', + multiple: true, +}; + +/** + * Example 24: File Field + * File upload + * Use Case: Documents, attachments + */ +export const FileField: Field = { + name: 'attachment', + label: 'Attachment', + type: 'file', +}; + +/** + * Example 25: Avatar Field + * User profile picture + * Use Case: User avatars, contact photos + */ +export const AvatarField: Field = { + name: 'avatar', + label: 'Avatar', + type: 'avatar', +}; + +// ============================================================================ +// CALCULATED FIELDS +// ============================================================================ + +/** + * Example 26: Formula Field + * Calculated field using expression + * Use Case: Computed values, derived data + */ +export const FormulaField: Field = { + name: 'total_amount', + label: 'Total Amount', + type: 'formula', + expression: 'quantity * unit_price', + readonly: true, +}; + +/** + * Example 27: Summary Field (Roll-up) + * Aggregate from related records + * Use Case: Order total, team size + */ +export const SummaryField: Field = { + name: 'total_opportunities', + label: 'Total Opportunities', + type: 'summary', + summaryOperations: { + object: 'opportunity', + field: 'amount', + function: 'sum', + }, + readonly: true, +}; + +/** + * Example 28: Auto Number Field + * Sequential number generation + * Use Case: Invoice numbers, ticket IDs + */ +export const AutoNumberField: Field = { + name: 'invoice_number', + label: 'Invoice Number', + type: 'autonumber', + readonly: true, +}; + +// ============================================================================ +// ENHANCED FIELDS +// ============================================================================ + +/** + * Example 29: Location Field + * GPS coordinates + * Use Case: Store locations, delivery addresses + */ +export const LocationField: Field = { + name: 'store_location', + label: 'Store Location', + type: 'location', + displayMap: true, + allowGeocoding: true, +}; + +/** + * Example 30: Address Field + * Structured address + * Use Case: Shipping, billing, office locations + */ +export const AddressField: Field = { + name: 'billing_address', + label: 'Billing Address', + type: 'address', + addressFormat: 'us', + required: true, +}; + +/** + * Example 31: Code Field + * Code editor with syntax highlighting + * Use Case: Configuration, scripts, JSON + */ +export const CodeField: Field = { + name: 'custom_script', + label: 'Custom Script', + type: 'code', + language: 'javascript', + theme: 'dark', + lineNumbers: true, +}; + +/** + * Example 32: Color Field + * Color picker + * Use Case: Branding, theming, categories + */ +export const ColorField: Field = { + name: 'brand_color', + label: 'Brand Color', + type: 'color', + colorFormat: 'hex', + presetColors: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00'], +}; + +/** + * Example 33: Rating Field + * Star rating system + * Use Case: Reviews, feedback, quality scores + */ +export const RatingField: Field = { + name: 'customer_rating', + label: 'Customer Rating', + type: 'rating', + maxRating: 5, + allowHalf: true, +}; + +/** + * Example 34: Slider Field + * Numeric slider input + * Use Case: Priority, satisfaction, volume + */ +export const SliderField: Field = { + name: 'priority_level', + label: 'Priority Level', + type: 'slider', + min: 0, + max: 100, + step: 10, + showValue: true, + marks: { + '0': 'Low', + '50': 'Medium', + '100': 'High', + }, +}; + +/** + * Example 35: Signature Field + * Digital signature capture + * Use Case: Approvals, contracts, agreements + */ +export const SignatureField: Field = { + name: 'customer_signature', + label: 'Customer Signature', + type: 'signature', + required: true, +}; + +/** + * Example 36: QR Code Field + * QR code generation and scanning + * Use Case: Tickets, inventory, tracking + */ +export const QrCodeField: Field = { + name: 'ticket_code', + label: 'Ticket Code', + type: 'qrcode', + barcodeFormat: 'qr', + qrErrorCorrection: 'M', + displayValue: true, + allowScanning: true, +}; diff --git a/examples/data/metadata-examples/src/filter.examples.ts b/examples/data/metadata-examples/src/filter.examples.ts new file mode 100644 index 000000000..32632017d --- /dev/null +++ b/examples/data/metadata-examples/src/filter.examples.ts @@ -0,0 +1,480 @@ +// @ts-nocheck +import { FilterCondition } from '@objectstack/spec/data'; + +/** + * Filter Examples - Demonstrating ObjectStack Filter Protocol + * + * Filters provide a unified query DSL for data filtering across all data sources. + * Inspired by MongoDB query language, Prisma filters, and SQL WHERE clauses. + */ + +// ============================================================================ +// SIMPLE FIELD FILTERS +// ============================================================================ + +/** + * Example 1: Equality Filter + * Simple field equals value + * Use Case: Filter by status + * + * SQL: WHERE status = 'active' + * MongoDB: { status: 'active' } + */ +export const EqualityFilter: FilterCondition = { + status: 'active', +}; + +/** + * Example 2: Explicit Equality Operator + * Using $eq operator + * Use Case: Same as Example 1, explicit syntax + * + * SQL: WHERE status = 'active' + */ +export const ExplicitEqualityFilter: FilterCondition = { + status: { $eq: 'active' }, +}; + +/** + * Example 3: Not Equal Filter + * Field not equals value + * Use Case: Exclude cancelled items + * + * SQL: WHERE status != 'cancelled' + * MongoDB: { status: { $ne: 'cancelled' } } + */ +export const NotEqualFilter: FilterCondition = { + status: { $ne: 'cancelled' }, +}; + +/** + * Example 4: Greater Than Filter + * Numeric comparison + * Use Case: High-value orders + * + * SQL: WHERE amount > 1000 + * MongoDB: { amount: { $gt: 1000 } } + */ +export const GreaterThanFilter: FilterCondition = { + amount: { $gt: 1000 }, +}; + +/** + * Example 5: Less Than or Equal Filter + * Date comparison + * Use Case: Past due items + * + * SQL: WHERE due_date <= '2024-01-31' + * MongoDB: { due_date: { $lte: ISODate('2024-01-31') } } + */ +export const LessThanEqualFilter: FilterCondition = { + due_date: { $lte: new Date('2024-01-31') }, +}; + +/** + * Example 6: Range Filter (Between) + * Value within range + * Use Case: Orders in Q1 2024 + * + * SQL: WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31' + * MongoDB: { order_date: { $gte: ..., $lte: ... } } + */ +export const RangeFilter: FilterCondition = { + order_date: { + $gte: new Date('2024-01-01'), + $lte: new Date('2024-03-31'), + }, +}; + +/** + * Example 7: Between Operator + * Using explicit $between + * Use Case: Price range filter + * + * SQL: WHERE price BETWEEN 100 AND 500 + */ +export const BetweenFilter: FilterCondition = { + price: { $between: [100, 500] }, +}; + +// ============================================================================ +// ARRAY/SET FILTERS +// ============================================================================ + +/** + * Example 8: IN Filter + * Value in list + * Use Case: Multiple status values + * + * SQL: WHERE status IN ('new', 'in_progress', 'review') + * MongoDB: { status: { $in: ['new', 'in_progress', 'review'] } } + */ +export const InFilter: FilterCondition = { + status: { $in: ['new', 'in_progress', 'review'] }, +}; + +/** + * Example 9: NOT IN Filter + * Value not in list + * Use Case: Exclude certain types + * + * SQL: WHERE type NOT IN ('archived', 'deleted') + * MongoDB: { type: { $nin: ['archived', 'deleted'] } } + */ +export const NotInFilter: FilterCondition = { + type: { $nin: ['archived', 'deleted'] }, +}; + +// ============================================================================ +// STRING FILTERS +// ============================================================================ + +/** + * Example 10: Contains Filter + * Substring search + * Use Case: Search in description + * + * SQL: WHERE description LIKE '%urgent%' + * MongoDB: { description: { $regex: 'urgent', $options: 'i' } } + */ +export const ContainsFilter: FilterCondition = { + description: { $contains: 'urgent' }, +}; + +/** + * Example 11: Starts With Filter + * Prefix matching + * Use Case: Find IDs starting with prefix + * + * SQL: WHERE customer_id LIKE 'CUST%' + * MongoDB: { customer_id: { $regex: '^CUST' } } + */ +export const StartsWithFilter: FilterCondition = { + customer_id: { $startsWith: 'CUST' }, +}; + +/** + * Example 12: Ends With Filter + * Suffix matching + * Use Case: Email domain filter + * + * SQL: WHERE email LIKE '%@company.com' + * MongoDB: { email: { $regex: '@company\\.com$' } } + */ +export const EndsWithFilter: FilterCondition = { + email: { $endsWith: '@company.com' }, +}; + +// ============================================================================ +// NULL/EXISTENCE FILTERS +// ============================================================================ + +/** + * Example 13: Is Null Filter + * Check for null values + * Use Case: Missing email addresses + * + * SQL: WHERE email IS NULL + * MongoDB: { email: null } + */ +export const IsNullFilter: FilterCondition = { + email: { $null: true }, +}; + +/** + * Example 14: Is Not Null Filter + * Check for non-null values + * Use Case: Has assigned owner + * + * SQL: WHERE owner_id IS NOT NULL + * MongoDB: { owner_id: { $ne: null } } + */ +export const IsNotNullFilter: FilterCondition = { + owner_id: { $null: false }, +}; + +/** + * Example 15: Field Exists Filter + * Check if field exists (NoSQL) + * Use Case: Documents with specific field + * + * MongoDB: { optional_field: { $exists: true } } + */ +export const FieldExistsFilter: FilterCondition = { + optional_field: { $exist: true }, +}; + +// ============================================================================ +// LOGICAL OPERATORS +// ============================================================================ + +/** + * Example 16: AND Filter (Implicit) + * Multiple conditions (all must match) + * Use Case: Active customers only + * + * SQL: WHERE is_active = true AND type = 'customer' + * MongoDB: { is_active: true, type: 'customer' } + */ +export const ImplicitAndFilter: FilterCondition = { + is_active: true, + type: 'customer', +}; + +/** + * Example 17: AND Filter (Explicit) + * Using $and operator + * Use Case: Complex conditions requiring explicit AND + * + * SQL: WHERE (amount > 1000) AND (status = 'pending') + */ +export const ExplicitAndFilter: FilterCondition = { + $and: [ + { amount: { $gt: 1000 } }, + { status: 'pending' }, + ], +}; + +/** + * Example 18: OR Filter + * Any condition matches + * Use Case: Urgent or high priority + * + * SQL: WHERE priority = 'high' OR is_urgent = true + * MongoDB: { $or: [{ priority: 'high' }, { is_urgent: true }] } + */ +export const OrFilter: FilterCondition = { + $or: [ + { priority: 'high' }, + { is_urgent: true }, + ], +}; + +/** + * Example 19: NOT Filter + * Negation + * Use Case: Not archived + * + * SQL: WHERE NOT (status = 'archived') + * MongoDB: { $not: { status: 'archived' } } + */ +export const NotFilter: FilterCondition = { + $not: { + status: 'archived', + }, +}; + +// ============================================================================ +// NESTED COMPLEX FILTERS +// ============================================================================ + +/** + * Example 20: Nested AND/OR Filter + * Complex business logic + * Use Case: (High priority OR urgent) AND (not completed) + * + * SQL: WHERE (priority = 'high' OR is_urgent = true) AND status != 'completed' + */ +export const NestedAndOrFilter: FilterCondition = { + $and: [ + { + $or: [ + { priority: 'high' }, + { is_urgent: true }, + ], + }, + { status: { $ne: 'completed' } }, + ], +}; + +/** + * Example 21: Multiple OR Groups + * Complex filter with multiple OR conditions + * Use Case: (Status A or B) OR (Amount > X and Date < Y) + * + * SQL: + * WHERE (status IN ('new', 'pending')) + * OR (amount > 5000 AND created_at < '2024-01-01') + */ +export const MultipleOrGroupsFilter: FilterCondition = { + $or: [ + { status: { $in: ['new', 'pending'] } }, + { + $and: [ + { amount: { $gt: 5000 } }, + { created_at: { $lt: new Date('2024-01-01') } }, + ], + }, + ], +}; + +/** + * Example 22: Deep Nested Filter + * Three levels of nesting + * Use Case: Complex eligibility criteria + * + * SQL: + * WHERE ( + * (type = 'enterprise' AND revenue > 1000000) + * OR (type = 'growth' AND revenue > 100000) + * ) + * AND is_active = true + * AND NOT (status = 'suspended') + */ +export const DeepNestedFilter: FilterCondition = { + $and: [ + { + $or: [ + { + $and: [ + { type: 'enterprise' }, + { revenue: { $gt: 1000000 } }, + ], + }, + { + $and: [ + { type: 'growth' }, + { revenue: { $gt: 100000 } }, + ], + }, + ], + }, + { is_active: true }, + { $not: { status: 'suspended' } }, + ], +}; + +/** + * Example 23: Combined Operators Filter + * Using multiple operator types + * Use Case: Product search with multiple criteria + * + * SQL: + * WHERE name LIKE '%widget%' + * AND price BETWEEN 10 AND 100 + * AND category_id IN (1, 2, 3) + * AND stock_quantity > 0 + * AND is_active = true + */ +export const CombinedOperatorsFilter: FilterCondition = { + name: { $contains: 'widget' }, + price: { $between: [10, 100] }, + category_id: { $in: [1, 2, 3] }, + stock_quantity: { $gt: 0 }, + is_active: true, +}; + +/** + * Example 24: Date Range with Status Filter + * Common report filter + * Use Case: Completed orders in last quarter + * + * SQL: + * WHERE status = 'completed' + * AND order_date >= '2024-10-01' + * AND order_date < '2025-01-01' + */ +export const DateRangeStatusFilter: FilterCondition = { + status: 'completed', + order_date: { + $gte: new Date('2024-10-01'), + $lt: new Date('2025-01-01'), + }, +}; + +/** + * Example 25: Real-World CRM Filter + * Complex sales pipeline filter + * Use Case: Qualified leads with recent activity + * + * SQL: + * WHERE ( + * (lead_source IN ('website', 'referral') AND rating = 'hot') + * OR (lead_source = 'trade_show' AND rating IN ('hot', 'warm')) + * ) + * AND status = 'qualified' + * AND last_activity_date > '2024-01-01' + * AND owner_id IS NOT NULL + */ +export const CrmPipelineFilter: FilterCondition = { + $and: [ + { + $or: [ + { + $and: [ + { lead_source: { $in: ['website', 'referral'] } }, + { rating: 'hot' }, + ], + }, + { + $and: [ + { lead_source: 'trade_show' }, + { rating: { $in: ['hot', 'warm'] } }, + ], + }, + ], + }, + { status: 'qualified' }, + { last_activity_date: { $gt: new Date('2024-01-01') } }, + { owner_id: { $null: false } }, + ], +}; + +/** + * Example 26: E-commerce Product Filter + * Multi-criteria product search + * Use Case: Available products in category with price range + * + * SQL: + * WHERE category_id = 'electronics' + * AND ( + * (price >= 100 AND price <= 500) + * OR on_sale = true + * ) + * AND stock_quantity > 0 + * AND is_active = true + * AND NOT (tags LIKE '%discontinued%') + */ +export const EcommerceProductFilter: FilterCondition = { + category_id: 'electronics', + $or: [ + { + price: { + $gte: 100, + $lte: 500, + }, + }, + { on_sale: true }, + ], + stock_quantity: { $gt: 0 }, + is_active: true, + $not: { + tags: { $contains: 'discontinued' }, + }, +}; + +/** + * Example 27: User Permission Filter + * Complex permission-based filtering + * Use Case: Records user can access + * + * SQL: + * WHERE ( + * owner_id = 'user123' + * OR team_id IN ('team1', 'team2') + * OR is_public = true + * ) + * AND status != 'deleted' + */ +export const PermissionFilter: FilterCondition = { + $and: [ + { + $or: [ + { owner_id: 'user123' }, + { team_id: { $in: ['team1', 'team2'] } }, + { is_public: true }, + ], + }, + { status: { $ne: 'deleted' } }, + ], +}; diff --git a/examples/data/metadata-examples/src/hook.examples.ts b/examples/data/metadata-examples/src/hook.examples.ts new file mode 100644 index 000000000..31177af32 --- /dev/null +++ b/examples/data/metadata-examples/src/hook.examples.ts @@ -0,0 +1,374 @@ +// @ts-nocheck +import { Hook } from '@objectstack/spec/data'; + +/** + * Hook Examples - Demonstrating ObjectStack Hook Protocol + * + * Hooks provide lifecycle interception points for injecting custom logic + * during data operations. + * Inspired by Mongoose Middleware, Sequelize Hooks, and Salesforce Triggers. + */ + +// ============================================================================ +// BEFORE INSERT HOOKS +// ============================================================================ + +/** + * Example 1: Set Default Values + * Populate default fields before insert + * Use Case: Auto-populate created_at, created_by + * + * Salesforce: Before Insert Trigger + */ +export const SetDefaultValuesHook: Hook = { + name: 'set_default_values', + label: 'Set Default Values', + object: 'account', + events: ['beforeInsert'], + priority: 100, + async: false, + onError: 'abort', + handler: 'setDefaultValues', +}; + +/** + * Example 2: Generate Auto Number + * Create sequential ID before insert + * Use Case: Invoice numbers, ticket IDs + */ +export const GenerateAutoNumberHook: Hook = { + name: 'generate_invoice_number', + label: 'Generate Invoice Number', + object: 'invoice', + events: ['beforeInsert'], + priority: 50, + async: false, + onError: 'abort', + handler: 'generateInvoiceNumber', +}; + +/** + * Example 3: Validate Business Rules + * Complex validation before insert + * Use Case: Credit limit check + */ +export const ValidateBusinessRulesHook: Hook = { + name: 'validate_credit_limit', + label: 'Validate Credit Limit', + object: 'order', + events: ['beforeInsert'], + priority: 200, + async: false, + onError: 'abort', + handler: 'validateCreditLimit', +}; + +// ============================================================================ +// AFTER INSERT HOOKS +// ============================================================================ + +/** + * Example 4: Send Notification + * Send email after record creation + * Use Case: New lead notification + * + * Salesforce: After Insert Trigger + */ +export const SendNotificationHook: Hook = { + name: 'send_new_lead_email', + label: 'Send New Lead Notification', + object: 'lead', + events: ['afterInsert'], + priority: 100, + async: true, + onError: 'log', + handler: 'sendNewLeadEmail', +}; + +/** + * Example 5: Create Related Records + * Auto-create child records + * Use Case: Create default tasks for new opportunities + */ +export const CreateRelatedRecordsHook: Hook = { + name: 'create_default_tasks', + label: 'Create Default Tasks', + object: 'opportunity', + events: ['afterInsert'], + priority: 100, + async: false, + onError: 'log', + handler: 'createDefaultTasks', +}; + +/** + * Example 6: Sync to External System + * Push data to external API + * Use Case: Sync to CRM, ERP, or data warehouse + */ +export const SyncExternalSystemHook: Hook = { + name: 'sync_to_salesforce', + label: 'Sync to Salesforce', + object: ['account', 'contact', 'opportunity'], + events: ['afterInsert', 'afterUpdate'], + priority: 300, + async: true, + onError: 'log', + handler: 'syncToSalesforce', +}; + +// ============================================================================ +// BEFORE UPDATE HOOKS +// ============================================================================ + +/** + * Example 7: Track Modified Fields + * Record which fields changed + * Use Case: Audit trail, change history + */ +export const TrackModifiedFieldsHook: Hook = { + name: 'track_modified_fields', + label: 'Track Modified Fields', + object: '*', + events: ['beforeUpdate'], + priority: 10, + async: false, + onError: 'log', + handler: 'trackModifiedFields', +}; + +/** + * Example 8: Validate State Transition + * Ensure valid status changes + * Use Case: Workflow enforcement + */ +export const ValidateStateTransitionHook: Hook = { + name: 'validate_status_transition', + label: 'Validate Status Transition', + object: 'order', + events: ['beforeUpdate'], + priority: 100, + async: false, + onError: 'abort', + handler: 'validateStatusTransition', +}; + +/** + * Example 9: Update Modified Timestamp + * Set updated_at field + * Use Case: Standard timestamp tracking + */ +export const UpdateTimestampHook: Hook = { + name: 'update_timestamp', + label: 'Update Modified Timestamp', + object: '*', + events: ['beforeUpdate'], + priority: 50, + async: false, + onError: 'log', + handler: 'updateModifiedTimestamp', +}; + +// ============================================================================ +// AFTER UPDATE HOOKS +// ============================================================================ + +/** + * Example 10: Send Change Notification + * Notify users of important changes + * Use Case: Status change alerts + */ +export const ChangeNotificationHook: Hook = { + name: 'send_status_change_email', + label: 'Send Status Change Notification', + object: 'support_ticket', + events: ['afterUpdate'], + priority: 100, + async: true, + onError: 'log', + handler: 'sendStatusChangeEmail', +}; + +/** + * Example 11: Update Summary Fields + * Recalculate roll-up summaries + * Use Case: Update opportunity total when line items change + */ +export const UpdateSummaryFieldsHook: Hook = { + name: 'update_opportunity_total', + label: 'Update Opportunity Total', + object: 'opportunity_line_item', + events: ['afterInsert', 'afterUpdate', 'afterDelete'], + priority: 100, + async: false, + onError: 'log', + handler: 'updateOpportunityTotal', +}; + +// ============================================================================ +// BEFORE DELETE HOOKS +// ============================================================================ + +/** + * Example 12: Prevent Delete with Children + * Block deletion if related records exist + * Use Case: Cannot delete account with active opportunities + */ +export const PreventDeleteHook: Hook = { + name: 'prevent_delete_with_opportunities', + label: 'Prevent Delete with Active Opportunities', + object: 'account', + events: ['beforeDelete'], + priority: 100, + async: false, + onError: 'abort', + handler: 'preventDeleteWithChildren', +}; + +/** + * Example 13: Archive Before Delete + * Move to archive instead of deleting + * Use Case: Soft delete implementation + */ +export const ArchiveBeforeDeleteHook: Hook = { + name: 'archive_before_delete', + label: 'Archive Record Before Delete', + object: ['lead', 'opportunity', 'case'], + events: ['beforeDelete'], + priority: 50, + async: false, + onError: 'log', + handler: 'archiveRecord', +}; + +// ============================================================================ +// AFTER DELETE HOOKS +// ============================================================================ + +/** + * Example 14: Cleanup Related Records + * Delete orphaned child records + * Use Case: Cascade delete implementation + */ +export const CleanupRelatedHook: Hook = { + name: 'cleanup_related_records', + label: 'Cleanup Related Records', + object: 'project', + events: ['afterDelete'], + priority: 100, + async: false, + onError: 'log', + handler: 'cleanupRelatedRecords', +}; + +/** + * Example 15: Log Deletion + * Audit log for deleted records + * Use Case: Compliance, audit trail + */ +export const LogDeletionHook: Hook = { + name: 'log_deletion', + label: 'Log Record Deletion', + object: '*', + events: ['afterDelete'], + priority: 200, + async: true, + onError: 'log', + handler: 'logDeletion', +}; + +// ============================================================================ +// READ HOOKS +// ============================================================================ + +/** + * Example 16: Filter by Security + * Apply row-level security on queries + * Use Case: Multi-tenant data isolation + */ +export const SecurityFilterHook: Hook = { + name: 'apply_tenant_filter', + label: 'Apply Tenant Security Filter', + object: '*', + events: ['beforeFind', 'beforeFindOne'], + priority: 10, + async: false, + onError: 'abort', + handler: 'applyTenantFilter', +}; + +/** + * Example 17: Enrich Result Data + * Add computed fields to query results + * Use Case: Add calculated values + */ +export const EnrichResultHook: Hook = { + name: 'add_computed_fields', + label: 'Add Computed Fields', + object: 'product', + events: ['afterFind', 'afterFindOne'], + priority: 100, + async: false, + onError: 'log', + handler: 'addComputedFields', +}; + +// ============================================================================ +// MULTI-EVENT HOOKS +// ============================================================================ + +/** + * Example 18: Full Audit Trail + * Track all changes to sensitive objects + * Use Case: Compliance, GDPR + */ +export const AuditTrailHook: Hook = { + name: 'audit_trail', + label: 'Audit Trail Logger', + object: ['account', 'contact', 'opportunity'], + events: [ + 'afterInsert', + 'afterUpdate', + 'afterDelete', + ], + priority: 500, + async: true, + onError: 'log', + handler: 'logAuditTrail', +}; + +/** + * Example 19: Real-time Sync + * Keep external system in sync + * Use Case: Data warehouse, analytics platform + */ +export const RealtimeSyncHook: Hook = { + name: 'sync_to_warehouse', + label: 'Sync to Data Warehouse', + object: '*', + events: [ + 'afterInsert', + 'afterUpdate', + 'afterDelete', + ], + priority: 1000, + async: true, + onError: 'log', + handler: 'syncToWarehouse', +}; + +/** + * Example 20: Comprehensive Data Validation + * Complex multi-stage validation + * Use Case: Financial transaction validation + */ +export const ComprehensiveValidationHook: Hook = { + name: 'validate_transaction', + label: 'Comprehensive Transaction Validation', + object: 'financial_transaction', + events: ['beforeInsert', 'beforeUpdate'], + priority: 100, + async: false, + onError: 'abort', + handler: 'validateFinancialTransaction', +}; diff --git a/examples/data/metadata-examples/src/index.ts b/examples/data/metadata-examples/src/index.ts new file mode 100644 index 000000000..8c9d29e8d --- /dev/null +++ b/examples/data/metadata-examples/src/index.ts @@ -0,0 +1,33 @@ +// @ts-nocheck +/** + * ObjectStack Data Protocol Examples + * + * This module exports comprehensive examples demonstrating all aspects + * of the ObjectStack Data Protocol. + * + * @packageDocumentation + */ + +// Field Examples +export * from './field.examples'; + +// Object Examples +export * from './object.examples'; + +// Query Examples +export * from './query.examples'; + +// Filter Examples +export * from './filter.examples'; + +// Validation Examples +export * from './validation.examples'; + +// Hook Examples +export * from './hook.examples'; + +// Mapping Examples +export * from './mapping.examples'; + +// Dataset Examples +export * from './dataset.examples'; diff --git a/examples/data/metadata-examples/src/mapping.examples.ts b/examples/data/metadata-examples/src/mapping.examples.ts new file mode 100644 index 000000000..25c310af2 --- /dev/null +++ b/examples/data/metadata-examples/src/mapping.examples.ts @@ -0,0 +1,506 @@ +// @ts-nocheck +import { Mapping } from '@objectstack/spec/data'; + +/** + * Mapping Examples - Demonstrating ObjectStack Mapping Protocol + * + * Mappings define ETL (Extract, Transform, Load) rules for importing + * and exporting data between ObjectStack and external sources. + * Inspired by Salesforce Data Loader and ServiceNow Import Sets. + */ + +// ============================================================================ +// SIMPLE MAPPINGS +// ============================================================================ + +/** + * Example 1: Simple CSV Import + * Basic field mapping from CSV to object + * Use Case: Import customer list from spreadsheet + */ +export const SimpleCsvImportMapping: Mapping = { + name: 'import_customers_csv', + label: 'Import Customers from CSV', + sourceFormat: 'csv', + targetObject: 'account', + mode: 'insert', + + fieldMapping: [ + { + source: 'Company Name', + target: 'name', + transform: 'none', + }, + { + source: 'Email', + target: 'email', + transform: 'none', + }, + { + source: 'Phone', + target: 'phone', + transform: 'none', + }, + { + source: 'Website', + target: 'website', + transform: 'none', + }, + ], + + errorPolicy: 'skip', + batchSize: 1000, +}; + +/** + * Example 2: JSON Import Mapping + * Import from JSON API response + * Use Case: Sync products from e-commerce API + */ +export const JsonImportMapping: Mapping = { + name: 'import_products_json', + label: 'Import Products from JSON', + sourceFormat: 'json', + targetObject: 'product', + mode: 'upsert', + upsertKey: ['sku'], + + fieldMapping: [ + { + source: 'product_name', + target: 'name', + transform: 'none', + }, + { + source: 'product_sku', + target: 'sku', + transform: 'none', + }, + { + source: 'price', + target: 'price', + transform: 'none', + }, + { + source: 'in_stock', + target: 'stock_quantity', + transform: 'none', + }, + ], + + errorPolicy: 'skip', + batchSize: 500, +}; + +// ============================================================================ +// TRANSFORMATION MAPPINGS +// ============================================================================ + +/** + * Example 3: Constant Value Mapping + * Set constant values during import + * Use Case: Tag all imports with source identifier + */ +export const ConstantValueMapping: Mapping = { + name: 'import_leads_with_source', + label: 'Import Leads with Source Tag', + sourceFormat: 'csv', + targetObject: 'lead', + mode: 'insert', + + fieldMapping: [ + { + source: 'First Name', + target: 'first_name', + transform: 'none', + }, + { + source: 'Last Name', + target: 'last_name', + transform: 'none', + }, + { + source: 'Email', + target: 'email', + transform: 'none', + }, + { + source: 'Company', + target: 'company', + transform: 'none', + }, + { + source: null, + target: 'lead_source', + transform: 'constant', + params: { + value: 'trade_show_2024', + }, + }, + { + source: null, + target: 'status', + transform: 'constant', + params: { + value: 'new', + }, + }, + ], + + errorPolicy: 'skip', + batchSize: 1000, +}; + +/** + * Example 4: Value Mapping (Translation) + * Map source values to target values + * Use Case: Convert external codes to internal codes + */ +export const ValueMappingTransform: Mapping = { + name: 'import_orders_with_mapping', + label: 'Import Orders with Status Mapping', + sourceFormat: 'csv', + targetObject: 'order', + mode: 'upsert', + upsertKey: ['order_number'], + + fieldMapping: [ + { + source: 'Order ID', + target: 'order_number', + transform: 'none', + }, + { + source: 'Customer Email', + target: 'customer_email', + transform: 'none', + }, + { + source: 'Status', + target: 'status', + transform: 'map', + params: { + valueMap: { + 'New': 'draft', + 'Processing': 'processing', + 'Shipped': 'shipped', + 'Delivered': 'fulfilled', + 'Cancelled': 'cancelled', + }, + }, + }, + ], + + errorPolicy: 'skip', + batchSize: 500, +}; + +/** + * Example 5: Lookup Mapping + * Resolve foreign keys by name + * Use Case: Import contacts with account name (not ID) + */ +export const LookupMapping: Mapping = { + name: 'import_contacts_with_lookup', + label: 'Import Contacts with Account Lookup', + sourceFormat: 'csv', + targetObject: 'contact', + mode: 'upsert', + upsertKey: ['email'], + + fieldMapping: [ + { + source: 'First Name', + target: 'first_name', + transform: 'none', + }, + { + source: 'Last Name', + target: 'last_name', + transform: 'none', + }, + { + source: 'Email', + target: 'email', + transform: 'none', + }, + { + source: 'Account Name', + target: 'account_id', + transform: 'lookup', + params: { + object: 'account', + fromField: 'name', + toField: 'id', + autoCreate: true, + }, + }, + ], + + errorPolicy: 'skip', + batchSize: 500, +}; + +/** + * Example 6: Split Field Mapping + * Split one field into multiple + * Use Case: Split full name into first/last + */ +export const SplitFieldMapping: Mapping = { + name: 'import_users_split_name', + label: 'Import Users (Split Name)', + sourceFormat: 'csv', + targetObject: 'user', + mode: 'insert', + + fieldMapping: [ + { + source: 'Full Name', + target: ['first_name', 'last_name'], + transform: 'split', + params: { + separator: ' ', + }, + }, + { + source: 'Email', + target: 'email', + transform: 'none', + }, + ], + + errorPolicy: 'skip', + batchSize: 1000, +}; + +/** + * Example 7: Join Field Mapping + * Combine multiple fields into one + * Use Case: Create full address from components + */ +export const JoinFieldMapping: Mapping = { + name: 'import_addresses_join', + label: 'Import Addresses (Join Fields)', + sourceFormat: 'csv', + targetObject: 'customer', + mode: 'upsert', + upsertKey: ['email'], + + fieldMapping: [ + { + source: 'Email', + target: 'email', + transform: 'none', + }, + { + source: ['Street', 'City', 'State', 'Zip'], + target: 'full_address', + transform: 'join', + params: { + separator: ', ', + }, + }, + ], + + errorPolicy: 'skip', + batchSize: 500, +}; + +// ============================================================================ +// EXPORT MAPPINGS +// ============================================================================ + +/** + * Example 8: Export to CSV + * Define extraction query and field mapping + * Use Case: Export customer list for analysis + */ +export const ExportToCsvMapping: Mapping = { + name: 'export_customers_csv', + label: 'Export Customers to CSV', + sourceFormat: 'csv', + targetObject: 'account', + mode: 'insert', + + extractQuery: { + object: 'account', + fields: ['name', 'email', 'phone', 'website', 'annual_revenue'], + where: { + is_active: true, + type: 'customer', + }, + orderBy: [ + { field: 'name', order: 'asc' }, + ], + }, + + fieldMapping: [ + { + source: 'name', + target: 'Company Name', + transform: 'none', + }, + { + source: 'email', + target: 'Email Address', + transform: 'none', + }, + { + source: 'phone', + target: 'Phone Number', + transform: 'none', + }, + { + source: 'website', + target: 'Website URL', + transform: 'none', + }, + { + source: 'annual_revenue', + target: 'Revenue', + transform: 'none', + }, + ], + + errorPolicy: 'skip', + batchSize: 1000, +}; + +/** + * Example 9: Complex Import with Multiple Lookups + * Import opportunities with account and owner lookup + * Use Case: Import sales pipeline from external CRM + */ +export const ComplexImportMapping: Mapping = { + name: 'import_opportunities_complex', + label: 'Import Opportunities (Complex)', + sourceFormat: 'csv', + targetObject: 'opportunity', + mode: 'upsert', + upsertKey: ['external_id'], + + fieldMapping: [ + { + source: 'External ID', + target: 'external_id', + transform: 'none', + }, + { + source: 'Opportunity Name', + target: 'name', + transform: 'none', + }, + { + source: 'Account Name', + target: 'account_id', + transform: 'lookup', + params: { + object: 'account', + fromField: 'name', + toField: 'id', + autoCreate: false, + }, + }, + { + source: 'Owner Email', + target: 'owner_id', + transform: 'lookup', + params: { + object: 'user', + fromField: 'email', + toField: 'id', + autoCreate: false, + }, + }, + { + source: 'Amount', + target: 'amount', + transform: 'none', + }, + { + source: 'Stage', + target: 'stage', + transform: 'map', + params: { + valueMap: { + 'Qualify': 'qualification', + 'Propose': 'proposal', + 'Negotiate': 'negotiation', + 'Won': 'closed_won', + 'Lost': 'closed_lost', + }, + }, + }, + { + source: 'Close Date', + target: 'close_date', + transform: 'none', + }, + ], + + errorPolicy: 'skip', + batchSize: 500, +}; + +/** + * Example 10: Migration Mapping + * Migrate data from legacy system + * Use Case: One-time data migration with cleanup + */ +export const MigrationMapping: Mapping = { + name: 'migrate_legacy_products', + label: 'Migrate Products from Legacy System', + sourceFormat: 'json', + targetObject: 'product', + mode: 'replace', + + fieldMapping: [ + { + source: 'legacy_id', + target: 'external_id', + transform: 'none', + }, + { + source: 'prod_name', + target: 'name', + transform: 'none', + }, + { + source: 'sku_code', + target: 'sku', + transform: 'none', + }, + { + source: 'category_name', + target: 'category_id', + transform: 'lookup', + params: { + object: 'product_category', + fromField: 'name', + toField: 'id', + autoCreate: true, + }, + }, + { + source: 'unit_price', + target: 'price', + transform: 'none', + }, + { + source: 'active_flag', + target: 'is_active', + transform: 'map', + params: { + valueMap: { + 'Y': true, + 'N': false, + '1': true, + '0': false, + }, + }, + }, + ], + + errorPolicy: 'abort', + batchSize: 100, +}; diff --git a/examples/data/metadata-examples/src/object.examples.ts b/examples/data/metadata-examples/src/object.examples.ts new file mode 100644 index 000000000..0f6236ff7 --- /dev/null +++ b/examples/data/metadata-examples/src/object.examples.ts @@ -0,0 +1,765 @@ +// @ts-nocheck +import { ServiceObject } from '@objectstack/spec/data'; + +/** + * Object Examples - Demonstrating ObjectStack Data Protocol + * + * Objects are the blueprints for business entities, defining their fields, + * capabilities, indexes, and validation rules. + * Inspired by Salesforce Objects and ServiceNow Tables. + */ + +// ============================================================================ +// BASIC OBJECTS +// ============================================================================ + +/** + * Example 1: Simple Object + * Basic object with minimal configuration + * Use Case: Quick prototyping, simple data models + */ +export const SimpleObject: ServiceObject = { + name: 'product', + label: 'Product', + pluralLabel: 'Products', + description: 'Product catalog', + icon: 'package', + + fields: { + name: { + name: 'name', + label: 'Product Name', + type: 'text', + required: true, + searchable: true, + }, + sku: { + name: 'sku', + label: 'SKU', + type: 'text', + unique: true, + externalId: true, + }, + price: { + name: 'price', + label: 'Price', + type: 'currency', + currencyConfig: { + precision: 2, + currencyMode: 'fixed', + defaultCurrency: 'USD', + }, + }, + }, +}; + +/** + * Example 2: Object with Full Capabilities + * Object with all system features enabled + * Use Case: Core business entities requiring full audit and collaboration + */ +export const FullFeaturedObject: ServiceObject = { + name: 'account', + label: 'Account', + pluralLabel: 'Accounts', + description: 'Customer and partner accounts', + icon: 'building', + + fields: { + name: { + name: 'name', + label: 'Account Name', + type: 'text', + required: true, + searchable: true, + }, + account_number: { + name: 'account_number', + label: 'Account Number', + type: 'autonumber', + readonly: true, + }, + type: { + name: 'type', + label: 'Account Type', + type: 'select', + options: [ + { label: 'Customer', value: 'customer', default: true }, + { label: 'Partner', value: 'partner' }, + { label: 'Vendor', value: 'vendor' }, + ], + }, + annual_revenue: { + name: 'annual_revenue', + label: 'Annual Revenue', + type: 'currency', + }, + website: { + name: 'website', + label: 'Website', + type: 'url', + }, + }, + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search'], + files: true, + feeds: true, + activities: true, + trash: true, + mru: true, + clone: true, + }, +}; + +/** + * Example 3: Object with Indexes + * Object with custom database indexes for performance + * Use Case: High-volume queries, optimized search + */ +export const IndexedObject: ServiceObject = { + name: 'order', + label: 'Order', + pluralLabel: 'Orders', + icon: 'shopping-cart', + + fields: { + order_number: { + name: 'order_number', + label: 'Order Number', + type: 'text', + unique: true, + externalId: true, + }, + customer_id: { + name: 'customer_id', + label: 'Customer', + type: 'lookup', + reference: 'account', + deleteBehavior: 'restrict', + }, + status: { + name: 'status', + label: 'Status', + type: 'select', + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Submitted', value: 'submitted' }, + { label: 'Fulfilled', value: 'fulfilled' }, + { label: 'Cancelled', value: 'cancelled' }, + ], + }, + order_date: { + name: 'order_date', + label: 'Order Date', + type: 'date', + required: true, + }, + total_amount: { + name: 'total_amount', + label: 'Total Amount', + type: 'currency', + }, + }, + + indexes: [ + { + name: 'idx_customer_date', + fields: ['customer_id', 'order_date'], + type: 'btree', + }, + { + name: 'idx_status', + fields: ['status'], + type: 'btree', + }, + { + name: 'idx_order_number', + fields: ['order_number'], + unique: true, + type: 'btree', + }, + ], +}; + +/** + * Example 4: Object with Search Configuration + * Object optimized for full-text search + * Use Case: Knowledge base, articles, documentation + */ +export const SearchableObject: ServiceObject = { + name: 'article', + label: 'Article', + pluralLabel: 'Articles', + icon: 'file-text', + + fields: { + title: { + name: 'title', + label: 'Title', + type: 'text', + required: true, + searchable: true, + }, + content: { + name: 'content', + label: 'Content', + type: 'richtext', + searchable: true, + }, + category: { + name: 'category', + label: 'Category', + type: 'select', + options: [ + { label: 'Tutorial', value: 'tutorial' }, + { label: 'Documentation', value: 'documentation' }, + { label: 'FAQ', value: 'faq' }, + ], + }, + tags: { + name: 'tags', + label: 'Tags', + type: 'select', + multiple: true, + options: [ + { label: 'Beginner', value: 'beginner' }, + { label: 'Advanced', value: 'advanced' }, + { label: 'Technical', value: 'technical' }, + ], + }, + published: { + name: 'published', + label: 'Published', + type: 'boolean', + defaultValue: false, + }, + }, + + search: { + fields: ['title', 'content', 'tags'], + displayFields: ['title', 'category', 'published'], + filters: ['published = true'], + }, + + enable: { + searchable: true, + }, +}; + +/** + * Example 5: Object with Validation Rules + * Object with business logic validation + * Use Case: Data quality enforcement, business rules + */ +export const ValidatedObject: ServiceObject = { + name: 'opportunity', + label: 'Opportunity', + pluralLabel: 'Opportunities', + icon: 'target', + + fields: { + name: { + name: 'name', + label: 'Opportunity Name', + type: 'text', + required: true, + }, + account_id: { + name: 'account_id', + label: 'Account', + type: 'lookup', + reference: 'account', + required: true, + }, + amount: { + name: 'amount', + label: 'Amount', + type: 'currency', + required: true, + }, + close_date: { + name: 'close_date', + label: 'Close Date', + type: 'date', + required: true, + }, + stage: { + name: 'stage', + label: 'Stage', + type: 'select', + required: true, + options: [ + { label: 'Qualification', value: 'qualification' }, + { label: 'Proposal', value: 'proposal' }, + { label: 'Negotiation', value: 'negotiation' }, + { label: 'Closed Won', value: 'closed_won' }, + { label: 'Closed Lost', value: 'closed_lost' }, + ], + }, + probability: { + name: 'probability', + label: 'Probability (%)', + type: 'percent', + }, + }, + + validations: [ + { + type: 'script', + name: 'amount_positive', + message: 'Amount must be greater than zero', + condition: 'amount <= 0', + severity: 'error', + active: true, + }, + { + type: 'cross_field', + name: 'close_date_future', + message: 'Close Date must be in the future', + condition: 'close_date < TODAY()', + fields: ['close_date'], + severity: 'warning', + active: true, + }, + { + type: 'state_machine', + name: 'stage_transitions', + message: 'Invalid stage transition', + field: 'stage', + transitions: { + qualification: ['proposal', 'closed_lost'], + proposal: ['negotiation', 'closed_lost'], + negotiation: ['closed_won', 'closed_lost'], + closed_won: [], + closed_lost: [], + }, + severity: 'error', + active: true, + }, + ], +}; + +// ============================================================================ +// CRM OBJECTS +// ============================================================================ + +/** + * Example 6: Contact Object + * Standard CRM contact entity + * Use Case: Contact management, CRM systems + */ +export const ContactObject: ServiceObject = { + name: 'contact', + label: 'Contact', + pluralLabel: 'Contacts', + description: 'Individual contacts and leads', + icon: 'user', + tags: ['crm', 'sales'], + + fields: { + first_name: { + name: 'first_name', + label: 'First Name', + type: 'text', + required: true, + searchable: true, + }, + last_name: { + name: 'last_name', + label: 'Last Name', + type: 'text', + required: true, + searchable: true, + }, + email: { + name: 'email', + label: 'Email', + type: 'email', + unique: true, + searchable: true, + }, + phone: { + name: 'phone', + label: 'Phone', + type: 'phone', + searchable: true, + }, + account_id: { + name: 'account_id', + label: 'Account', + type: 'lookup', + reference: 'account', + deleteBehavior: 'set_null', + }, + title: { + name: 'title', + label: 'Title', + type: 'text', + }, + department: { + name: 'department', + label: 'Department', + type: 'text', + }, + mailing_address: { + name: 'mailing_address', + label: 'Mailing Address', + type: 'address', + addressFormat: 'international', + }, + }, + + titleFormat: '{first_name} {last_name}', + compactLayout: ['first_name', 'last_name', 'email', 'phone', 'account_id'], + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + activities: true, + files: true, + feeds: true, + mru: true, + }, +}; + +/** + * Example 7: Lead Object + * CRM lead tracking + * Use Case: Lead management, sales pipeline + */ +export const LeadObject: ServiceObject = { + name: 'lead', + label: 'Lead', + pluralLabel: 'Leads', + description: 'Potential customers and prospects', + icon: 'user-plus', + tags: ['crm', 'sales'], + + fields: { + first_name: { + name: 'first_name', + label: 'First Name', + type: 'text', + required: true, + }, + last_name: { + name: 'last_name', + label: 'Last Name', + type: 'text', + required: true, + }, + company: { + name: 'company', + label: 'Company', + type: 'text', + required: true, + searchable: true, + }, + email: { + name: 'email', + label: 'Email', + type: 'email', + required: true, + }, + phone: { + name: 'phone', + label: 'Phone', + type: 'phone', + }, + status: { + name: 'status', + label: 'Status', + type: 'select', + required: true, + options: [ + { label: 'New', value: 'new', default: true }, + { label: 'Contacted', value: 'contacted' }, + { label: 'Qualified', value: 'qualified' }, + { label: 'Unqualified', value: 'unqualified' }, + { label: 'Converted', value: 'converted' }, + ], + }, + lead_source: { + name: 'lead_source', + label: 'Lead Source', + type: 'select', + options: [ + { label: 'Website', value: 'website' }, + { label: 'Referral', value: 'referral' }, + { label: 'Trade Show', value: 'trade_show' }, + { label: 'Cold Call', value: 'cold_call' }, + ], + }, + rating: { + name: 'rating', + label: 'Rating', + type: 'select', + options: [ + { label: 'Hot', value: 'hot', color: '#FF0000' }, + { label: 'Warm', value: 'warm', color: '#FFA500' }, + { label: 'Cold', value: 'cold', color: '#0066CC' }, + ], + }, + }, + + titleFormat: '{first_name} {last_name} - {company}', + + validations: [ + { + type: 'unique', + name: 'unique_email', + message: 'A lead with this email already exists', + fields: ['email'], + scope: 'status != "converted"', + caseSensitive: false, + severity: 'error', + active: true, + }, + ], + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + activities: true, + mru: true, + }, +}; + +// ============================================================================ +// E-COMMERCE OBJECTS +// ============================================================================ + +/** + * Example 8: Product Catalog Object + * E-commerce product with variants + * Use Case: Online stores, catalogs + */ +export const ProductCatalogObject: ServiceObject = { + name: 'product_catalog', + label: 'Product', + pluralLabel: 'Products', + icon: 'shopping-bag', + tags: ['ecommerce', 'catalog'], + + fields: { + name: { + name: 'name', + label: 'Product Name', + type: 'text', + required: true, + searchable: true, + }, + sku: { + name: 'sku', + label: 'SKU', + type: 'text', + required: true, + unique: true, + externalId: true, + }, + description: { + name: 'description', + label: 'Description', + type: 'richtext', + searchable: true, + }, + price: { + name: 'price', + label: 'Price', + type: 'currency', + required: true, + }, + cost: { + name: 'cost', + label: 'Cost', + type: 'currency', + }, + margin: { + name: 'margin', + label: 'Margin', + type: 'formula', + expression: '(price - cost) / price * 100', + readonly: true, + }, + stock_quantity: { + name: 'stock_quantity', + label: 'Stock Quantity', + type: 'number', + defaultValue: 0, + }, + category_id: { + name: 'category_id', + label: 'Category', + type: 'lookup', + reference: 'product_category', + }, + images: { + name: 'images', + label: 'Product Images', + type: 'image', + multiple: true, + }, + is_active: { + name: 'is_active', + label: 'Active', + type: 'boolean', + defaultValue: true, + }, + }, + + indexes: [ + { + name: 'idx_sku', + fields: ['sku'], + unique: true, + }, + { + name: 'idx_category_active', + fields: ['category_id', 'is_active'], + }, + ], + + enable: { + searchable: true, + apiEnabled: true, + files: true, + }, +}; + +// ============================================================================ +// PROJECT MANAGEMENT OBJECTS +// ============================================================================ + +/** + * Example 9: Project Task Object + * Task tracking with dependencies + * Use Case: Project management, task tracking + */ +export const ProjectTaskObject: ServiceObject = { + name: 'project_task', + label: 'Task', + pluralLabel: 'Tasks', + icon: 'check-square', + tags: ['project', 'task'], + + fields: { + name: { + name: 'name', + label: 'Task Name', + type: 'text', + required: true, + searchable: true, + }, + description: { + name: 'description', + label: 'Description', + type: 'textarea', + }, + project_id: { + name: 'project_id', + label: 'Project', + type: 'master_detail', + reference: 'project', + required: true, + deleteBehavior: 'cascade', + }, + assigned_to: { + name: 'assigned_to', + label: 'Assigned To', + type: 'lookup', + reference: 'user', + }, + status: { + name: 'status', + label: 'Status', + type: 'select', + required: true, + options: [ + { label: 'To Do', value: 'todo', default: true }, + { label: 'In Progress', value: 'in_progress' }, + { label: 'Done', value: 'done' }, + ], + }, + priority: { + name: 'priority', + label: 'Priority', + type: 'select', + options: [ + { label: 'Low', value: 'low' }, + { label: 'Medium', value: 'medium', default: true }, + { label: 'High', value: 'high' }, + { label: 'Critical', value: 'critical' }, + ], + }, + due_date: { + name: 'due_date', + label: 'Due Date', + type: 'date', + }, + estimated_hours: { + name: 'estimated_hours', + label: 'Estimated Hours', + type: 'number', + }, + actual_hours: { + name: 'actual_hours', + label: 'Actual Hours', + type: 'number', + }, + }, + + enable: { + trackHistory: true, + activities: true, + files: true, + feeds: true, + }, +}; + +/** + * Example 10: System Object + * Protected system object + * Use Case: System configuration, core entities + */ +export const SystemObject: ServiceObject = { + name: 'user_role', + label: 'User Role', + pluralLabel: 'User Roles', + icon: 'shield', + tags: ['system', 'security'], + isSystem: true, + + fields: { + name: { + name: 'name', + label: 'Role Name', + type: 'text', + required: true, + unique: true, + }, + description: { + name: 'description', + label: 'Description', + type: 'textarea', + }, + permissions: { + name: 'permissions', + label: 'Permissions', + type: 'code', + language: 'json', + }, + }, + + enable: { + trackHistory: true, + apiEnabled: true, + apiMethods: ['get', 'list'], + trash: false, + }, +}; diff --git a/examples/data/metadata-examples/src/query.examples.ts b/examples/data/metadata-examples/src/query.examples.ts new file mode 100644 index 000000000..a0d1dd8b6 --- /dev/null +++ b/examples/data/metadata-examples/src/query.examples.ts @@ -0,0 +1,521 @@ +// @ts-nocheck +import { QueryAST } from '@objectstack/spec/data'; + +/** + * Query Examples - Demonstrating ObjectStack Query Protocol + * + * ObjectQL is the universal query language for ObjectStack, abstracting + * SQL, NoSQL, and SaaS APIs into a unified interface. + * Inspired by GraphQL, Prisma, and Salesforce SOQL. + */ + +// ============================================================================ +// BASIC QUERIES +// ============================================================================ + +/** + * Example 1: Simple Select Query + * Retrieve specific fields from an object + * Use Case: Basic data retrieval + * + * SQL Equivalent: SELECT name, email FROM account + */ +export const SimpleSelectQuery: QueryAST = { + object: 'account', + fields: ['name', 'email', 'phone'], +}; + +/** + * Example 2: Select All Fields + * Omit fields to retrieve all + * Use Case: Full record retrieval + * + * SQL Equivalent: SELECT * FROM account + */ +export const SelectAllQuery: QueryAST = { + object: 'account', +}; + +/** + * Example 3: Query with Simple Filter + * Filter records by field value + * Use Case: Active records only + * + * SQL Equivalent: SELECT * FROM account WHERE is_active = true + */ +export const SimpleFilterQuery: QueryAST = { + object: 'account', + where: { + is_active: true, + }, +}; + +// ============================================================================ +// FILTERING QUERIES +// ============================================================================ + +/** + * Example 4: Query with Multiple Filters (AND) + * Combine multiple conditions + * Use Case: Active customers only + * + * SQL Equivalent: SELECT * FROM account WHERE is_active = true AND type = 'customer' + */ +export const MultipleFiltersQuery: QueryAST = { + object: 'account', + where: { + is_active: true, + type: 'customer', + }, +}; + +/** + * Example 5: Query with OR Condition + * Match any of multiple conditions + * Use Case: High priority or urgent items + * + * SQL Equivalent: SELECT * FROM task WHERE priority = 'high' OR status = 'urgent' + */ +export const OrConditionQuery: QueryAST = { + object: 'task', + where: { + $or: [ + { priority: 'high' }, + { status: 'urgent' }, + ], + }, +}; + +/** + * Example 6: Query with Comparison Operators + * Use greater than, less than operators + * Use Case: High-value opportunities + * + * SQL Equivalent: SELECT * FROM opportunity WHERE amount > 100000 + */ +export const ComparisonQuery: QueryAST = { + object: 'opportunity', + fields: ['name', 'amount', 'close_date'], + where: { + amount: { $gt: 100000 }, + }, +}; + +/** + * Example 7: Query with Range Filter + * Filter within a value range + * Use Case: Orders in date range + * + * SQL Equivalent: SELECT * FROM order WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31' + */ +export const RangeQuery: QueryAST = { + object: 'order', + where: { + order_date: { + $gte: new Date('2024-01-01'), + $lte: new Date('2024-12-31'), + }, + }, +}; + +/** + * Example 8: Query with IN Operator + * Match against list of values + * Use Case: Specific statuses + * + * SQL Equivalent: SELECT * FROM lead WHERE status IN ('new', 'contacted', 'qualified') + */ +export const InOperatorQuery: QueryAST = { + object: 'lead', + where: { + status: { $in: ['new', 'contacted', 'qualified'] }, + }, +}; + +/** + * Example 9: Query with String Pattern Matching + * Search for text patterns + * Use Case: Email domain search + * + * SQL Equivalent: SELECT * FROM contact WHERE email LIKE '%@example.com' + */ +export const PatternMatchQuery: QueryAST = { + object: 'contact', + where: { + email: { $endsWith: '@example.com' }, + }, +}; + +/** + * Example 10: Complex Nested Conditions + * Combine AND/OR with multiple levels + * Use Case: Complex business logic + * + * SQL Equivalent: + * SELECT * FROM opportunity + * WHERE (stage = 'proposal' OR stage = 'negotiation') + * AND amount > 50000 + * AND close_date < '2024-12-31' + */ +export const ComplexFilterQuery: QueryAST = { + object: 'opportunity', + where: { + $and: [ + { + $or: [ + { stage: 'proposal' }, + { stage: 'negotiation' }, + ], + }, + { amount: { $gt: 50000 } }, + { close_date: { $lt: new Date('2024-12-31') } }, + ], + }, +}; + +// ============================================================================ +// SORTING & PAGINATION +// ============================================================================ + +/** + * Example 11: Query with Sorting + * Order results by field + * Use Case: Recent orders first + * + * SQL Equivalent: SELECT * FROM order ORDER BY order_date DESC + */ +export const SortedQuery: QueryAST = { + object: 'order', + orderBy: [ + { field: 'order_date', order: 'desc' }, + ], +}; + +/** + * Example 12: Query with Multiple Sort Fields + * Sort by multiple criteria + * Use Case: Sort by priority then due date + * + * SQL Equivalent: SELECT * FROM task ORDER BY priority DESC, due_date ASC + */ +export const MultiSortQuery: QueryAST = { + object: 'task', + orderBy: [ + { field: 'priority', order: 'desc' }, + { field: 'due_date', order: 'asc' }, + ], +}; + +/** + * Example 13: Query with Pagination (Limit/Offset) + * Paginate results + * Use Case: Display page 3 of results (20 per page) + * + * SQL Equivalent: SELECT * FROM product LIMIT 20 OFFSET 40 + */ +export const PaginatedQuery: QueryAST = { + object: 'product', + limit: 20, + offset: 40, + orderBy: [{ field: 'name', order: 'asc' }], +}; + +// ============================================================================ +// AGGREGATION QUERIES +// ============================================================================ + +/** + * Example 14: Count Query + * Count total records + * Use Case: Total number of accounts + * + * SQL Equivalent: SELECT COUNT(*) as total FROM account + */ +export const CountQuery: QueryAST = { + object: 'account', + aggregations: [ + { function: 'count', alias: 'total' }, + ], +}; + +/** + * Example 15: Sum Aggregation + * Calculate sum of field + * Use Case: Total revenue + * + * SQL Equivalent: SELECT SUM(amount) as total_revenue FROM opportunity WHERE stage = 'closed_won' + */ +export const SumAggregationQuery: QueryAST = { + object: 'opportunity', + where: { stage: 'closed_won' }, + aggregations: [ + { function: 'sum', field: 'amount', alias: 'total_revenue' }, + ], +}; + +/** + * Example 16: Group By with Aggregation + * Group and aggregate data + * Use Case: Revenue by region + * + * SQL Equivalent: + * SELECT region, COUNT(*) as count, SUM(amount) as total + * FROM opportunity + * GROUP BY region + */ +export const GroupByQuery: QueryAST = { + object: 'opportunity', + fields: ['region'], + aggregations: [ + { function: 'count', alias: 'count' }, + { function: 'sum', field: 'amount', alias: 'total' }, + ], + groupBy: ['region'], +}; + +/** + * Example 17: Multiple Aggregations + * Calculate multiple metrics + * Use Case: Sales statistics + * + * SQL Equivalent: + * SELECT + * COUNT(*) as total_deals, + * SUM(amount) as total_value, + * AVG(amount) as average_value, + * MIN(amount) as min_value, + * MAX(amount) as max_value + * FROM opportunity + * WHERE stage = 'closed_won' + */ +export const MultiAggregationQuery: QueryAST = { + object: 'opportunity', + where: { stage: 'closed_won' }, + aggregations: [ + { function: 'count', alias: 'total_deals' }, + { function: 'sum', field: 'amount', alias: 'total_value' }, + { function: 'avg', field: 'amount', alias: 'average_value' }, + { function: 'min', field: 'amount', alias: 'min_value' }, + { function: 'max', field: 'amount', alias: 'max_value' }, + ], +}; + +/** + * Example 18: Group By with Having Clause + * Filter aggregated results + * Use Case: Customers with more than 10 orders + * + * SQL Equivalent: + * SELECT customer_id, COUNT(*) as order_count + * FROM order + * GROUP BY customer_id + * HAVING COUNT(*) > 10 + */ +export const HavingClauseQuery: QueryAST = { + object: 'order', + fields: ['customer_id'], + aggregations: [ + { function: 'count', alias: 'order_count' }, + ], + groupBy: ['customer_id'], + having: { + order_count: { $gt: 10 }, + }, +}; + +// ============================================================================ +// JOIN QUERIES +// ============================================================================ + +/** + * Example 19: Simple Join Query + * Join two objects + * Use Case: Orders with customer information + * + * SQL Equivalent: + * SELECT o.*, c.name as customer_name + * FROM order o + * INNER JOIN customer c ON o.customer_id = c.id + */ +export const SimpleJoinQuery: QueryAST = { + object: 'order', + fields: ['order_number', 'amount', 'order_date'], + joins: [ + { + type: 'inner', + object: 'customer', + alias: 'c', + on: { + customer_id: { $eq: { $field: 'c.id' } }, + }, + }, + ], +}; + +/** + * Example 20: Left Join Query + * Join with optional related records + * Use Case: Accounts with optional contacts + * + * SQL Equivalent: + * SELECT a.*, c.name as contact_name + * FROM account a + * LEFT JOIN contact c ON a.id = c.account_id + */ +export const LeftJoinQuery: QueryAST = { + object: 'account', + fields: ['name', 'type'], + joins: [ + { + type: 'left', + object: 'contact', + alias: 'c', + on: { + id: { $eq: { $field: 'c.account_id' } }, + }, + }, + ], +}; + +/** + * Example 21: Multiple Joins + * Join multiple objects + * Use Case: Order with customer and shipping info + * + * SQL Equivalent: + * SELECT o.*, c.name, s.tracking_number + * FROM order o + * INNER JOIN customer c ON o.customer_id = c.id + * LEFT JOIN shipment s ON o.id = s.order_id + */ +export const MultiJoinQuery: QueryAST = { + object: 'order', + fields: ['order_number', 'amount'], + joins: [ + { + type: 'inner', + object: 'customer', + alias: 'c', + on: { + customer_id: { $eq: { $field: 'c.id' } }, + }, + }, + { + type: 'left', + object: 'shipment', + alias: 's', + on: { + id: { $eq: { $field: 's.order_id' } }, + }, + }, + ], +}; + +// ============================================================================ +// WINDOW FUNCTION QUERIES +// ============================================================================ + +/** + * Example 22: Row Number Window Function + * Assign sequential numbers within groups + * Use Case: Rank products by sales within category + * + * SQL Equivalent: + * SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) as rank + * FROM product + */ +export const RowNumberQuery: QueryAST = { + object: 'product', + fields: ['name', 'category', 'sales'], + windowFunctions: [ + { + function: 'row_number', + alias: 'rank', + over: { + partitionBy: ['category'], + orderBy: [{ field: 'sales', order: 'desc' }], + }, + }, + ], +}; + +/** + * Example 23: Running Total with Window Function + * Calculate cumulative sum + * Use Case: Running total of sales over time + * + * SQL Equivalent: + * SELECT date, amount, + * SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_total + * FROM transaction + */ +export const RunningTotalQuery: QueryAST = { + object: 'transaction', + fields: ['date', 'amount'], + windowFunctions: [ + { + function: 'sum', + field: 'amount', + alias: 'running_total', + over: { + orderBy: [{ field: 'date', order: 'asc' }], + frame: { + type: 'rows', + start: 'UNBOUNDED PRECEDING', + end: 'CURRENT ROW', + }, + }, + }, + ], +}; + +// ============================================================================ +// ADVANCED QUERIES +// ============================================================================ + +/** + * Example 24: Distinct Query + * Remove duplicate rows + * Use Case: Unique customer cities + * + * SQL Equivalent: SELECT DISTINCT city FROM customer + */ +export const DistinctQuery: QueryAST = { + object: 'customer', + fields: ['city'], + distinct: true, +}; + +/** + * Example 25: Complex Business Query + * Real-world complex query + * Use Case: Sales pipeline analysis + * + * Get open opportunities > $50k, sorted by close date, with account info + */ +export const ComplexBusinessQuery: QueryAST = { + object: 'opportunity', + fields: ['name', 'amount', 'close_date', 'stage', 'probability'], + where: { + $and: [ + { stage: { $in: ['qualification', 'proposal', 'negotiation'] } }, + { amount: { $gte: 50000 } }, + { close_date: { $gte: new Date() } }, + ], + }, + joins: [ + { + type: 'inner', + object: 'account', + alias: 'a', + on: { + account_id: { $eq: { $field: 'a.id' } }, + }, + }, + ], + orderBy: [ + { field: 'close_date', order: 'asc' }, + { field: 'amount', order: 'desc' }, + ], + limit: 50, +}; diff --git a/examples/data/metadata-examples/src/validation.examples.ts b/examples/data/metadata-examples/src/validation.examples.ts new file mode 100644 index 000000000..03c937c1f --- /dev/null +++ b/examples/data/metadata-examples/src/validation.examples.ts @@ -0,0 +1,475 @@ +// @ts-nocheck +import { ValidationRule } from '@objectstack/spec/data'; + +/** + * Validation Examples - Demonstrating ObjectStack Validation Protocol + * + * Validation rules enforce data integrity and business logic at the data layer. + * Inspired by Salesforce Validation Rules and ServiceNow Business Rules. + */ + +// ============================================================================ +// SCRIPT/EXPRESSION VALIDATION +// ============================================================================ + +/** + * Example 1: Required Field Validation + * Ensure field is not empty + * Use Case: Opportunity must have account + * + * Salesforce: ISBLANK(AccountId) + */ +export const RequiredFieldValidation: ValidationRule = { + type: 'script', + name: 'account_required', + label: 'Account Required', + message: 'Account is required for all opportunities', + condition: 'account_id = null OR account_id = ""', + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 2: Positive Amount Validation + * Ensure numeric value is positive + * Use Case: Amount must be greater than zero + * + * Salesforce: Amount <= 0 + */ +export const PositiveAmountValidation: ValidationRule = { + type: 'script', + name: 'amount_positive', + label: 'Amount Must Be Positive', + message: 'Amount must be greater than zero', + condition: 'amount <= 0', + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 3: Percentage Range Validation + * Ensure value is within valid percentage range + * Use Case: Discount cannot exceed 100% + * + * Salesforce: Discount__c < 0 || Discount__c > 100 + */ +export const PercentageRangeValidation: ValidationRule = { + type: 'script', + name: 'discount_range', + label: 'Discount Percentage Range', + message: 'Discount must be between 0% and 100%', + condition: 'discount_percent < 0 OR discount_percent > 100', + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 4: Maximum Discount Validation + * Business rule: discount cannot exceed threshold + * Use Case: Protect profit margins + * + * Salesforce: Discount_Percent__c > 0.40 + */ +export const MaxDiscountValidation: ValidationRule = { + type: 'script', + name: 'max_discount_40_percent', + label: 'Maximum Discount 40%', + description: 'Sales reps cannot offer discounts exceeding 40% without approval', + message: 'Discount cannot exceed 40%. Please contact your manager for approval.', + condition: 'discount_percent > 0.40', + severity: 'error', + events: ['insert', 'update'], + tags: ['sales', 'pricing'], + active: true, +}; + +// ============================================================================ +// UNIQUENESS VALIDATION +// ============================================================================ + +/** + * Example 5: Unique Email Validation + * Ensure email is unique across active records + * Use Case: No duplicate emails + */ +export const UniqueEmailValidation: ValidationRule = { + type: 'unique', + name: 'unique_email', + label: 'Unique Email Address', + message: 'A contact with this email address already exists', + fields: ['email'], + scope: 'is_active = true', + caseSensitive: false, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 6: Compound Unique Validation + * Multiple fields must be unique together + * Use Case: Unique product SKU per warehouse + */ +export const CompoundUniqueValidation: ValidationRule = { + type: 'unique', + name: 'unique_sku_per_warehouse', + label: 'Unique SKU per Warehouse', + message: 'This SKU already exists in the selected warehouse', + fields: ['sku', 'warehouse_id'], + caseSensitive: true, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +// ============================================================================ +// STATE MACHINE VALIDATION +// ============================================================================ + +/** + * Example 7: Opportunity Stage Validation + * Control valid stage transitions + * Use Case: Sales pipeline workflow + */ +export const OpportunityStageValidation: ValidationRule = { + type: 'state_machine', + name: 'opportunity_stage_flow', + label: 'Opportunity Stage Workflow', + description: 'Enforces valid stage transitions in sales pipeline', + message: 'Invalid stage transition. Please follow the sales process.', + field: 'stage', + transitions: { + qualification: ['proposal', 'closed_lost'], + proposal: ['qualification', 'negotiation', 'closed_lost'], + negotiation: ['proposal', 'closed_won', 'closed_lost'], + closed_won: [], + closed_lost: ['qualification'], + }, + severity: 'error', + events: ['update'], + tags: ['sales', 'workflow'], + active: true, +}; + +/** + * Example 8: Order Status Validation + * Control order lifecycle + * Use Case: Order fulfillment workflow + */ +export const OrderStatusValidation: ValidationRule = { + type: 'state_machine', + name: 'order_status_flow', + label: 'Order Status Workflow', + message: 'Invalid order status transition', + field: 'status', + transitions: { + draft: ['submitted', 'cancelled'], + submitted: ['processing', 'cancelled'], + processing: ['shipped', 'cancelled'], + shipped: ['delivered', 'returned'], + delivered: ['returned'], + cancelled: [], + returned: [], + }, + severity: 'error', + events: ['update'], + active: true, +}; + +// ============================================================================ +// FORMAT VALIDATION +// ============================================================================ + +/** + * Example 9: Phone Format Validation + * Validate phone number format + * Use Case: Consistent phone formatting + */ +export const PhoneFormatValidation: ValidationRule = { + type: 'format', + name: 'phone_format', + label: 'Phone Number Format', + message: 'Phone number must be in format: (XXX) XXX-XXXX', + field: 'phone', + regex: '^\\(\\d{3}\\) \\d{3}-\\d{4}$', + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 10: Custom ID Format Validation + * Validate custom identifier pattern + * Use Case: Project codes follow naming convention + */ +export const ProjectCodeFormatValidation: ValidationRule = { + type: 'format', + name: 'project_code_format', + label: 'Project Code Format', + description: 'Project codes must start with PRJ- followed by year and 4 digits', + message: 'Project code must follow format: PRJ-YYYY-NNNN (e.g., PRJ-2024-0001)', + field: 'project_code', + regex: '^PRJ-\\d{4}-\\d{4}$', + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +// ============================================================================ +// CROSS-FIELD VALIDATION +// ============================================================================ + +/** + * Example 11: Date Range Validation + * End date must be after start date + * Use Case: Project timelines, events + * + * Salesforce: End_Date__c < Start_Date__c + */ +export const DateRangeValidation: ValidationRule = { + type: 'cross_field', + name: 'end_date_after_start', + label: 'End Date After Start Date', + message: 'End date must be after start date', + condition: 'end_date <= start_date', + fields: ['start_date', 'end_date'], + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 12: Discount Amount Validation + * Discount cannot exceed total + * Use Case: Order discounts + * + * Salesforce: Discount__c > Amount__c + */ +export const DiscountAmountValidation: ValidationRule = { + type: 'cross_field', + name: 'discount_not_exceed_amount', + label: 'Discount Within Amount', + message: 'Discount cannot exceed the total amount', + condition: 'discount_amount > total_amount', + fields: ['discount_amount', 'total_amount'], + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 13: Close Date Future Validation + * Opportunity close date must be in future + * Use Case: Sales forecasting + * + * Salesforce: CloseDate < TODAY() + */ +export const CloseDateFutureValidation: ValidationRule = { + type: 'cross_field', + name: 'close_date_future', + label: 'Close Date in Future', + message: 'Close date must be today or in the future', + condition: 'close_date < TODAY()', + fields: ['close_date'], + severity: 'warning', + events: ['insert', 'update'], + active: true, +}; + +// ============================================================================ +// ASYNC VALIDATION +// ============================================================================ + +/** + * Example 14: Email Uniqueness Check + * Validate email availability via API + * Use Case: Real-time duplicate checking + */ +export const AsyncEmailValidation: ValidationRule = { + type: 'async', + name: 'check_email_availability', + label: 'Email Availability', + message: 'This email address is already registered', + field: 'email', + validatorUrl: '/api/users/check-email', + method: 'GET', + timeout: 3000, + debounce: 500, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 15: Tax ID Validation + * Validate tax ID with external service + * Use Case: Government compliance + */ +export const TaxIdValidation: ValidationRule = { + type: 'async', + name: 'validate_tax_id', + label: 'Tax ID Validation', + message: 'Invalid Tax ID number', + field: 'tax_id', + validatorFunction: 'validateTaxIdWithIRS', + timeout: 10000, + params: { + country: 'US', + format: 'EIN', + }, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +// ============================================================================ +// CONDITIONAL VALIDATION +// ============================================================================ + +/** + * Example 16: Conditional Required Field + * Field required only in certain conditions + * Use Case: Shipping address required for physical products + */ +export const ConditionalRequiredValidation: ValidationRule = { + type: 'conditional', + name: 'shipping_address_when_physical', + label: 'Shipping Address for Physical Products', + description: 'Shipping address is required when product type is physical', + when: 'product_type = "physical"', + message: 'Validation for physical products', + then: { + type: 'script', + name: 'shipping_address_required', + message: 'Shipping address is required for physical products', + condition: 'shipping_address = null OR shipping_address = ""', + severity: 'error', + events: ['insert', 'update'], + active: true, + }, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 17: Conditional Approval Required + * High-value orders require approval + * Use Case: Manager approval workflow + */ +export const ConditionalApprovalValidation: ValidationRule = { + type: 'conditional', + name: 'high_value_approval', + label: 'High Value Order Approval', + description: 'Orders over $10,000 require manager approval', + when: 'total_amount > 10000', + message: 'High value order validation', + then: { + type: 'script', + name: 'manager_approval_required', + message: 'Orders over $10,000 require manager approval', + condition: 'manager_approval_id = null', + severity: 'error', + events: ['insert', 'update'], + active: true, + }, + otherwise: { + type: 'script', + name: 'payment_method_required', + message: 'Payment method is required', + condition: 'payment_method = null', + severity: 'error', + events: ['insert', 'update'], + active: true, + }, + severity: 'error', + events: ['insert', 'update'], + active: true, +}; + +/** + * Example 18: Regional Compliance Validation + * Different rules for different regions + * Use Case: GDPR compliance for EU customers + */ +export const RegionalComplianceValidation: ValidationRule = { + type: 'conditional', + name: 'regional_compliance', + label: 'Regional Compliance Rules', + description: 'Apply region-specific compliance validation', + when: 'region = "EU"', + message: 'EU compliance validation', + then: { + type: 'script', + name: 'gdpr_consent', + message: 'GDPR consent is required for EU customers', + condition: 'gdpr_consent_given = false', + severity: 'error', + events: ['insert', 'update'], + active: true, + }, + otherwise: { + type: 'script', + name: 'tos_acceptance', + message: 'Terms of Service acceptance required', + condition: 'tos_accepted = false', + severity: 'error', + events: ['insert', 'update'], + active: true, + }, + severity: 'error', + events: ['insert', 'update'], + tags: ['compliance', 'legal'], + active: true, +}; + +// ============================================================================ +// CUSTOM VALIDATION +// ============================================================================ + +/** + * Example 19: Custom Business Logic Validation + * Complex validation requiring custom code + * Use Case: Credit limit check + */ +export const CustomCreditCheckValidation: ValidationRule = { + type: 'custom', + name: 'credit_limit_check', + label: 'Credit Limit Check', + description: 'Validate customer has sufficient credit for order', + message: 'Customer credit limit exceeded', + handler: 'validateCreditLimit', + params: { + includeOutstanding: true, + includePending: true, + }, + severity: 'error', + events: ['insert', 'update'], + tags: ['finance', 'credit'], + active: true, +}; + +/** + * Example 20: Custom Inventory Validation + * Check product availability + * Use Case: Prevent overselling + */ +export const CustomInventoryValidation: ValidationRule = { + type: 'custom', + name: 'inventory_availability', + label: 'Inventory Availability', + description: 'Verify sufficient inventory for order', + message: 'Insufficient inventory for requested quantity', + handler: 'checkInventoryAvailability', + params: { + reserveInventory: false, + checkAllWarehouses: true, + }, + severity: 'error', + events: ['insert', 'update'], + tags: ['inventory', 'fulfillment'], + active: true, +}; diff --git a/examples/data/metadata-examples/tsconfig.json b/examples/data/metadata-examples/tsconfig.json new file mode 100644 index 000000000..c2e3d9ccf --- /dev/null +++ b/examples/data/metadata-examples/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} From 3a3cb8999e227eeffcd2d04a69e0a6f4758ba343 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:29:30 +0000 Subject: [PATCH 4/4] Fix filter structures to use MongoDB-style operators and mapping mode Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../metadata-examples/src/mapping.examples.ts | 2 +- .../metadata-examples/src/report.examples.ts | 30 +++++++------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/examples/data/metadata-examples/src/mapping.examples.ts b/examples/data/metadata-examples/src/mapping.examples.ts index 25c310af2..61bba3609 100644 --- a/examples/data/metadata-examples/src/mapping.examples.ts +++ b/examples/data/metadata-examples/src/mapping.examples.ts @@ -452,7 +452,7 @@ export const MigrationMapping: Mapping = { label: 'Migrate Products from Legacy System', sourceFormat: 'json', targetObject: 'product', - mode: 'replace', + mode: 'upsert', fieldMapping: [ { diff --git a/examples/ui/metadata-examples/src/report.examples.ts b/examples/ui/metadata-examples/src/report.examples.ts index 8bc20fb65..aacca86f7 100644 --- a/examples/ui/metadata-examples/src/report.examples.ts +++ b/examples/ui/metadata-examples/src/report.examples.ts @@ -52,8 +52,8 @@ export const ActiveOpportunitiesReport: Report = { ], filter: { $and: [ - { field: 'stage', operator: 'ne', value: 'Closed Won' }, - { field: 'stage', operator: 'ne', value: 'Closed Lost' }, + { stage: { $ne: 'Closed Won' } }, + { stage: { $ne: 'Closed Lost' } }, ], }, }; @@ -102,9 +102,7 @@ export const SalesByRegionAndRepReport: Report = { { field: 'owner_name', sortOrder: 'asc' }, ], filter: { - field: 'stage', - operator: 'eq', - value: 'Closed Won', + stage: { $eq: 'Closed Won' }, }, }; @@ -127,9 +125,7 @@ export const MonthlyRevenueReport: Report = { { field: 'close_date', sortOrder: 'desc', dateGranularity: 'month' }, ], filter: { - field: 'stage', - operator: 'eq', - value: 'Closed Won', + stage: { $eq: 'Closed Won' }, }, }; @@ -158,9 +154,7 @@ export const SalesByProductAndQuarterReport: Report = { { field: 'close_date', sortOrder: 'asc', dateGranularity: 'quarter' }, ], filter: { - field: 'stage', - operator: 'eq', - value: 'Closed Won', + stage: { $eq: 'Closed Won' }, }, }; @@ -237,9 +231,7 @@ export const QuarterlyGrowthTrendReport: Report = { { field: 'close_date', sortOrder: 'asc', dateGranularity: 'quarter' }, ], filter: { - field: 'stage', - operator: 'eq', - value: 'Closed Won', + stage: { $eq: 'Closed Won' }, }, chart: { type: 'line', @@ -300,11 +292,11 @@ export const HighValueOpportunitiesReport: Report = { ], filter: { $and: [ - { field: 'amount', operator: 'gte', value: 100000 }, + { amount: { $gte: 100000 } }, { $or: [ - { field: 'stage', operator: 'eq', value: 'Negotiation' }, - { field: 'stage', operator: 'eq', value: 'Proposal' }, + { stage: { $eq: 'Negotiation' } }, + { stage: { $eq: 'Proposal' } }, ], }, ], @@ -332,8 +324,6 @@ export const SalesPerformanceReport: Report = { { field: 'owner_name', sortOrder: 'desc' }, ], filter: { - field: 'stage', - operator: 'eq', - value: 'Closed Won', + stage: { $eq: 'Closed Won' }, }, };