AgentSkillsCN

machhub-sdk-advanced

MACHHUB SDK 的高级功能,包括历史查询、远程函数、工作流执行以及时序数据分析。

SKILL.md
--- frontmatter
name: machhub-sdk-advanced
description: Advanced MACHHUB SDK features including Historian queries, remote functions, workflow execution, and time-series data analysis.
related_skills: [machhub-sdk-initialization, machhub-sdk-realtime]

Overview

This skill covers advanced features of the MACHHUB SDK including Historian (time-series data), remote function invocation, and workflow execution.

Use this skill when:

  • Querying historical sensor/tag data
  • Analyzing time-series trends
  • Invoking remote functions
  • Executing workflows
  • Building analytics dashboards

Prerequisites:

  • SDK initialized using Designer Extension (zero-config recommended) - see machhub-sdk-initialization
  • For production: Manual configuration - see machhub-sdk-initialization templates

Related Skills:

  • machhub-sdk-initialization - SDK must be initialized first
  • machhub-sdk-realtime - Real-time tags complement historical data

Historian (Time-Series Data)

What is Historian?

Historian stores all time-series sensor/tag data for historical analysis and trending. Use it for:

  • ✅ Time-series data storage
  • ✅ Historical queries and analysis
  • ✅ Data aggregation (avg, min, max, sum)
  • ✅ Trend analysis

Do NOT use Collections for time-series data - use Historian instead.

Basic Historian Query

typescript
import { getOrInitializeSDK } from './sdk.service';

const sdk = await getOrInitializeSDK();

const history = await sdk.historian.query({
  tagNames: ['temperature/room1'],
  startTime: '2024-01-01T00:00:00Z',
  endTime: '2024-01-02T00:00:00Z',
  interval: '1h',
  aggregation: 'avg'
});

console.log(history);
// [
//   { timestamp: '2024-01-01T00:00:00Z', value: 22.5 },
//   { timestamp: '2024-01-01T01:00:00Z', value: 23.1 },
//   ...
// ]

Query Parameters

typescript
interface HistorianQuery {
  tagNames: string[];              // Tags to query
  startTime: string;                // ISO 8601 format
  endTime: string;                  // ISO 8601 format
  interval?: string;                // '1m', '5m', '1h', '1d'
  aggregation?: 'avg' | 'min' | 'max' | 'sum' | 'count';
}

Aggregation Types

TypeDescriptionUse Case
avgAverage valueTemperature trends
minMinimum valueFind lowest readings
maxMaximum valuePeak detection
sumSum of valuesTotal production count
countNumber of readingsData availability

Historian Examples

Last 24 Hours Average

typescript
const now = new Date();
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);

const data = await sdk.historian.query({
  tagNames: ['temperature/room1'],
  startTime: yesterday.toISOString(),
  endTime: now.toISOString(),
  interval: '1h',
  aggregation: 'avg'
});

Multiple Sensors

typescript
const data = await sdk.historian.query({
  tagNames: [
    'temperature/room1',
    'temperature/room2',
    'temperature/room3'
  ],
  startTime: '2024-01-01T00:00:00Z',
  endTime: '2024-01-02T00:00:00Z',
  interval: '30m',
  aggregation: 'avg'
});

Production Metrics

typescript
// Get hourly production counts
const production = await sdk.historian.query({
  tagNames: ['production/line1/count'],
  startTime: '2024-01-01T08:00:00Z',
  endTime: '2024-01-01T17:00:00Z',
  interval: '1h',
  aggregation: 'sum'
});

Historian Service Example

typescript
// services/analytics.service.ts
import { getOrInitializeSDK } from './sdk.service';

interface TrendData {
  timestamp: string;
  value: number;
}

class AnalyticsService {
  async getProductionTrends(
    startDate: Date,
    endDate: Date
  ): Promise<TrendData[]> {
    try {
      const sdk = await getOrInitializeSDK();

      const data = await sdk.historian.query({
        tagNames: ['production/rate'],
        startTime: startDate.toISOString(),
        endTime: endDate.toISOString(),
        interval: '1h',
        aggregation: 'avg'
      });

      return data;
    } catch (error) {
      console.error('Error fetching production trends:', error);
      throw error;
    }
  }

