or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdasync-operations.mdbuffer-operations.mddata-structures.mdencoding-hashing.mdevent-handling.mdindex.mdperformance-monitoring.mdutility-functions.md
tile.json

utility-functions.mddocs/

Utility Functions

Collection of utility functions for common programming tasks including lazy initialization, disposal patterns, rate limiting, safe JSON parsing, and logging infrastructure.

Capabilities

Lazy Initialization

Helper for lazy-initialized values ensuring single generation and immutability.

/**
 * Helper for lazy-initialized values with thread-safe single generation
 * Ensures the value is computed only once and becomes immutable thereafter
 */
class Lazy<T> {
  /**
   * Creates a new Lazy instance
   * @param valueGenerator - Function that generates the value when first accessed
   */
  constructor(valueGenerator: () => T);
  
  /** Whether the value has been generated yet */
  get evaluated(): boolean;
  
  /** 
   * Gets the lazy value, generating it if this is the first access
   * Subsequent calls return the same cached value
   */
  get value(): T;
}

Usage Examples:

import { Lazy } from "@fluidframework/common-utils";

// Expensive computation that's only done when needed
const expensiveResult = new Lazy(() => {
  console.log("Computing expensive result...");
  return performExpensiveCalculation();
});

// Value hasn't been computed yet
console.log(expensiveResult.evaluated); // false

// First access triggers computation
const result1 = expensiveResult.value; // Logs "Computing expensive result..."
console.log(expensiveResult.evaluated); // true

// Subsequent accesses return cached value
const result2 = expensiveResult.value; // No logging, returns cached value
console.log(result1 === result2); // true

// Configuration loading
const config = new Lazy(() => {
  console.log("Loading configuration...");
  return JSON.parse(fs.readFileSync("config.json", "utf8"));
});

// Only loads when first accessed
function getApiUrl(): string {
  return config.value.apiUrl;
}

// Database connection
const dbConnection = new Lazy(async () => {
  console.log("Establishing database connection...");
  return await connectToDatabase();
});

// Can be used with async values too (though the getter itself is synchronous)
// You'd typically wrap this in another async function:
async function getDb() {
  return await dbConnection.value;
}

// Factory pattern with lazy initialization
class ServiceFactory {
  private static readonly userService = new Lazy(() => new UserService());
  private static readonly emailService = new Lazy(() => new EmailService());
  private static readonly loggerService = new Lazy(() => new LoggerService());
  
  static getUserService(): UserService {
    return this.userService.value;
  }
  
  static getEmailService(): EmailService {
    return this.emailService.value;
  }
  
  static getLogger(): LoggerService {
    return this.loggerService.value;
  }
}

// Services are only instantiated when first requested
const users = ServiceFactory.getUserService(); // Creates UserService
const sameUsers = ServiceFactory.getUserService(); // Returns same instance

Disposal Patterns

Wrapper for functions that only execute if the target object hasn't been disposed.

/**
 * Returns a wrapper function that only executes if IDisposable object is not disposed
 * @param disposable - Object that implements IDisposable interface
 * @returns Function that wraps other functions with disposal checking
 * @throws Error if called after disposal
 */
function doIfNotDisposed<TDisposable extends IDisposable>(
  disposable: TDisposable
): <TArgs extends any[], TReturn>(
  fn: (this: TDisposable, ...args: TArgs) => TReturn
) => (...args: TArgs) => TReturn;

/**
 * Standard disposable interface
 */
interface IDisposable {
  /** Whether the object has been disposed */
  readonly disposed: boolean;
  /** Disposes the object and releases resources */
  dispose(): void;
}

Usage Examples:

import { doIfNotDisposed } from "@fluidframework/common-utils";

// Resource management class
class DatabaseConnection implements IDisposable {
  private _disposed = false;
  private connection: any;
  
  constructor() {
    this.connection = establishConnection();
  }
  
  get disposed(): boolean {
    return this._disposed;
  }
  
  // Wrap methods that shouldn't run after disposal
  query = doIfNotDisposed(this)((sql: string) => {
    console.log(`Executing query: ${sql}`);
    return this.connection.query(sql);
  });
  
  transaction = doIfNotDisposed(this)((callback: () => void) => {
    console.log("Starting transaction");
    this.connection.beginTransaction();
    try {
      callback();
      this.connection.commit();
    } catch (error) {
      this.connection.rollback();
      throw error;
    }
  });
  
  dispose(): void {
    if (!this._disposed) {
      this.connection.close();
      this._disposed = true;
    }
  }
}

// Usage
const db = new DatabaseConnection();

// Normal usage
db.query("SELECT * FROM users"); // Works fine

// After disposal
db.dispose();
try {
  db.query("SELECT * FROM users"); // Throws error
} catch (error) {
  console.error("Operation failed:", error.message);
}

// Event handler cleanup
class EventHandler implements IDisposable {
  private _disposed = false;
  
  get disposed(): boolean {
    return this._disposed;
  }
  
