CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-fluidframework--common-utils

Collection of utility functions for Fluid Framework including async operations, data structures, performance monitoring, and cross-platform compatibility.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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.

docs

assertions.md

async-operations.md

buffer-operations.md

data-structures.md

encoding-hashing.md

event-handling.md

index.md

performance-monitoring.md

utility-functions.md

tile.json