  async getTemperatureStats(
    sensor: string,
    hours: number = 24
  ) {
    try {
      const sdk = await getOrInitializeSDK();
      const now = new Date();
      const start = new Date(now.getTime() - hours * 60 * 60 * 1000);

      // Get average
      const avgData = await sdk.historian.query({
        tagNames: [sensor],
        startTime: start.toISOString(),
        endTime: now.toISOString(),
        interval: '1h',
        aggregation: 'avg'
      });

      // Get min
      const minData = await sdk.historian.query({
        tagNames: [sensor],
        startTime: start.toISOString(),
        endTime: now.toISOString(),
        interval: '1h',
        aggregation: 'min'
      });

      // Get max
      const maxData = await sdk.historian.query({
        tagNames: [sensor],
        startTime: start.toISOString(),
        endTime: now.toISOString(),
        interval: '1h',
        aggregation: 'max'
      });

      const average = avgData.reduce((sum, d) => sum + d.value, 0) / avgData.length;
      const minimum = Math.min(...minData.map(d => d.value));
      const maximum = Math.max(...maxData.map(d => d.value));

      return { average, minimum, maximum, data: avgData };
    } catch (error) {
      console.error('Error fetching temperature stats:', error);
      throw error;
    }
  }

  async compareMultipleSensors(
    sensors: string[],
    hours: number = 24
  ) {
    try {
      const sdk = await getOrInitializeSDK();
      const now = new Date();
      const start = new Date(now.getTime() - hours * 60 * 60 * 1000);

      const data = await sdk.historian.query({
        tagNames: sensors,
        startTime: start.toISOString(),
        endTime: now.toISOString(),
        interval: '30m',
        aggregation: 'avg'
      });

      return data;
    } catch (error) {
      console.error('Error comparing sensors:', error);
      throw error;
    }
  }
}

export const analyticsService = new AnalyticsService();

Remote Functions

Invoke Function

typescript
const sdk = await getOrInitializeSDK();

// Invoke remote function with parameters
const result = await sdk.function.invoke('functionName', {
  param1: 'value1',
  param2: 123,
  param3: true
});

console.log('Function result:', result);

Function Examples

typescript
// Send email notification
await sdk.function.invoke('sendEmail', {
  to: 'user@example.com',
  subject: 'Alert',
  body: 'Temperature exceeded threshold'
});

// Process data
const processed = await sdk.function.invoke('processData', {
  source: 'sensors',
  operation: 'aggregate',
  timeRange: '1h'
});

// Trigger action
await sdk.function.invoke('triggerAction', {
  action: 'restart_service',
  service: 'data_collector'
});

Workflows (Flows)

Execute Workflow

typescript
const sdk = await getOrInitializeSDK();

// Execute workflow with input data
const result = await sdk.flow.execute('workflowName', {
  input: 'data',
  config: {
    mode: 'production',
    notify: true
  }
});

console.log('Workflow result:', result);

Workflow Examples

typescript
// Data processing workflow
const result = await sdk.flow.execute('data_processing_flow', {
  source: 'sensor_readings',
  destination: 'processed_data',
  filters: {
    quality: 'good',
    status: 'active'
  }
});

// Report generation workflow
await sdk.flow.execute('generate_report', {
  reportType: 'daily',
  format: 'pdf',
  recipients: ['manager@example.com']
});

// Automated maintenance workflow
await sdk.flow.execute('maintenance_check', {
  equipment: ['machine1', 'machine2'],
  checkType: 'preventive'
});

Workflow Service Example

typescript
// services/workflow.service.ts
import { getOrInitializeSDK } from './sdk.service';