  // Protected event handlers
  handleClick = doIfNotDisposed(this)((event: MouseEvent) => {
    console.log("Click handled");
    this.processClick(event);
  });
  
  handleKeypress = doIfNotDisposed(this)((event: KeyboardEvent) => {
    console.log("Keypress handled");
    this.processKeypress(event);
  });
  
  private processClick(event: MouseEvent): void {
    // Click processing logic
  }
  
  private processKeypress(event: KeyboardEvent): void {
    // Keypress processing logic
  }
  
  dispose(): void {
    this._disposed = true;
    // Remove event listeners, etc.
  }
}

// Async operation protection
class AsyncService implements IDisposable {
  private _disposed = false;
  
  get disposed(): boolean {
    return this._disposed;
  }
  
  fetchData = doIfNotDisposed(this)(async (url: string) => {
    const response = await fetch(url);
    return response.json();
  });
  
  processData = doIfNotDisposed(this)((data: any) => {
    // Processing logic that shouldn't run after disposal
    return this.transform(data);
  });
  
  private transform(data: any): any {
    return { ...data, processed: true };
  }
  
  dispose(): void {
    this._disposed = true;
  }
}

Rate Limiting

Rate limiter for controlling request frequency within time windows.

/**
 * Rate limiter for client requests within sliding time windows
 * Filters items based on configured maximum count and window size
 */
class RateLimiter {
  /**
   * Creates a new rate limiter
   * @param maxCount - Maximum number of items allowed in the time window
   * @param windowSizeInMs - Time window size in milliseconds
   */
  constructor(maxCount: number, windowSizeInMs: number);
  
  /**
   * Filters items based on rate limiting rules
   * Returns only the items that are allowed within the current time window
   * @param items - Array of items to filter
   * @returns Array containing only the approved items
   */
  filter<T>(items: T[]): T[];
}

Usage Examples:

import { RateLimiter } from "@fluidframework/common-utils";

// Basic rate limiting
const rateLimiter = new RateLimiter(5, 60000); // 5 requests per minute

// Filter API requests
const requests = [
  "request1", "request2", "request3", "request4", "request5",
  "request6", "request7", "request8" // These last 3 would be rate limited
];

const allowedRequests = rateLimiter.filter(requests);
console.log(allowedRequests); // ["request1", "request2", "request3", "request4", "request5"]

// Email rate limiting
class EmailService {
  private rateLimiter = new RateLimiter(10, 3600000); // 10 emails per hour
  
  async sendEmails(emails: { to: string; subject: string; body: string }[]): Promise<void> {
    const allowedEmails = this.rateLimiter.filter(emails);
    
    console.log(`Sending ${allowedEmails.length} of ${emails.length} emails`);
    
    for (const email of allowedEmails) {
      await this.sendSingleEmail(email);
    }
    
    const rateLimited = emails.length - allowedEmails.length;
    if (rateLimited > 0) {
      console.log(`${rateLimited} emails were rate limited`);
    }
  }
  
  private async sendSingleEmail(email: any): Promise<void> {
    // Email sending logic
    console.log(`Sending email to: ${email.to}`);
  }
}

// API endpoint protection
class ApiController {
  private rateLimiter = new RateLimiter(100, 60000); // 100 requests per minute
  
  handleBatchRequest(requests: any[]): any[] {
    const allowedRequests = this.rateLimiter.filter(requests);
    
    if (allowedRequests.length < requests.length) {
      console.warn(`Rate limited ${requests.length - allowedRequests.length} requests`);
    }
    
    return allowedRequests.map(req => this.processRequest(req));
  }
  
  private processRequest(request: any): any {
    return { ...request, processed: true, timestamp: Date.now() };
  }
}

// Multi-tier rate limiting
class MultiTierRateLimiter {
  private shortTerm = new RateLimiter(10, 60000);    // 10 per minute
  private longTerm = new RateLimiter(100, 3600000);  // 100 per hour
  
  filter<T>(items: T[]): T[] {
    // Apply both limits
    const shortTermFiltered = this.shortTerm.filter(items);
    return this.longTerm.filter(shortTermFiltered);
  }
}

const multiLimiter = new MultiTierRateLimiter();
const filteredRequests = multiLimiter.filter(manyRequests);

Safe JSON Parsing

Wrapper for JSON.parse that returns undefined instead of throwing on error.

/**
 * Safe wrapper for JSON.parse that returns undefined on parsing errors
 * @param json - JSON string to parse
 * @returns Parsed object or undefined if parsing fails
 */
function safelyParseJSON(json: string): any;

Usage Examples:

import { safelyParseJSON } from "@fluidframework/common-utils";

// Safe parsing without try-catch
const validJson = '{"name": "John", "age": 30}';
const result1 = safelyParseJSON(validJson);
console.log(result1); // { name: "John", age: 30 }

const invalidJson = '{"name": "John", "age": 30'; // Missing closing brace
const result2 = safelyParseJSON(invalidJson);
console.log(result2); // undefined

