Data structure utilities for converting between different tree formats and handling blob operations. Essential for working with Fluid Framework's tree-based data structures.
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"
);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`);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...
}
}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`);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}']
])
);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`);