class WorkflowService {
  async processData(
    sourceCollection: string,
    destinationCollection: string,
    filters?: any
  ) {
    try {
      const sdk = await getOrInitializeSDK();

      return await sdk.flow.execute('data_processing_flow', {
        source: sourceCollection,
        destination: destinationCollection,
        filters: filters || {}
      });
    } catch (error) {
      console.error('Error processing data:', error);
      throw error;
    }
  }

  async generateReport(
    reportType: string,
    parameters: any
  ) {
    try {
      const sdk = await getOrInitializeSDK();

      return await sdk.flow.execute('generate_report', {
        reportType,
        ...parameters
      });
    } catch (error) {
      console.error('Error generating report:', error);
      throw error;
    }
  }

  async invokeFunction(
    functionName: string,
    parameters: any
  ) {
    try {
      const sdk = await getOrInitializeSDK();
      return await sdk.function.invoke(functionName, parameters);
    } catch (error) {
      console.error(`Error invoking function ${functionName}:`, error);
      throw error;
    }
  }
}

export const workflowService = new WorkflowService();

Complete Analytics Example

typescript
// Dashboard with historical data
import { analyticsService } from './services';

let temperatureData = [];
let stats = null;

async function loadDashboardData() {
  // Get last 24 hours of temperature data
  const data = await analyticsService.getProductionTrends(
    new Date(Date.now() - 24 * 60 * 60 * 1000),
    new Date()
  );

  temperatureData = data;

  // Get statistics
  stats = await analyticsService.getTemperatureStats(
    'temperature/room1',
    24
  );
  
  // Update UI with data
  console.log('Dashboard data loaded:', { temperatureData, stats });
}

// Call on page load
loadDashboardData();

Best Practices

Historian

  1. Use for time-series - Don't store all sensor data in Collections
  2. Choose appropriate intervals - Balance detail vs performance
  3. Limit time ranges - Query only what you need
  4. Use aggregation - Reduce data volume for large ranges
  5. Cache results - Store frequently accessed historical data

Functions & Workflows

  1. Error handling - Wrap invocations in try-catch
  2. Parameter validation - Validate inputs before invoking
  3. Timeout handling - Consider long-running operations
  4. Logging - Log function/workflow executions
  5. Idempotency - Design functions to be safely retriable

Templates

Template 1: Historian Service

File: src/services/historian.service.ts

Purpose: Service for querying time-series data with aggregations

Code:

typescript
// filepath: src/services/historian.service.ts
import { getOrInitializeSDK } from './sdk.service';
import type { SDK } from '@machhub-dev/sdk-ts';

export type AggregationType = 'avg' | 'min' | 'max' | 'sum' | 'count';

export interface HistorianQuery {
  tagName: string;
  startTime: Date;
  endTime: Date;
  aggregation?: AggregationType;
  interval?: string; // e.g., '1h', '15m', '1d'
}

export interface HistorianDataPoint {
  timestamp: Date;
  value: number;
  quality?: 'good' | 'bad' | 'uncertain';
}

class HistorianService {
  private sdk: SDK | null = null;

  private async getSDK(): Promise<SDK> {
    if (!this.sdk) {
      this.sdk = await getOrInitializeSDK();
    }
    return this.sdk;
  }

  /**
   * Query historian data
   */
  async query(options: HistorianQuery): Promise<HistorianDataPoint[]> {
    try {
      const sdk = await this.getSDK();
      
      let query = sdk.historian
        .tag(options.tagName)
        .from(options.startTime)
        .to(options.endTime);

      if (options.aggregation) {
        query = query.aggregate(options.aggregation, options.interval || '1h');
      }

      const results = await query.execute();
      
      return results.map((point: any) => ({
        timestamp: new Date(point.timestamp),
        value: point.value,
        quality: point.quality || 'good'
      }));
    } catch (error) {
      console.error('Historian query failed:', error);
      throw error;
    }
  }

  /**
   * Get average value over time period
   */
  async getAverage(
    tagName: string,
    startTime: Date,
    endTime: Date,
    interval: string = '1h'
  ): Promise<HistorianDataPoint[]> {
    return await this.query({
      tagName,
      startTime,
      endTime,
      aggregation: 'avg',
      interval
    });
  }

