Contents
- ✨ New Features
- Simplify creating attached Fluid containers with ServiceClient (#27789)
- Settled change notification (#27814)
- 🌳 SharedTree DDS Changes
- Array node deltas now cover the complete array (#27809)
- Add getOrInsert and getOrInsertComputed methods to TreeMapNodeAlpha (#27787)
- SharedTree now emits telemetry when it heals an unresolvable identifier on decode (#27756)
- Array insertion anchors now track their index from change deltas (#27697)
- Rename the TextAsTree domain to PlainText (#27853)
- Promote FormattedText APIs to alpha (#27843)
- Removes legacy tree-agent APIs (#27868)
- 🐛 Bug Fixes
- Fix extra blank lines in collaborating Quill editors (#27809)
✨ New Features
Simplify creating attached Fluid containers with ServiceClient (#27789)
Added ServiceClient.createAttachedContainer which creates and attaches a Fluid container in one operation. It is a convenient shorthand for calling createContainer followed by attach when detached-container access is not needed.
const container = await client.createAttachedContainer(dataStoreKind);Change details
Commit: 1671447
Affected packages:
- @fluidframework/driver-definitions
- @fluidframework/local-driver
- fluid-framework
⬆️ Table of contents
Settled change notification (#27814)
LocalChangeMetadata now exposes an events: Listenable<LocalCommitEvents> property that fires a "settled" event once a commit has been ordered by the sequencing service.
Once a commit is sequenced, the following guarantees hold:
- The changes carried by the commit have been persisted and other peers are able to see them.
- There can be no more concurrent changes sequenced before this commit, which means this commit has reached its settled form.
The "settled" event provides details about the outcome of applying this settled form. This can be used by an application to determine whether any constraints associated with the commits were violated.
This event can be used by applications to inform the end user that their changes have been saved (CommitOutcome.FullyApplied) or rejected (CommitOutcome.FullyDropped and CommitOutcome.NewContentOnly). It can also be used to queue up a new attempt at making the rejected changes. Note however that new edits must be made outside of the event callback.
Example:
// Use `asAlpha` API to access the settled event API
const view = asAlpha(tree.viewWith(config));
// Function to clear all contents of the tree, with a precondition that no changes have occurred.
const clearAllContents = () => {
view.runTransaction(
() => {
// Remove all contents at the root
view.root.removeRange();
},
{ preconditions: [{ type: "noChange" }] },
);
};
// Register the logic for notifying the user of the outcome and allow them to retry
view.events.on("changed", (metadata) => {
if (metadata.isLocal) {
metadata.events.on("settled", (outcome) => {
if (outcome === CommitOutcome.FullyApplied) {
alert("Clear operation succeeded.");
} else {
const shouldTryAgain = confirm(
"The contents have changed. Do you still want to clear everything?",
);
if (shouldTryAgain) {
// It is invalid to make edits during the event callback, so we schedule the retry to occur asynchronously.
setTimeout(clearAllContents);
} else {
alert("Clear operation aborted.");
}
}
});
}
});
// First attempt to clear all contents.
// This will synchronously trigger the changed "event" and register the listener for the settled event.
clearAllContents();Change details
Commit: 73360b3
Affected packages:
- fluid-framework
- @fluidframework/tree
⬆️ Table of contents
🌳 SharedTree DDS Changes
Array node deltas now cover the complete array (#27809)
ArrayNodeDeltaOp and ArrayNodeTreeChangedDeltaOp sequences now include a final retain operation for an unchanged trailing portion of the array. Consumers can process the operations as a complete delta without separately retaining an omitted suffix.
Text deltas inherit the same complete-coverage behavior.
This should not break any existing users as this behavior was allowed under the old specification, but may allow some users to simplify their processing of the delta.
Change details
Commit: 6af2aba
Affected packages:
- @fluidframework/tree
- fluid-framework
⬆️ Table of contents
Add getOrInsert and getOrInsertComputed methods to TreeMapNodeAlpha (#27787)
TreeMapNodeAlpha now has getOrInsert and getOrInsertComputed methods, further aligning it with JavaScript's built-in Map API. Both return the value at a key, first inserting a value if the map has no entry for that key: getOrInsert takes the fallback value directly, while getOrInsertComputed takes a callback which is only invoked (with the key) when an insert is needed, which is preferable when producing the fallback value is expensive.
When the fallback value is inserted and is not already a TreeNode, the inserted and returned value is the result of implicitly constructing a node from it.
These methods are available on TreeMapNodeAlpha, which can be obtained from an existing TreeMapNode via asAlpha, or by declaring the schema with SchemaFactoryAlpha's mapAlpha.
const schemaFactory = new SchemaFactoryAlpha("example");
class Inventory extends schemaFactory.mapAlpha(
"Inventory",
schemaFactory.number,
) {}
const inventory = new Inventory(
new Map([
["apples", 5],
["pears", 3],
]),
);
inventory.getOrInsert("apples", 10); // 5 (existing value returned, not overwritten)
inventory.getOrInsert("oranges", 10); // 10 (inserted and returned)
inventory.getOrInsertComputed("pears", () => computeRestockAmount()); // 3 (existing value returned, callback not invoked)
inventory.getOrInsertComputed("plums", () => computeRestockAmount()); // inserts and returns the computed value
inventory.size; // 4Change details
Commit: e84cb5f
Affected packages:
- fluid-framework
- @fluidframework/tree
⬆️ Table of contents
SharedTree now emits telemetry when it heals an unresolvable identifier on decode (#27756)
When SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode is enabled and an unresolvable identifier is healed while loading a summary, SharedTree now records a HealUnresolvableIdentifierOnDecode telemetry event (at LogLevel.essential). This lets applications relying on the healing workaround detect which documents actually required healing.
This only affects applications that have opted into healUnresolvableIdentifiersOnDecode; the telemetry is emitted through the same logger the DDS already uses, and no behavior other than the added telemetry has changed.
Change details
Commit: e5ada10
Affected packages:
- @fluidframework/tree
- fluid-framework
⬆️ Table of contents
Array insertion anchors now track their index from change deltas (#27697)
The @alpha ArrayPlaceAnchor returned by createArrayInsertionAnchor now maintains its index incrementally from the array node's change delta instead of re-deriving it from the child that happened to sit at the anchor point when it was created. Inserts and removes before the anchor shift it, while edits after it leave it in place.
As a result, removing the child originally at the anchor's index no longer sends the anchor to the end of the array: it now stays in the gap between the surviving neighbors, which is the behavior an insertion point (such as a text cursor) needs.
Because the anchor now holds a subscription to the array node to receive those deltas, ArrayPlaceAnchor gained a dispose() method. Call it when the anchor is no longer needed to release the subscription. Interacting with an anchor after it has been disposed is invalid and will throw.
const anchor = createArrayInsertionAnchor(array, 1);
// ... use anchor.index as content is inserted and removed around it ...
anchor.dispose(); // release the subscription when doneChange details
Commit: 99d71d7
Affected packages:
- @fluidframework/tree
- fluid-framework
⬆️ Table of contents
Rename the TextAsTree domain to PlainText (#27853)
The experimental (@alpha) text domain namespace exported from @fluidframework/tree has been renamed: TextAsTree is now PlainText. This is a breaking rename.
Consumers should update their imports and usages accordingly. For example:
// Before
import { TextAsTree } from "@fluidframework/tree/alpha";
const node = TextAsTree.Tree.fromString("hello");
// After
import { PlainText } from "@fluidframework/tree/alpha";
const node = PlainText.Tree.fromString("hello");The persisted schema identifiers for this domain are unchanged, so existing documents remain compatible.
Change details
Commit: cd26d78
Affected packages:
- @fluidframework/tree
- fluid-framework
⬆️ Table of contents
Promote FormattedText APIs to alpha (#27843)
The FormattedText namespace is now available from the fluid-framework/alpha entrypoint. It provides a generic, collaborative rich-text domain built on SharedTree, parameterized by the formatting you want to associate with each unit of text and by any extra "atom" (embedded object) types you want to allow alongside plain characters.
Use FormattedText.createSchema to generate a text schema for your chosen formatting, then treat the resulting node like a formatted string.
import { SchemaFactory } from "fluid-framework";
import { SchemaFactoryBeta } from "fluid-framework/beta";
import { FormattedText } from "fluid-framework/alpha";
// Note that a beta schema factory is currently required for use with `FormattedText`
const schemaFactory = new SchemaFactoryBeta("com.example.doc");
// Describe the formatting associated with each character.
class CharacterFormat extends schemaFactory.object("CharacterFormat", {
bold: SchemaFactory.boolean,
italic: SchemaFactory.boolean,
}) {}
// Generate the formatted-text schema. The last argument is the format applied
// to text inserted through the non-formatted APIs (for example `fromString`).
class RichText extends FormattedText.createSchema(
schemaFactory,
CharacterFormat,
[], // No extra embedded atom types.
{ bold: false, italic: false },
) {}Once you have a schema, you can construct and edit formatted text:
// Create some text using the default format.
const text = RichText.fromString("hello world");
// Append more text with an explicit format.
text.insertAt(text.characterCount(), "!", { bold: true, italic: false });
// Bold everything from index 0 up to (but not including) index 5.
text.formatRange(0, 5, { bold: true });
// Read back the content with its associated formatting.
for (const atom of text.charactersWithFormatting()) {
console.log(atom.content, atom.format.bold, atom.format.italic);
}FormattedText is currently surfaced as an alpha API and is subject to change.
Change details
Commit: 71895da
Affected packages:
- @fluidframework/tree
- fluid-framework
⬆️ Table of contents
Removes legacy tree-agent APIs (#27868)
The legacy stateful SharedTreeSemanticAgent API and its associated SharedTreeChatModel.query, SharedTreeChatModel.appendContext, SharedTreeChatQuery, and SemanticAgentOptions APIs have been removed. Use createTreeAgent for stateful conversations or executeSemanticEditing for one-shot edits. SharedTreeChatModel.invoke is now required, and the legacy-only disabledError and expiredError variants have been removed from EditResult.
The deprecated createLegacyLangchainChatModel adapter has also been removed. Use createLangchainChatModel with createTreeAgent or executeSemanticEditing instead.
Change details
Commit: d889463
Affected packages:
- @fluidframework/tree-agent
- @fluidframework/tree-agent-langchain
⬆️ Table of contents
🐛 Bug Fixes
Fix extra blank lines in collaborating Quill editors (#27809)
Quill React bindings now distinguish Quill's required terminal newline from user-authored content. Remote editors no longer render an extra blank paragraph after text or line-formatting changes, while intentional trailing line breaks remain synchronized.
Change details
Commit: 6af2aba
Affected packages:
- @fluidframework/quill-react
⬆️ Table of contents
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!