or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

data-processing-summary.mdindex.mdnetwork-utilities.mdprotocol-message-utilities.mdrequest-management.mdretry-rate-limiting.mdstorage-services.mdtree-blob-utilities.mdurl-compression.md
tile.json

tree-blob-utilities.mddocs/

Tree and Blob Utilities

Data structure utilities for converting between different tree formats and handling blob operations. Essential for working with Fluid Framework's tree-based data structures.

Capabilities

Tree Entry Classes

Classes for creating different types of tree entries that implement the ITreeEntry interface.

/**
 * Tree entry for blob nodes (files)
 */
class BlobTreeEntry implements ITreeEntry {
  /**
   * @param path - Path of the blob within the tree
   * @param contents - String content of the blob
   * @param encoding - Text encoding, defaults to "utf-8"
   */
  constructor(path: string, contents: string, encoding?: "utf-8" | "base64");

  readonly mode: FileMode.File;
  readonly type: TreeEntry.Blob;
  readonly path: string;
  readonly value: IBlob;
}

/**
 * Tree entry for directory nodes
 */
class TreeTreeEntry implements ITreeEntry {
  /**
   * @param path - Path of the directory within the tree
   * @param value - ITree representing the directory structure
   */
  constructor(path: string, value: ITree);

  readonly mode: FileMode.Directory;
  readonly type: TreeEntry.Tree;
  readonly path: string;
  readonly value: ITree;
}

/**
 * Tree entry for external blob attachments
 */
class AttachmentTreeEntry implements ITreeEntry {
  /**
   * @param path - Path of the attachment within the tree
   * @param id - External blob ID reference
   */
  constructor(path: string, id: string);

  readonly mode: FileMode.File;
  readonly type: TreeEntry.Attachment;
  readonly path: string;
  readonly value: IAttachment;
}

Usage Examples:

import { 
  BlobTreeEntry, 
  TreeTreeEntry, 
  AttachmentTreeEntry 
} from "@fluidframework/driver-utils";

// Create blob entry for a text file
const textBlob = new BlobTreeEntry(
  "documents/readme.txt",
  "Welcome to our application!",
  "utf-8"
);

// Create blob entry for binary data
const binaryBlob = new BlobTreeEntry(
  "images/logo.png",
  base64ImageData,
  "base64"
);

// Create directory entry
const directoryEntry = new TreeTreeEntry(
  "config",
  {
    id: "config-tree-id",
    entries: [
      /* tree entries for config files */
    ]
  }
);

// Create attachment entry referencing external blob
const attachmentEntry = new AttachmentTreeEntry(
  "attachments/large-file.zip",
  "external-blob-id-123"
);

Tree Conversion Functions

Functions for converting between different tree and snapshot formats used throughout the Fluid Framework.

/**
 * Converts ISummaryTree format to ITree format
 * Handles combined app+protocol summary layouts
 * @param summaryTree - Summary tree to convert
 * @returns Converted ITree structure
 */
function convertSummaryTreeToSnapshotITree(summaryTree: ISummaryTree): ITree;

/**
 * Builds hierarchical snapshot tree from flat tree entries
 * @param entries - Array of tree entries to organize into hierarchy
 * @param blobMap - Map to populate with blob UUIDs and content
 * @returns Hierarchical snapshot tree structure
 */
function buildSnapshotTree(
  entries: ITreeEntry[], 
  blobMap: Map<string, ArrayBufferLike>
): ISnapshotTree;

Usage Examples:

import { 
  convertSummaryTreeToSnapshotITree,
  buildSnapshotTree,
  BlobTreeEntry
} from "@fluidframework/driver-utils";

// Convert summary tree to snapshot tree
const summaryTree: ISummaryTree = {
  type: SummaryType.Tree,
  tree: {
    ".app": {
      type: SummaryType.Tree,
      tree: {
        "data.json": {
          type: SummaryType.Blob,
          content: '{"key": "value"}'
        }
      }
    }
  }
};

const snapshotTree = convertSummaryTreeToSnapshotITree(summaryTree);

