github microsoft/FluidFramework client_v2.92.0
Fluid Framework v2.92.0 (minor)

4 hours ago

Contents

  • ✨ New Features
    • Array node nodeChanged events now include a delta payload (via TreeAlpha) (#26677)
  • 🌳 SharedTree DDS Changes
    • Add TreeArrayNodeAlpha with a new splice method (#26740)
  • ⚠️ Deprecations
    • Deprecate IIdCompressorCore interface (#26865)
  • Legacy API Changes
    • The deprecated getBranch API has been removed (#26796)

✨ New Features

Array node nodeChanged events now include a delta payload (via TreeAlpha) (#26677)

The nodeChanged event for array nodes (accessed via TreeAlpha.on) now provides a delta field, a sequence of ArrayNodeDeltaOp values that describe exactly what changed in the array. This lets you efficiently sync an external representation with tree changes, without taking a snapshot of the old state or diffing the entire array.

The delta follows Quill-style semantics: each op covers a contiguous run of positions in the array before the change.

  • { type: "retain", count: N }—N elements stayed in place. Their positions are unchanged, though their contents may have changed (which would fire separate nodeChanged events on those elements).
  • { type: "insert", count: N }—N elements were inserted; read their values from the current tree at these positions.
  • { type: "remove", count: N }—N elements were removed.

Trailing unchanged elements are not represented by a trailing "retain" op.

Use TreeAlpha.on to subscribe to the richer alpha events. The data passed to the callback is typed as NodeChangedDataAlpha<TNode>:

  • Object, map, and record nodes receive NodeChangedDataProperties (with a required changedProperties set).
  • Array nodes receive NodeChangedDataDelta (with a delta field).

TreeBeta.on is unchanged and does not include delta information.

Example: Applying a Delta to a Plain Array Mirror

// Walk the delta to keep a plain JS array in sync with an array node.
// retain = advance past unchanged elements,
// insert = splice in new elements,
// remove = splice out removed elements.
const mirror: number[] = [1, 2, 3];

TreeAlpha.on(myArrayNode, "nodeChanged", ({ delta }) => {
  let readPos = 0; // position in the current (post-change) tree
  let writePos = 0; // position in the mirror array

  for (const op of delta ?? []) {
    if (op.type === "retain") {
      writePos += op.count;
      readPos += op.count;
    } else if (op.type === "insert") {
      const newItems = Array.from(
        { length: op.count },
        (_, i) => myArrayNode[readPos + i],
      );
      mirror.splice(writePos, 0, ...newItems);
      writePos += op.count;
      readPos += op.count;
    } else if (op.type === "remove") {
      mirror.splice(writePos, op.count);
    }
  }
});

Example: Narrowing the Union in a Generic Handler

TreeAlpha.on(node as TreeNode, "nodeChanged", (data) => {
  if ("delta" in data) {
    // Array node — data is NodeChangedDataDelta
    console.log("array changed, delta:", data.delta);
  } else {
    // Object/map/record node — data is NodeChangedDataProperties
    console.log("properties changed:", data.changedProperties);
  }
});

Note: The delta value may be undefined in two cases:

  • The node was created locally and has not yet been inserted into a document tree (a known temporary limitation).
  • The document was updated in a way that required multiple internal change passes in a single operation (for example, a data change combined with a schema upgrade).

Change details

Commit: bf02e33

Affected packages:

  • fluid-framework
  • @fluidframework/tree

⬆️ Table of contents

🌳 SharedTree DDS Changes

Add TreeArrayNodeAlpha with a new splice method (#26740)

Adds a splice method on TreeArrayNodeAlpha that supports removing and inserting items in a single operation to align with JavaScript's Array splice API. Returns the removed items as an array. Supports negative start indices (wraps from end). Optional deleteCount (omitting removes everything from start onward). The alpha API is accessible by an asAlpha cast on existing TreeArrayNodes, or using schemaFactoryAlpha. arrayAlpha nodes are accepted wherever TreeArrayNode is expected, but not the reverse. asAlpha is bidirectional since it's the same underlying schema.

Usage

import {
  SchemaFactory,
  SchemaFactoryAlpha,
  asAlpha,
} from "@fluidframework/tree";

// Using asAlpha to cast an existing TreeArrayNode
const sf = new SchemaFactory("example");
const Inventory = sf.array("Inventory", sf.string);
const inventory = new Inventory(["Apples", "Bananas", "Pears"]);
const inventoryAlpha = asAlpha(inventory);

// Using SchemaFactoryAlpha so splice is available directly
const sf = new SchemaFactoryAlpha("example");
const Inventory = sf.arrayAlpha("Inventory", sf.string);
const inventoryAlpha = new Inventory(["Apples", "Bananas", "Pears"]);

// Remove 2 items starting at index 0, insert new items in their place
const removed = inventoryAlpha.splice(0, 2, "Oranges", "Grapes");
// removed: ["Apples", "Bananas"]
// inventory: ["Oranges", "Grapes", "Pears"]

// Removed everything from index 1 onward (omitting deleteCount)
const rest = inventoryAlpha.splice(1);
// rest: ["Grapes", "Pears"]
// inventory: ["Oranges"]

Change details

Commit: f2b0cf9

Affected packages:

  • fluid-framework
  • @fluidframework/tree

⬆️ Table of contents

⚠️ Deprecations

Deprecate IIdCompressorCore interface (#26865)

The IIdCompressorCore interface is deprecated and will be removed from the public API surface in 2.100.0. This also affects the return types of createIdCompressor and deserializeIdCompressor, which currently return IIdCompressor & IIdCompressorCore but will be narrowed to IIdCompressor.

Migration

  • serialize(): Use the new serializeIdCompressor(compressor, withSession) free function instead of calling compressor.serialize(withSession) directly.
  • takeNextCreationRange(), takeUnfinalizedCreationRange(), finalizeCreationRange(), beginGhostSession(): These are internal runtime operations that should not be called by external consumers. If you depend on these APIs, please file an issue on the FluidFramework repository describing your use case.
  • Return types of createIdCompressor / deserializeIdCompressor: Stop relying on the IIdCompressorCore portion of the intersection type. Type your variables as IIdCompressor instead of IIdCompressor & IIdCompressorCore.

Change details

Commit: 2e890f6

Affected packages:

  • @fluidframework/id-compressor

⬆️ Table of contents

Legacy API Changes

The deprecated getBranch API has been removed (#26796)

To obtain a branch-like object, create a view from your tree via viewWith. Or, use TreeAlpha.context to get a view from a TreeNode.

Change details

Commit: e80a48e

Affected packages:

  • fluid-framework
  • @fluidframework/tree

⬆️ Table of contents

🛠️ Start Building Today!

Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!

Don't miss a new FluidFramework release

NewReleases is sending notifications on new releases.