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

data-structures.mddocs/

Data Structures

Efficient data structures including priority heaps and range tracking utilities designed for high-performance scenarios in Fluid Framework applications.

Capabilities

Heap Data Structure

Ordered heap implementation with customizable comparison functions for priority queue operations.

/**
 * Comparer interface for heap ordering
 */
interface IComparer<T> {
  /** Minimum value for the type */
  min: T;
  /** Comparison function returning negative, zero, or positive values */
  compare(a: T, b: T): number;
}

/**
 * Built-in number comparer for numeric heaps
 */
const NumberComparer: IComparer<number>;

/**
 * Heap node interface for direct heap manipulation
 */
interface IHeapNode<T> {
  /** The stored value */
  value: T;
  /** Internal heap position (managed by heap) */
  position: number;
}

/**
 * Ordered heap data structure with customizable comparison
 * Maintains heap property where parent <= children (min-heap by default)
 */
class Heap<T> {
  /**
   * Creates a new heap with required comparer
   * @param comp - Comparer for heap ordering (required parameter)
   */
  constructor(comp: IComparer<T>);
  
  /**
   * Gets and removes the minimum element from the heap
   * @returns The minimum element value
   */
  get(): T;
  
  /**
   * Gets the minimum element without removing it
   * @returns The heap node containing the minimum element
   */
  peek(): IHeapNode<T>;
  
  /**
   * Adds a new value to the heap
   * @param value - Value to add
   * @returns The heap node that was created
   */
  add(value: T): IHeapNode<T>;
  
  /**
   * Updates the position of a node after its value has changed
   * @param node - The heap node to update
   */
  update(node: IHeapNode<T>): void;
  
  /**
   * Removes a specific node from the heap
   * @param node - The heap node to remove
   */
  remove(node: IHeapNode<T>): void;
  
  /**
   * Gets the number of elements in the heap
   * @returns The number of elements in the heap
   */
  count(): number;
}

Usage Examples:

import { Heap, NumberComparer } from "@fluidframework/common-utils";

// Basic numeric heap (min-heap)
const numberHeap = new Heap<number>(NumberComparer);
numberHeap.add(5);
numberHeap.add(2);
numberHeap.add(8);
numberHeap.add(1);

console.log(numberHeap.peek().value); // 1 (minimum)
console.log(numberHeap.get());  // 1 (removes and returns minimum)
console.log(numberHeap.get());  // 2 (next minimum)

// Custom comparer for max-heap
const maxComparer = {
  min: Number.NEGATIVE_INFINITY,
  compare: (a: number, b: number) => b - a // Reverse comparison for max-heap
};

const maxHeap = new Heap<number>(maxComparer);
maxHeap.add(5);
maxHeap.add(2);
maxHeap.add(8);
console.log(maxHeap.peek().value); // 8 (maximum in max-heap)

// Object heap with custom comparison
interface Task {
  id: string;
  priority: number;
  description: string;
}

const taskComparer: IComparer<Task> = {
  min: { id: "", priority: Number.NEGATIVE_INFINITY, description: "" },
  compare: (a, b) => a.priority - b.priority // Lower number = higher priority
};

const taskQueue = new Heap<Task>(taskComparer);

taskQueue.add({ id: "1", priority: 3, description: "Low priority task" });
taskQueue.add({ id: "2", priority: 1, description: "High priority task" });
taskQueue.add({ id: "3", priority: 2, description: "Medium priority task" });

// Process tasks in priority order
while (taskQueue.count() > 0) {
  const nextTask = taskQueue.get();
  console.log(`Processing: ${nextTask.description}`);
}

// Using heap nodes for updates
const urgentTask = { id: "4", priority: 5, description: "Initially low priority" };
const taskNode = taskQueue.add(urgentTask);

// Later, update priority
urgentTask.priority = 0; // Make it highest priority
taskQueue.update(taskNode); // Heap reorders accordingly

Range Tracker

Tracks 1:N relationships between two ranges, commonly used for mapping between different coordinate systems.

/**
 * Range interface representing a span with primary and secondary coordinates
 */
interface IRange {
  /** Primary range start position */
  primary: number;
  /** Secondary range start position */  
  secondary: number;
  /** Length of the range */
  length: number;
}

/**
 * Serializable snapshot of range tracker state
 */
interface IRangeTrackerSnapshot {
  /** Array of tracked ranges */
  ranges: IRange[];
  /** Last primary position */
  lastPrimary: number;
  /** Last secondary position */
  lastSecondary: number;
}

/**
 * Tracks 1:N relation between two ranges
 * Used by Fluid Framework deli for branch mapping and coordinate transformation
 */
class RangeTracker {
  /**
   * Creates a new RangeTracker
   */
  constructor();
  
  /**
   * Adds a new range mapping
   * @param primaryStart - Start position in primary coordinate space
   * @param primaryEnd - End position in primary coordinate space  
   * @param secondaryStart - Start position in secondary coordinate space
   */
  add(primaryStart: number, primaryEnd: number, secondaryStart: number): void;
  
  /**
   * Gets range information for a primary position
   * @param primaryPosition - Position in primary coordinate space
   * @param primaryEnd - Optional end position for range queries
   * @returns Range information or undefined if not found
   */
  get(primaryPosition: number, primaryEnd?: number): IRange | undefined;
  