// Build snapshot tree from entries
const entries = [
  new BlobTreeEntry("config.json", '{"setting": true}'),
  new BlobTreeEntry("data/items.json", '["item1", "item2"]'),
  new BlobTreeEntry("readme.md", "# Project Documentation")
];

const blobMap = new Map<string, ArrayBufferLike>();
const snapshot = buildSnapshotTree(entries, blobMap);

// blobMap now contains blob content keyed by UUID
console.log(`Created ${blobMap.size} blobs`);

Storage Utilities

Utility functions for working with snapshots and extracting tree structures.

/**
 * Extracts ISnapshotTree from either ISnapshot or ISnapshotTree input
 * @param tree - Input that may be either format
 * @returns The snapshot tree portion
 */
function getSnapshotTree(tree: ISnapshotTree | ISnapshot): ISnapshotTree;

/**
 * Type guard to check if object is ISnapshot
 * @param obj - Object to check
 * @returns True if object is ISnapshot (checks for snapshotFormatV: 1)
 */
function isInstanceOfISnapshot(
  obj: ISnapshotTree | ISnapshot | undefined
): obj is ISnapshot;

Usage Examples:

import { 
  getSnapshotTree, 
  isInstanceOfISnapshot 
} from "@fluidframework/driver-utils";

// Handle both snapshot formats
function processSnapshot(data: ISnapshotTree | ISnapshot) {
  const tree = getSnapshotTree(data);
  
  // Now work with the tree structure
  processTreeNodes(tree);
}

// Type checking
function analyzeSnapshotData(data: ISnapshotTree | ISnapshot | undefined) {
  if (!data) {
    console.log("No snapshot data");
    return;
  }

  if (isInstanceOfISnapshot(data)) {
    console.log("Processing full snapshot with blobs");
    console.log(`Snapshot has ${Object.keys(data.blobs).length} blobs`);
    const tree = data.snapshotTree;
    // Process tree...
  } else {
    console.log("Processing tree-only snapshot");
    // data is ISnapshotTree
    // Process tree directly...
  }
}

Advanced Usage Patterns

Building Complex Tree Structures

import { 
  BlobTreeEntry, 
  TreeTreeEntry, 
  buildSnapshotTree 
} from "@fluidframework/driver-utils";

class DocumentTreeBuilder {
  private entries: ITreeEntry[] = [];

  addDocument(path: string, content: any): this {
    const jsonContent = JSON.stringify(content, null, 2);
    this.entries.push(new BlobTreeEntry(path, jsonContent));
    return this;
  }

  addBinaryFile(path: string, data: ArrayBuffer): this {
    const base64Data = this.arrayBufferToBase64(data);
    this.entries.push(new BlobTreeEntry(path, base64Data, "base64"));
    return this;
  }

  addDirectory(path: string, subtree: ITree): this {
    this.entries.push(new TreeTreeEntry(path, subtree));
    return this;
  }

  build(): { tree: ISnapshotTree; blobs: Map<string, ArrayBufferLike> } {
    const blobMap = new Map<string, ArrayBufferLike>();
    const tree = buildSnapshotTree(this.entries, blobMap);
    return { tree, blobs: blobMap };
  }

  private arrayBufferToBase64(buffer: ArrayBuffer): string {
    const bytes = new Uint8Array(buffer);
    const binary = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
    return btoa(binary);
  }
}

// Usage
const builder = new DocumentTreeBuilder();
const result = builder
  .addDocument("manifest.json", { version: "1.0", name: "MyApp" })
  .addDocument("config/settings.json", { debug: true })
  .addBinaryFile("assets/icon.png", iconBuffer)
  .build();

console.log(`Built tree with ${result.blobs.size} blobs`);

Tree Transformation Pipeline

import { 
  convertSummaryTreeToSnapshotITree,
  getSnapshotTree,
  BlobTreeEntry
} from "@fluidframework/driver-utils";

class TreeTransformer {
  static transformSummaryToSnapshot(summaryTree: ISummaryTree): ITree {
    return convertSummaryTreeToSnapshotITree(summaryTree);
  }