  /**
   * Get min/max values
   */
  async getMinMax(
    tagName: string,
    startTime: Date,
    endTime: Date
  ): Promise<{ min: number; max: number }> {
    const [minData, maxData] = await Promise.all([
      this.query({
        tagName,
        startTime,
        endTime,
        aggregation: 'min'
      }),
      this.query({
        tagName,
        startTime,
        endTime,
        aggregation: 'max'
      })
    ]);

    return {
      min: minData.length > 0 ? minData[0].value : 0,
      max: maxData.length > 0 ? maxData[0].value : 0
    };
  }

  /**
   * Get data for the last N hours
   */
  async getLastHours(
    tagName: string,
    hours: number,
    aggregation?: AggregationType,
    interval?: string
  ): Promise<HistorianDataPoint[]> {
    const endTime = new Date();
    const startTime = new Date(endTime.getTime() - hours * 60 * 60 * 1000);

    return await this.query({
      tagName,
      startTime,
      endTime,
      aggregation,
      interval
    });
  }

  /**
   * Get data for today
   */
  async getToday(
    tagName: string,
    aggregation?: AggregationType
  ): Promise<HistorianDataPoint[]> {
    const now = new Date();
    const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const endTime = new Date(startTime.getTime() + 24 * 60 * 60 * 1000);

    return await this.query({
      tagName,
      startTime,
      endTime,
      aggregation,
      interval: '1h'
    });
  }

  /**
   * Compare two time periods
   */
  async comparePeriods(
    tagName: string,
    period1Start: Date,
    period1End: Date,
    period2Start: Date,
    period2End: Date,
    aggregation: AggregationType = 'avg'
  ): Promise<{
    period1: HistorianDataPoint[];
    period2: HistorianDataPoint[];
  }> {
    const [period1, period2] = await Promise.all([
      this.query({
        tagName,
        startTime: period1Start,
        endTime: period1End,
        aggregation
      }),
      this.query({
        tagName,
        startTime: period2Start,
        endTime: period2End,
        aggregation
      })
    ]);

    return { period1, period2 };
  }
}

export const historianService = new HistorianService();

Usage:

typescript
import { historianService } from './services/historian.service';

// Get average temperature for last 24 hours
const data = await historianService.getLastHours(
  'temperature/room1',
  24,
  'avg',
  '1h'
);

// Get today's data
const todayData = await historianService.getToday('temperature/room1', 'avg');

// Get min/max values
const { min, max } = await historianService.getMinMax(
  'temperature/room1',
  new Date('2024-01-01'),
  new Date('2024-01-31')
);

// Compare two weeks
const comparison = await historianService.comparePeriods(
  'temperature/room1',
  new Date('2024-01-01'),
  new Date('2024-01-07'),
  new Date('2024-01-08'),
  new Date('2024-01-14'),
  'avg'
);

Template 2: Function Invocation Service

File: src/services/function.service.ts

Purpose: Service for invoking MACHHUB functions and workflows

Code:

typescript
// filepath: src/services/function.service.ts
import { getOrInitializeSDK } from './sdk.service';
import type { SDK } from '@machhub-dev/sdk-ts';

export interface FunctionParams {
  [key: string]: any;
}

export interface FunctionResult {
  success: boolean;
  result?: any;
  error?: string;
  executionTime?: number;
}

class FunctionService {
  private sdk: SDK | null = null;

  private async getSDK(): Promise<SDK> {
    if (!this.sdk) {
      this.sdk = await getOrInitializeSDK();
    }
    return this.sdk;
  }

  /**
   * Invoke a function
   */
  async invoke(
    functionName: string,
    params: FunctionParams = {}
  ): Promise<FunctionResult> {
    const startTime = Date.now();

    try {
      // Validate parameters
      this.validateParams(params);

      const sdk = await this.getSDK();
      const result = await sdk.function.invoke(functionName, params);

      return {
        success: true,
        result,
        executionTime: Date.now() - startTime
      };
    } catch (error: any) {
      console.error(`Function ${functionName} failed:`, error);
      
      return {
        success: false,
        error: error.message || 'Function execution failed',
        executionTime: Date.now() - startTime
      };
    }
  }

