Collection of utility functions for Fluid Framework including async operations, data structures, performance monitoring, and cross-platform compatibility.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Collection of utility functions for common programming tasks including lazy initialization, disposal patterns, rate limiting, safe JSON parsing, and logging infrastructure.
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 instanceWrapper 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 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);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 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()
);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.