or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

adapters-integration.mdbrain-user-management.mdcli.mdcore-bot-management.mddatastore.mdindex.mdmessage-handling.mdscripts-middleware.md
tile.json

datastore.mddocs/

Data Persistence

Pluggable data storage system providing persistent storage for robot data with support for custom backend implementations and async operations.

Capabilities

DataStore Base Class

Abstract base class for implementing custom data persistence backends.

/**
 * Abstract DataStore interface for persistent storage
 * @param robot - Robot instance that owns this datastore
 */
class DataStore {
  constructor(robot: Robot);
  
  // Properties
  robot: Robot;
  
  // Global data operations
  async set(key: string, value: any): Promise<void>;
  async get(key: string): Promise<any>;
  
  // Object property operations
  async setObject(key: string, objectKey: string, value: any): Promise<void>;
  async getObject(key: string, objectKey: string): Promise<any>;
  
  // Array operations
  async setArray(key: string, value: any): Promise<void>;
  
  // Abstract implementation methods (must implement in subclasses)
  abstract async _set(key: string, value: any, table: string): Promise<void>;
  abstract async _get(key: string, table: string): Promise<any>;
}

Usage Example - Creating Custom DataStore:

import { DataStore } from "hubot";

class RedisDataStore extends DataStore {
  constructor(robot) {
    super(robot);
    this.redis = new Redis(process.env.REDIS_URL);
  }
  
  async _set(key, value, table) {
    const redisKey = `hubot:${table}:${key}`;
    await this.redis.set(redisKey, JSON.stringify(value));
  }
  
  async _get(key, table) {
    const redisKey = `hubot:${table}:${key}`;
    const value = await this.redis.get(redisKey);
    return value ? JSON.parse(value) : null;
  }
}

// Use custom datastore
const robot = new Robot("Shell");
robot.datastore = new RedisDataStore(robot);

Built-in Memory DataStore

Default in-memory implementation for development and testing.

/**
 * In-memory DataStore implementation
 * Stores data in memory with global and users tables
 */
class MemoryDataStore extends DataStore {
  constructor(robot: Robot);
  
  // Internal storage tables
  data: {
    global: Record<string, any>;
    users: Record<string, any>;
  };
}

Usage Example:

import { Robot } from "hubot";

const robot = new Robot("Shell");
// MemoryDataStore is used by default

// Store global data
await robot.datastore.set("app-config", { 
  version: "1.0.0",
  features: ["chat", "api"] 
});

// Retrieve global data
const config = await robot.datastore.get("app-config");

// Store object properties
await robot.datastore.setObject("user-stats", "total-users", 150);
await robot.datastore.setObject("user-stats", "active-today", 42);

// Retrieve object properties
const totalUsers = await robot.datastore.getObject("user-stats", "total-users");

// Add to arrays
await robot.datastore.setArray("recent-actions", "user-login");
await robot.datastore.setArray("recent-actions", "command-executed");

Global Data Operations

Methods for storing and retrieving global robot data.

/**
 * Store global key-value data
 * @param key - Storage key
 * @param value - Value to store (will be JSON serialized)
 */
async set(key: string, value: any): Promise<void>;

/**
 * Retrieve global data by key
 * @param key - Storage key
 * @returns Stored value or null if not found
 */
async get(key: string): Promise<any>;

Usage Examples:

// Store configuration
await robot.datastore.set("api-keys", {
  weather: process.env.WEATHER_API_KEY,
  translation: process.env.TRANSLATION_API_KEY
});

// Store metrics
await robot.datastore.set("bot-stats", {
  started: new Date().toISOString(),
  messages_processed: 0,
  commands_executed: 0
});

// Retrieve and update
const stats = await robot.datastore.get("bot-stats");
stats.messages_processed += 1;
await robot.datastore.set("bot-stats", stats);

Object Property Operations

Methods for storing nested object properties efficiently.

/**
 * Set property within a stored object
 * @param key - Parent object key
 * @param objectKey - Property key within the object
 * @param value - Value to store
 */
async setObject(key: string, objectKey: string, value: any): Promise<void>;

/**
 * Get property from within a stored object
 * @param key - Parent object key
 * @param objectKey - Property key within the object
 * @returns Property value or null if not found
 */
async getObject(key: string, objectKey: string): Promise<any>;

Usage Examples:

// Store user preferences individually
await robot.datastore.setObject("user-prefs", "user123", {
  theme: "dark",
  notifications: true,
  timezone: "UTC"
});

// Update specific preference
await robot.datastore.setObject("user-prefs", "user123.theme", "light");

// Retrieve specific preference
const theme = await robot.datastore.getObject("user-prefs", "user123.theme");

// Store room configurations
await robot.datastore.setObject("room-config", "general", {
  auto_greet: true,
  moderators: ["admin1", "admin2"]
});

await robot.datastore.setObject("room-config", "development", {
  auto_greet: false,
  debug_mode: true
});

Array Operations

Methods for appending data to stored arrays.

/**
 * Append value to stored array
 * @param key - Array key
 * @param value - Value to append
 */
async setArray(key: string, value: any): Promise<void>;

Usage Examples:

// Log recent activities
await robot.datastore.setArray("activity-log", {
  timestamp: new Date().toISOString(),
  action: "user-joined",
  user: "alice",
  room: "general"
});

await robot.datastore.setArray("activity-log", {
  timestamp: new Date().toISOString(),
  action: "command-executed",
  command: "weather",
  user: "bob"
});

// Store error logs
await robot.datastore.setArray("error-log", {
  timestamp: new Date().toISOString(),
  error: error.message,
  stack: error.stack
});

// Retrieve full arrays
const activities = await robot.datastore.get("activity-log");
const recentActivities = activities.slice(-10); // Last 10 activities

DataStore Error Handling

Handling DataStore unavailability and connection issues.

/**
 * Error class for DataStore unavailable conditions
 * Thrown when datastore operations fail due to backend unavailability
 */
class DataStoreUnavailable extends Error {
  constructor(message: string);
}

Usage Examples:

import { DataStoreUnavailable } from "hubot";

// Graceful error handling
robot.hear(/save data (.+)/i, async (res) => {
  const data = res.match[1];
  
  try {
    await robot.datastore.set("user-data", data);
    res.send("Data saved successfully!");
  } catch (error) {
    if (error instanceof DataStoreUnavailable) {
      res.send("Sorry, data storage is temporarily unavailable.");
      robot.logger.error("DataStore unavailable:", error);
    } else {
      res.send("An error occurred while saving data.");
      robot.logger.error("DataStore error:", error);
    }
  }
});

// Custom DataStore with error handling
class DatabaseDataStore extends DataStore {
  async _set(key, value, table) {
    try {
      await this.database.query(
        "INSERT INTO datastore (table_name, key, value) VALUES (?, ?, ?)",
        [table, key, JSON.stringify(value)]
      );
    } catch (error) {
      if (error.code === 'CONNECTION_LOST') {
        throw new DataStoreUnavailable("Database connection lost");
      }
      throw error;
    }
  }
  
  async _get(key, table) {
    try {
      const result = await this.database.query(
        "SELECT value FROM datastore WHERE table_name = ? AND key = ?",
        [table, key]
      );
      return result.length > 0 ? JSON.parse(result[0].value) : null;
    } catch (error) {
      if (error.code === 'CONNECTION_LOST') {
        throw new DataStoreUnavailable("Database connection lost");
      }
      throw error;
    }
  }
}

Integration with Brain

DataStore works alongside Brain for comprehensive data persistence.

// Brain uses DataStore for user data persistence
robot.brain.on('save', async (data) => {
  try {
    await robot.datastore.set('brain-data', data);
    robot.logger.info('Brain data saved to datastore');
  } catch (error) {
    robot.logger.error('Failed to save brain data:', error);
  }
});

// Load brain data from DataStore on startup
robot.brain.on('loaded', async () => {
  try {
    const savedData = await robot.datastore.get('brain-data');
    if (savedData) {
      robot.brain.mergeData(savedData);
      robot.logger.info('Brain data loaded from datastore');
    }
  } catch (error) {
    robot.logger.error('Failed to load brain data:', error);
  }
});

Custom Backend Examples

Examples of implementing DataStore for different persistence backends.

// File-based DataStore
class FileDataStore extends DataStore {
  constructor(robot) {
    super(robot);
    this.dataDir = process.env.HUBOT_DATA_DIR || './data';
    this.ensureDirectory();
  }
  
  async _set(key, value, table) {
    const filePath = path.join(this.dataDir, `${table}_${key}.json`);
    await fs.writeFile(filePath, JSON.stringify(value), 'utf8');
  }
  
  async _get(key, table) {
    const filePath = path.join(this.dataDir, `${table}_${key}.json`);
    try {
      const data = await fs.readFile(filePath, 'utf8');
      return JSON.parse(data);
    } catch (error) {
      if (error.code === 'ENOENT') return null;
      throw error;
    }
  }
}

// MongoDB DataStore
class MongoDataStore extends DataStore {
  constructor(robot) {
    super(robot);
    this.db = new MongoClient(process.env.MONGODB_URL).db('hubot');
  }
  
  async _set(key, value, table) {
    await this.db.collection(table).replaceOne(
      { _id: key },
      { _id: key, value: value },
      { upsert: true }
    );
  }
  
  async _get(key, table) {
    const doc = await this.db.collection(table).findOne({ _id: key });
    return doc ? doc.value : null;
  }
}

Types

abstract class DataStore {
  robot: Robot;
  constructor(robot: Robot);
  
  // Public API
  set(key: string, value: any): Promise<void>;
  get(key: string): Promise<any>;
  setObject(key: string, objectKey: string, value: any): Promise<void>;
  getObject(key: string, objectKey: string): Promise<any>;
  setArray(key: string, value: any): Promise<void>;
  
  // Abstract methods for implementation
  abstract _set(key: string, value: any, table: string): Promise<void>;
  abstract _get(key: string, table: string): Promise<any>;
}

interface DataStoreOptions {
  timeout?: number;
  retries?: number;
  [key: string]: any;
}

interface StoredData {
  key: string;
  value: any;
  table: string;
  timestamp?: Date;
}