  /**
   * Invoke workflow
   */
  async invokeWorkflow(
    workflowName: string,
    inputs: FunctionParams = {}
  ): Promise<FunctionResult> {
    const startTime = Date.now();

    try {
      this.validateParams(inputs);

      const sdk = await this.getSDK();
      const result = await sdk.workflow.invoke(workflowName, inputs);

      return {
        success: true,
        result,
        executionTime: Date.now() - startTime
      };
    } catch (error: any) {
      console.error(`Workflow ${workflowName} failed:`, error);
      
      return {
        success: false,
        error: error.message || 'Workflow execution failed',
        executionTime: Date.now() - startTime
      };
    }
  }

  /**
   * Invoke function with retry logic
   */
  async invokeWithRetry(
    functionName: string,
    params: FunctionParams = {},
    maxRetries: number = 3,
    retryDelay: number = 1000
  ): Promise<FunctionResult> {
    let lastError: any;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const result = await this.invoke(functionName, params);
        
        if (result.success) {
          return result;
        }

        lastError = result.error;
      } catch (error) {
        lastError = error;
      }

      if (attempt < maxRetries) {
        await this.delay(retryDelay * attempt);
      }
    }

    return {
      success: false,
      error: `Failed after ${maxRetries} attempts: ${lastError}`
    };
  }

  /**
   * Invoke multiple functions in parallel
   */
  async invokeParallel(
    functions: Array<{ name: string; params?: FunctionParams }>
  ): Promise<FunctionResult[]> {
    const promises = functions.map(({ name, params }) =>
      this.invoke(name, params)
    );

    return await Promise.all(promises);
  }

  /**
   * Invoke functions in sequence
   */
  async invokeSequential(
    functions: Array<{ name: string; params?: FunctionParams }>
  ): Promise<FunctionResult[]> {
    const results: FunctionResult[] = [];

    for (const { name, params } of functions) {
      const result = await this.invoke(name, params);
      results.push(result);

      // Stop if any function fails
      if (!result.success) {
        break;
      }
    }

    return results;
  }

  /**
   * Get function list
   */
  async getFunctions(): Promise<string[]> {
    try {
      const sdk = await this.getSDK();
      return await sdk.function.list();
    } catch (error) {
      console.error('Failed to get functions:', error);
      return [];
    }
  }

  /**
   * Get workflow list
   */
  async getWorkflows(): Promise<string[]> {
    try {
      const sdk = await this.getSDK();
      return await sdk.workflow.list();
    } catch (error) {
      console.error('Failed to get workflows:', error);
      return [];
    }
  }

  /**
   * Validate parameters
   */
  private validateParams(params: FunctionParams): void {
    if (typeof params !== 'object' || params === null) {
      throw new Error('Parameters must be an object');
    }

    // Add custom validation logic here
  }

  /**
   * Delay helper
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

export const functionService = new FunctionService();

Usage:

typescript
import { functionService } from './services/function.service';

// Invoke function
const result = await functionService.invoke('calculateTotal', {
  items: [10, 20, 30]
});

if (result.success) {
  console.log('Result:', result.result);
  console.log('Execution time:', result.executionTime, 'ms');
}

// Invoke workflow
const workflowResult = await functionService.invokeWorkflow('processOrder', {
  orderId: 'order-123',
  customerId: 'customer-456'
});

// Invoke with retry
const retryResult = await functionService.invokeWithRetry(
  'unreliableFunction',
  { data: 'test' },
  3, // max retries
  1000 // delay between retries
);

// Invoke multiple functions in parallel
const parallelResults = await functionService.invokeParallel([
  { name: 'function1', params: { a: 1 } },
  { name: 'function2', params: { b: 2 } },
  { name: 'function3', params: { c: 3 } }
]);

Template 3: Cache Service

File: src/services/cache.service.ts

Purpose: Simple in-memory cache for API results

Code:

typescript
// filepath: src/services/cache.service.ts

export interface CacheOptions {
  ttl?: number; // Time to live in milliseconds
  maxSize?: number; // Maximum number of entries
}

interface CacheEntry<T> {
  value: T;
  expires: number;
}

class CacheService {
  private cache = new Map<string, CacheEntry<any>>();
  private defaultTTL = 5 * 60 * 1000; // 5 minutes
  private maxSize = 100;

  constructor(options: CacheOptions = {}) {
    if (options.ttl) this.defaultTTL = options.ttl;
    if (options.maxSize) this.maxSize = options.maxSize;
  }

  /**
   * Get value from cache
   */
  get<T>(key: string): T | null {
    const entry = this.cache.get(key);

    if (!entry) {
      return null;
    }

    // Check if expired
    if (Date.now() > entry.expires) {
      this.cache.delete(key);
      return null;
    }

    return entry.value as T;
  }

  /**
   * Set value in cache
   */
  set<T>(key: string, value: T, ttl?: number): void {
    // Enforce max size
    if (this.cache.size >= this.maxSize) {
      // Remove oldest entry
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    const expires = Date.now() + (ttl || this.defaultTTL);

    this.cache.set(key, {
      value,
      expires
    });
  }

  /**
   * Delete value from cache
   */
  delete(key: string): void {
    this.cache.delete(key);
  }

  /**
   * Clear entire cache
   */
  clear(): void {
    this.cache.clear();
  }

  /**
   * Check if key exists and is not expired
   */
  has(key: string): boolean {
    const value = this.get(key);
    return value !== null;
  }

  /**
   * Get or set (fetch if not in cache)
   */
  async getOrSet<T>(
    key: string,
    fetcher: () => Promise<T>,
    ttl?: number
  ): Promise<T> {
    const cached = this.get<T>(key);

    if (cached !== null) {
      return cached;
    }

    const value = await fetcher();
    this.set(key, value, ttl);
    return value;
  }

  /**
   * Create cache key from parts
   */
  static createKey(...parts: (string | number)[]): string {
    return parts.join(':');
  }

  /**
   * Get cache statistics
   */
  getStats(): { size: number; maxSize: number; keys: string[] } {
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      keys: Array.from(this.cache.keys())
    };
  }

  /**
   * Clean expired entries
   */
  cleanup(): void {
    const now = Date.now();
    
    for (const [key, entry] of this.cache.entries()) {
      if (now > entry.expires) {
        this.cache.delete(key);
      }
    }
  }
}

