Collection of utility functions for Fluid drivers
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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`);