  /**
   * Updates the base positions for relative calculations
   * @param primaryBase - New primary base position
   * @param secondaryBase - New secondary base position  
   */
  updateBase(primaryBase: number, secondaryBase: number): void;
  
  /**
   * Serializes the current state for persistence
   * @returns Serializable snapshot of current state
   */
  serialize(): IRangeTrackerSnapshot;
  
  /**
   * Restores state from a serialized snapshot
   * @param snapshot - Previously serialized snapshot
   */
  deserialize(snapshot: IRangeTrackerSnapshot): void;
}

Usage Examples:

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

// Document coordinate mapping
const coordMapper = new RangeTracker();

// Map document sections to display coordinates
// Document chars 0-100 map to display pixels 0-500
coordMapper.add(0, 100, 0);

// Document chars 100-200 map to display pixels 600-1100 (with gap)
coordMapper.add(100, 200, 600);

// Find display coordinates for document position
const range1 = coordMapper.get(50); // Document position 50
console.log(range1?.secondary); // Display coordinate for doc position 50

const range2 = coordMapper.get(150); // Document position 150  
console.log(range2?.secondary); // Display coordinate for doc position 150

// Branch mapping in collaborative editing
class BranchMapper {
  private tracker = new RangeTracker();
  
  addEdit(localStart: number, localEnd: number, remoteStart: number): void {
    this.tracker.add(localStart, localEnd, remoteStart);
  }
  
  mapToRemote(localPosition: number): number | undefined {
    const range = this.tracker.get(localPosition);
    if (!range) return undefined;
    
    const offset = localPosition - range.primary;
    return range.secondary + offset;
  }
  
  save(): string {
    return JSON.stringify(this.tracker.serialize());
  }
  
  load(serialized: string): void {
    const snapshot = JSON.parse(serialized);
    this.tracker.deserialize(snapshot);
  }
}

// Time series data mapping
const timeMapper = new RangeTracker();

// Map time ranges to data indices
// Hours 0-24 (day 1) map to data indices 0-1440 (minute-level data)
timeMapper.add(0, 24, 0);

// Hours 24-48 (day 2) map to data indices 1440-2880
timeMapper.add(24, 48, 1440);

// Find data index for specific time
const dataRange = timeMapper.get(36); // Hour 36 (day 2, hour 12)
if (dataRange) {
  const hourOffset = 36 - dataRange.primary;
  const dataIndex = dataRange.secondary + (hourOffset * 60); // Convert to minutes
  console.log(`Hour 36 maps to data index: ${dataIndex}`);
}

// Viewport mapping with dynamic updates
class ViewportMapper {
  private tracker = new RangeTracker();
  
  addViewport(contentStart: number, contentEnd: number, viewStart: number): void {
    this.tracker.add(contentStart, contentEnd, viewStart);
  }
  
  scrollTo(newContentBase: number, newViewBase: number): void {
    this.tracker.updateBase(newContentBase, newViewBase);
  }
  
  getViewPosition(contentPosition: number): number | undefined {
    const range = this.tracker.get(contentPosition);
    if (!range) return undefined;
    
    return range.secondary + (contentPosition - range.primary);
  }
}

Advanced Usage Patterns

Priority Queue with Custom Objects

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

// Event scheduling system
interface ScheduledEvent {
  id: string;
  timestamp: number;
  callback: () => void;
  data?: any;
}

class EventScheduler {
  private eventHeap = new Heap<ScheduledEvent>({
    min: { id: "", timestamp: 0, callback: () => {} },
    compare: (a, b) => a.timestamp - b.timestamp
  });
  
  schedule(event: ScheduledEvent): void {
    this.eventHeap.add(event);
  }
  
  processNext(): ScheduledEvent | null {
    if (this.eventHeap.isEmpty) return null;
    
    const nextEvent = this.eventHeap.get();
    if (nextEvent.timestamp <= Date.now()) {
      nextEvent.callback();
      return nextEvent;
    }
    
    // Put it back if not ready
    this.eventHeap.add(nextEvent);
    return null;
  }
  
  getNextTimestamp(): number | null {
    if (this.eventHeap.isEmpty) return null;
    return this.eventHeap.peek().timestamp;
  }
}

// Usage
const scheduler = new EventScheduler();

scheduler.schedule({
  id: "reminder",
  timestamp: Date.now() + 5000, // 5 seconds from now
  callback: () => console.log("Reminder fired!"),
  data: { message: "Don't forget the meeting" }
});

Complex Range Mapping

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

// Multi-layer coordinate system
class MultiLayerMapper {
  private layers: Map<string, RangeTracker> = new Map();
  
  addLayer(layerId: string): void {
    this.layers.set(layerId, new RangeTracker());
  }
  
  mapRange(layerId: string, sourceStart: number, sourceEnd: number, targetStart: number): void {
    const tracker = this.layers.get(layerId);
    if (tracker) {
      tracker.add(sourceStart, sourceEnd, targetStart);
    }
  }
  
  // Map position through multiple layers
  mapThroughLayers(position: number, layerOrder: string[]): number {
    let currentPosition = position;
    
    for (const layerId of layerOrder) {
      const tracker = this.layers.get(layerId);
      if (tracker) {
        const range = tracker.get(currentPosition);
        if (range) {
          const offset = currentPosition - range.primary;
          currentPosition = range.secondary + offset;
        }
      }
    }
    
    return currentPosition;
  }
}

These data structures provide efficient solutions for priority-based processing and coordinate mapping scenarios common in Fluid Framework applications.

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