export const cacheService = new CacheService({
  ttl: 5 * 60 * 1000, // 5 minutes
  maxSize: 100
});

// Run cleanup every minute
setInterval(() => cacheService.cleanup(), 60 * 1000);

Usage:

typescript
import { cacheService, CacheService } from './services/cache.service';
import { productService } from './services/product.service';

// Simple get/set
cacheService.set('user:123', { name: 'John' }, 10 * 60 * 1000); // 10 min TTL
const user = cacheService.get('user:123');

// Get or fetch
const products = await cacheService.getOrSet(
  'products:all',
  () => productService.getAllProducts(),
  5 * 60 * 1000 // 5 minutes
);

// Create cache key
const key = CacheService.createKey('product', productId, 'details');

// Manual delete
cacheService.delete(key);

// Clear all
cacheService.clear();

// Stats
const stats = cacheService.getStats();
console.log('Cache size:', stats.size);

Template 4: Data Transformation Utilities

File: src/utils/data-transform.ts

Purpose: Utilities for transforming and formatting data

Code:

typescript
// filepath: src/utils/data-transform.ts

export class DataTransform {
  /**
   * Group array by key
   */
  static groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
    return array.reduce((result, item) => {
      const groupKey = String(item[key]);
      if (!result[groupKey]) {
        result[groupKey] = [];
      }
      result[groupKey].push(item);
      return result;
    }, {} as Record<string, T[]>);
  }

  /**
   * Sort array by key
   */
  static sortBy<T>(
    array: T[],
    key: keyof T,
    direction: 'asc' | 'desc' = 'asc'
  ): T[] {
    return [...array].sort((a, b) => {
      const aVal = a[key];
      const bVal = b[key];

      if (aVal < bVal) return direction === 'asc' ? -1 : 1;
      if (aVal > bVal) return direction === 'asc' ? 1 : -1;
      return 0;
    });
  }

  /**
   * Filter array by multiple conditions
   */
  static filterBy<T>(
    array: T[],
    filters: Partial<Record<keyof T, any>>
  ): T[] {
    return array.filter(item => {
      return Object.entries(filters).every(([key, value]) => {
        return item[key as keyof T] === value;
      });
    });
  }

  /**
   * Map array to key-value object
   */
  static toMap<T>(array: T[], key: keyof T): Record<string, T> {
    return array.reduce((result, item) => {
      result[String(item[key])] = item;
      return result;
    }, {} as Record<string, T>);
  }

  /**
   * Paginate array
   */
  static paginate<T>(
    array: T[],
    page: number,
    limit: number
  ): { data: T[]; total: number; page: number; pages: number } {
    const total = array.length;
    const pages = Math.ceil(total / limit);
    const start = (page - 1) * limit;
    const end = start + limit;
    const data = array.slice(start, end);

    return { data, total, page, pages };
  }

  /**
   * Calculate statistics for numeric array
   */
  static stats(numbers: number[]): {
    min: number;
    max: number;
    avg: number;
    sum: number;
    count: number;
  } {
    if (numbers.length === 0) {
      return { min: 0, max: 0, avg: 0, sum: 0, count: 0 };
    }

    const sum = numbers.reduce((acc, val) => acc + val, 0);
    const avg = sum / numbers.length;
    const min = Math.min(...numbers);
    const max = Math.max(...numbers);

    return { min, max, avg, sum, count: numbers.length };
  }

  /**
   * Flatten nested object
   */
  static flatten(
    obj: any,
    prefix: string = ''
  ): Record<string, any> {
    const result: Record<string, any> = {};

    for (const [key, value] of Object.entries(obj)) {
      const newKey = prefix ? `${prefix}.${key}` : key;

      if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
        Object.assign(result, this.flatten(value, newKey));
      } else {
        result[newKey] = value;
      }
    }

    return result;
  }

  /**
   * Deep clone object
   */
  static clone<T>(obj: T): T {
    return JSON.parse(JSON.stringify(obj));
  }

  /**
   * Merge objects deeply
   */
  static merge<T>(...objects: Partial<T>[]): T {
    return objects.reduce((result, obj) => {
      return { ...result, ...obj };
    }, {} as T);
  }
}