  static extractTextBlobs(tree: ISnapshotTree): Map<string, string> {
    const textBlobs = new Map<string, string>();
    
    this.walkTree(tree, "", (path, blobId) => {
      // This would need access to blob storage to read content
      // Simplified for example
      if (path.endsWith('.json') || path.endsWith('.txt') || path.endsWith('.md')) {
        textBlobs.set(path, blobId);
      }
    });
    
    return textBlobs;
  }

  private static walkTree(
    tree: ISnapshotTree, 
    currentPath: string,
    onBlob: (path: string, blobId: string) => void
  ): void {
    // Process blobs at current level
    for (const [name, blobId] of Object.entries(tree.blobs)) {
      const fullPath = currentPath ? `${currentPath}/${name}` : name;
      onBlob(fullPath, blobId);
    }

    // Recursively process subtrees
    for (const [name, subtree] of Object.entries(tree.trees)) {
      const fullPath = currentPath ? `${currentPath}/${name}` : name;
      this.walkTree(subtree, fullPath, onBlob);
    }
  }

  static createTreeFromPaths(
    pathToBlobMap: Map<string, string>
  ): ITreeEntry[] {
    const entries: ITreeEntry[] = [];
    
    for (const [path, content] of pathToBlobMap) {
      entries.push(new BlobTreeEntry(path, content));
    }
    
    return entries;
  }
}

// Usage
const transformer = TreeTransformer;

// Transform summary to snapshot format
const snapshotTree = transformer.transformSummaryToSnapshot(summaryData);

// Extract text files from a tree
const textFiles = transformer.extractTextBlobs(snapshotTree);

// Create tree entries from paths
const newEntries = transformer.createTreeFromPaths(
  new Map([
    ["docs/readme.md", "# Welcome"],
    ["config.json", '{"version": 1}']
  ])
);

Snapshot Analysis Utility

import { 
  getSnapshotTree, 
  isInstanceOfISnapshot 
} from "@fluidframework/driver-utils";

class SnapshotAnalyzer {
  static analyze(snapshot: ISnapshotTree | ISnapshot): SnapshotStats {
    const tree = getSnapshotTree(snapshot);
    const stats: SnapshotStats = {
      treeNodeCount: 0,
      blobCount: 0,
      totalBlobSize: 0,
      maxDepth: 0,
      fileTypes: new Map()
    };

    // If it's a full snapshot, we can get blob sizes
    let blobSizes: Map<string, number> | undefined;
    if (isInstanceOfISnapshot(snapshot)) {
      blobSizes = new Map();
      for (const [id, blob] of Object.entries(snapshot.blobs)) {
        const size = blob.byteLength || blob.size || 0;
        blobSizes.set(id, size);
      }
    }

    this.analyzeTree(tree, stats, 0, blobSizes);
    return stats;
  }

  private static analyzeTree(
    tree: ISnapshotTree,
    stats: SnapshotStats,
    depth: number,
    blobSizes?: Map<string, number>
  ): void {
    stats.treeNodeCount++;
    stats.maxDepth = Math.max(stats.maxDepth, depth);

    // Analyze blobs
    for (const [name, blobId] of Object.entries(tree.blobs)) {
      stats.blobCount++;
      
      const extension = name.split('.').pop()?.toLowerCase() || 'unknown';
      stats.fileTypes.set(extension, (stats.fileTypes.get(extension) || 0) + 1);
      
      if (blobSizes) {
        const size = blobSizes.get(blobId) || 0;
        stats.totalBlobSize += size;
      }
    }

    // Recursively analyze subtrees
    for (const subtree of Object.values(tree.trees)) {
      this.analyzeTree(subtree, stats, depth + 1, blobSizes);
    }
  }
}

interface SnapshotStats {
  treeNodeCount: number;
  blobCount: number;
  totalBlobSize: number;
  maxDepth: number;
  fileTypes: Map<string, number>;
}

// Usage
const stats = SnapshotAnalyzer.analyze(snapshotData);
console.log(`Tree has ${stats.treeNodeCount} nodes and ${stats.blobCount} blobs`);
console.log(`Max depth: ${stats.maxDepth}, Total blob size: ${stats.totalBlobSize} bytes`);