// Configuration loading with fallback
function loadConfig(configString: string) {
  const config = safelyParseJSON(configString);
  return config || {
    // Default configuration
    apiUrl: "http://localhost:3000",
    timeout: 5000,
    retries: 3
  };
}

// API response handling
async function fetchUserData(userId: string) {
  const response = await fetch(`/api/users/${userId}`);
  const text = await response.text();
  
  const userData = safelyParseJSON(text);
  if (!userData) {
    console.error("Failed to parse user data response");
    return null;
  }
  
  return userData;
}

// Batch processing with error resilience
function processJsonItems(jsonStrings: string[]) {
  const validItems = jsonStrings
    .map(json => safelyParseJSON(json))
    .filter(item => item !== undefined);
  
  console.log(`Processed ${validItems.length} of ${jsonStrings.length} items`);
  return validItems;
}

// Local storage helper
class SafeLocalStorage {
  static getItem<T>(key: string, defaultValue: T): T {
    try {
      const stored = localStorage.getItem(key);
      if (stored === null) return defaultValue;
      
      const parsed = safelyParseJSON(stored);
      return parsed !== undefined ? parsed : defaultValue;
    } catch {
      return defaultValue;
    }
  }
  
  static setItem<T>(key: string, value: T): boolean {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch {
      return false;
    }
  }
}

// Usage
const userPrefs = SafeLocalStorage.getItem("userPreferences", {
  theme: "light",
  language: "en"
});

Null Loggers

Null logger implementations for telemetry interfaces when logging is disabled.

/**
 * Null logger for ITelemetryBaseLogger interface
 * Provides no-op implementation for basic telemetry logging
 */
class BaseTelemetryNullLogger {
  /** No-op send method that discards all telemetry data */
  send(event: any): void;
}

/**
 * Null logger for ITelemetryLogger interface  
 * Provides no-op implementation for all telemetry event types
 */
class TelemetryNullLogger extends BaseTelemetryNullLogger {
  /** No-op method for sending generic telemetry events */
  sendTelemetryEvent(event: any, properties?: any, measurements?: any): void;
  
  /** No-op method for sending error telemetry events */
  sendErrorEvent(event: any, error?: any): void;
  
  /** No-op method for sending performance telemetry events */
  sendPerformanceEvent(event: any, measurements?: any): void;
}

Usage Examples:

import { TelemetryNullLogger, BaseTelemetryNullLogger } from "@fluidframework/common-utils";

// Conditional telemetry
const logger = process.env.ENABLE_TELEMETRY === "true" 
  ? new RealTelemetryLogger()
  : new TelemetryNullLogger();

// Logger works the same way, but null logger discards everything
logger.sendTelemetryEvent("user-action", { action: "click", target: "button" });
logger.sendErrorEvent("api-error", new Error("Network timeout"));
logger.sendPerformanceEvent("page-load", { duration: 1500 });

// Service with optional telemetry
class UserService {
  constructor(private logger: ITelemetryLogger = new TelemetryNullLogger()) {}
  
  async createUser(userData: any): Promise<User> {
    const startTime = Date.now();
    
    try {
      const user = await this.persistUser(userData);
      
      this.logger.sendTelemetryEvent("user-created", {
        userId: user.id,
        source: "api"
      });
      
      this.logger.sendPerformanceEvent("user-creation", {
        duration: Date.now() - startTime
      });
      
      return user;
    } catch (error) {
      this.logger.sendErrorEvent("user-creation-failed", error);
      throw error;
    }
  }
  
  private async persistUser(userData: any): Promise<User> {
    // User persistence logic
    return { id: "123", ...userData };
  }
}

// Usage in different environments
const userService = new UserService(
  process.env.NODE_ENV === "production" 
    ? new ApplicationInsightsLogger()
    : new TelemetryNullLogger()
);

Utility Combinations

import { Lazy, doIfNotDisposed, RateLimiter, safelyParseJSON } from "@fluidframework/common-utils";

// Cached rate-limited service
class CachedApiService implements IDisposable {
  private _disposed = false;
  private rateLimiter = new RateLimiter(60, 60000); // 60 requests per minute
  
  // Lazy-loaded cache
  private cache = new Lazy(() => new Map<string, any>());
  
  get disposed(): boolean {
    return this._disposed;
  }
  
  // Rate-limited and disposal-protected method
  fetchData = doIfNotDisposed(this)(async (urls: string[]) => {
    const allowedUrls = this.rateLimiter.filter(urls);
    const results = [];
    
    for (const url of allowedUrls) {
      // Check cache first
      const cached = this.cache.value.get(url);
      if (cached) {
        results.push(cached);
        continue;
      }
      
      // Fetch and parse safely
      const response = await fetch(url);
      const text = await response.text();
      const data = safelyParseJSON(text);
      
      if (data) {
        this.cache.value.set(url, data);
        results.push(data);
      }
    }
    
    return results;
  });
  
  dispose(): void {
    this._disposed = true;
    this.cache.value.clear();
  }
}

These utility functions provide essential building blocks for robust, efficient applications with proper resource management and error handling.