Usage:

typescript
import { DataTransform } from './utils/data-transform';

const products = [
  { id: 1, category: 'electronics', price: 100 },
  { id: 2, category: 'electronics', price: 200 },
  { id: 3, category: 'books', price: 50 }
];

// Group by category
const grouped = DataTransform.groupBy(products, 'category');
// { electronics: [...], books: [...] }

// Sort by price
const sorted = DataTransform.sortBy(products, 'price', 'desc');

// Filter
const electronics = DataTransform.filterBy(products, { category: 'electronics' });

// Paginate
const page = DataTransform.paginate(products, 1, 2);
// { data: [...], total: 3, page: 1, pages: 2 }

// Stats
const prices = products.map(p => p.price);
const stats = DataTransform.stats(prices);
// { min: 50, max: 200, avg: 116.67, sum: 350, count: 3 }

Advanced Checklist

  • Historian used for time-series data
  • Appropriate aggregation selected
  • Time ranges optimized for performance
  • Function parameters validated
  • Workflow inputs structured correctly
  • Error handling implemented
  • Results cached where appropriate
  • Service layer used for advanced features

Resources

  • MACHHUB SDK Docs: https://docs.machhub.dev
  • Initialization Guide: See machhub-sdk-initialization
  • Real-time Data: See machhub-sdk-realtime