Releases: microsoft/FluidFramework
Fluid Framework v2.63.0 (minor)
Contents
- 🌳 SharedTree DDS Changes
- Add FluidSerializableAsTree domain for representing trees of serializable data (alpha) (#25604)
- Promote Record node types and factories to beta (#25606)
- Add TreeBeta.create (#25623)
- Alpha APIs for annotated allowed types have been refactored (#25595)
- Promote importConcise and exportConcise to beta (#25629)
- Stabilize allowUnknownOptionalFields to beta (#25638)
- Add SchemaFactoryAlpha.typesRecursive and SchemaFactoryAlpha.stagedRecursive (#25199)
- MinimumVersionForCollab is now used in place of tree's alpha FluidClientVersion (#25402)
🌳 SharedTree DDS Changes
Add FluidSerializableAsTree domain for representing trees of serializable data (alpha) (#25604)
Like JsonAsTree, but also supports Fluid Handles.
Change details
Commit: 46e6dce
Affected packages:
- @fluidframework/tree
- fluid-framework
Promote Record node types and factories to beta (#25606)
Record tree node schema may now be declared using SchemaFactoryBeta in addition to SchemaFactoryAlpha.
Change details
Commit: 2e2de30
Affected packages:
- @fluidframework/tree
- fluid-framework
Add TreeBeta.create (#25623)
Adds TreeBeta.create, which is a more stable version of the existing TreeAlpha.create. The only difference is the new TreeBeta.create does not support the @alpha UnsafeUnknownSchema option.
Change details
Commit: 376c2d1
Affected packages:
- fluid-framework
- @fluidframework/tree
Alpha APIs for annotated allowed types have been refactored (#25595)
Staged allowed types must now be run through SchemaFactoryAlpha.types to convert them into an AllowedTypes. This change also means that it is now possible to use the produced AllowedTypesFull in non-alpha APIs since it implements AllowedTypes.
Reading data out of ImplicitAllowedTypes should now be done via normalizeAllowedTypes which now returns a AllowedTypesFull providing access to all the data in a friendly format.
Change details
Commit: c6ba37e
Affected packages:
- fluid-framework
- @fluidframework/tree
Promote importConcise and exportConcise to beta (#25629)
importConcise and exportConcise were previously available via TreeAlpha. They may now also be accessed via TreeBeta.
Note that the beta form of importConcise does not support UnsafeUnknownSchema.
Change details
Commit: b5d4602
Affected packages:
- @fluidframework/tree
- fluid-framework
Stabilize allowUnknownOptionalFields to beta (#25638)
When constructing object node schema with SchemaFactoryBeta.object or SchemaFactoryBeta.objectRecursive you can now provide the allowUnknownOptionalFields option as well as other metadata which were previously only available in SchemaFactoryAlpha.objectAlpha and SchemaFactoryAlpha.objectRecursive.
Additionally the alpha interface SchemaFactoryObjectOptions has been renamed to ObjectSchemaOptionsAlpha to better align with the other related types.
Change details
Commit: b7222d1
Affected packages:
- fluid-framework
- @fluidframework/tree
Add SchemaFactoryAlpha.typesRecursive and SchemaFactoryAlpha.stagedRecursive (#25199)
With these new APIs, it is now possible to stage changes to recursive types.
Change details
Commit: 931e986
Affected packages:
- fluid-framework
- @fluidframework/tree
MinimumVersionForCollab is now used in place of tree's alpha FluidClientVersion (#25402)
FluidClientVersion: No longer used as the type for Fluid Client versions in APIs/codecs (for example, oldestCompatibleClient). Additionally, FluidClientVersion is now a const object with members that declare specific MinimumVersionForCollab versions. These are intended to be used with APIs that require a version (such as TreeAlpha.exportCompressed).
CodecWriteOptions and SharedTreeOptions: oldestCompatibleClient has been replaced by minVersionForCollab. See migration guide below.
TreeAlpha.exportCompressed: The options parameter previously had oldestCompatibleClient and now has minVersionForCollab. Migrating requires a rename. Existing FluidClientVersion.* values are now MinimumClientVersions.
Migrating
If an application is calling loadContainerRuntime directly and previously specified the minimum client version when initializing Shared Tree like:
const factory = configuredSharedTree({ ..., oldestCompatibleClient: FluidClientVersion.v2_52 });Then the new implementation depends on how the application initializes Fluid.
Applications using AzureClient/OdspClient
If an application is using the declarative model (for example, AzureClient/OdspClient), it should continue to call configuredSharedTree but specify minVersionForCollab instead:
const factory = configuredSharedTree({ ..., minVersionForCollab: "2.52.0" });Applications calling loadContainerRuntime
If an application is initializing the ContainerRuntime directly, it should now specify the minVersionForCollab there:
const runtime = await loadContainerRuntime({ ..., minVersionForCollab: "2.52.0" });Change details
Commit: 7f59e31
Affected packages:
- @fluidframework/tree
- fluid-framework
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!
Fluid Framework v2.62.0 (minor)
Contents
🚨 Breaking Changes
The exports of @fluid-experimental/tree-react-api have been moved to the new @fluidframework/react package and placed under its /alpha exports (#25542)
@fluid-experimental/tree-react-api has been adjusted to align with Fluid Framework's API Support Levels. It has been renamed to @fluidframework/react and all existing APIs are now available under @fluidframework/react/alpha.
Since this package was under @fluid-experimental, previously it implicitly made no guarantees. Now all the APIs are @alpha, which also amounts to making no guarantees but makes it possible to promote APIs to @beta in the future to offer some stability.
To accommodate this change, all users of this package will need to adjust:
- Package dependencies from
"@fluid-experimental/tree-react-api"to"@fluidframework/react". - Imports from
"@fluid-experimental/tree-react-api"to"@fluidframework/react/alpha".
Change details
Commit: b388c7b
Affected packages:
- @fluid-experimental/tree-react-api
- @fluidframework/react
🌳 SharedTree DDS Changes
Added APIs for tracking observations of SharedTree content for automatic invalidation (#25459)
TreeAlpha.trackObservations and TreeAlpha.trackObservationsOnce have been added. These provide a way to run some operation which reads content from TreeNodes, then run a call back when anything observed by that operation changes.
This functionality has also been exposed in the form of React hooks and React higher order components via the @fluid-experimental/tree-react-api package. It is now possible to use these utilities to implement React applications which pass TreeNodes in their props and get all necessary invalidation from tree changes handled automatically. The recommended pattern for doing this is to use treeDataObject or TreeViewComponent at the root, then withTreeObservations or withMemoizedTreeObservations for any sub-components which read from TreeNodes. Alternatively more localized changes can be made by using PropNode to type erase TreeNodes passed in props, then use one of the usePropTreeNode or usePropTreeRecord hooks to read from them.
These APIs work with both hydrated and un-hydrated TreeNodes.
React Support
Here is a simple example of a React components which has an invalidation bug due to reading a mutable field from a TreeNode that was provided in a prop:
const builder = new SchemaFactory("example");
class Item extends builder.object("Item", { text: SchemaFactory.string }) {}
const ItemComponentBug = ({ item }: { item: Item }): JSX.Element => (
<span>{item.text}</span> // Reading `text`, a mutable value from a React prop, causes an invalidation bug.
);This bug can now easily be fixed using withTreeObservations or withMemoizedTreeObservations:
const ItemComponent = withTreeObservations(
({ item }: { item: Item }): JSX.Element => <span>{item.text}</span>,
);For components which take in TreeNodes, but merely forward them and do not read their properties, they can use PropTreeNode as shown:
const ItemParentComponent = ({ item }: { item: PropTreeNode<Item> }): JSX.Element => (
<ItemComponent item={item} />
);If such a component reads from the TreeNode, it gets a compile error instead of an invalidation bug. In this case the invalidation bug would be that if item.text is modified, the component would not re-render.
const InvalidItemParentComponent = ({
item,
}: { item: PropTreeNode<Item> }): JSX.Element => (
// @ts-expect-error PropTreeNode turns this invalidation bug into a compile error
<span>{item.text}</span>
);To provide access to TreeNode content in only part of a component the usePropTreeNode or usePropTreeRecord hooks can be used.
TreeAlpha.trackObservationsOnce Examples
Here is a rather minimal example of how TreeAlpha.trackObservationsOnce can be used:
cachedFoo ??= TreeAlpha.trackObservationsOnce(
() => {
cachedFoo = undefined;
},
() => nodeA.someChild.bar + nodeB.someChild.baz,
).result;That is equivalent to doing the following:
if (cachedFoo === undefined) {
cachedFoo = nodeA.someChild.bar + nodeB.someChild.baz;
const invalidate = (): void => {
cachedFoo = undefined;
for (const u of unsubscribe) {
u();
}
};
const unsubscribe: (() => void)[] = [
TreeBeta.on(nodeA, "nodeChanged", (data) => {
if (data.changedProperties.has("someChild")) {
invalidate();
}
}),
TreeBeta.on(nodeB, "nodeChanged", (data) => {
if (data.changedProperties.has("someChild")) {
invalidate();
}
}),
TreeBeta.on(nodeA.someChild, "nodeChanged", (data) => {
if (data.changedProperties.has("bar")) {
invalidate();
}
}),
TreeBeta.on(nodeB.someChild, "nodeChanged", (data) => {
if (data.changedProperties.has("baz")) {
invalidate();
}
}),
];
}Here is more complete example showing how to use TreeAlpha.trackObservationsOnce invalidate a property derived from its tree fields.
const factory = new SchemaFactory("com.example");
class Vector extends factory.object("Vector", {
x: SchemaFactory.number,
y: SchemaFactory.number,
}) {
#length: number | undefined = undefined;
public length(): number {
if (this.#length === undefined) {
const result = TreeAlpha.trackObservationsOnce(
() => {
this.#length = undefined;
},
() => Math.hypot(this.x, this.y),
);
this.#length = result.result;
}
return this.#length;
}
}
const vec = new Vector({ x: 3, y: 4 });
assert.equal(vec.length(), 5);
vec.x = 0;
assert.equal(vec.length(), 4);Change details
Commit: 21d45d5
Affected packages:
- fluid-framework
- @fluidframework/tree
- @fluid-experimental/tree-react-api
- @fluidframework/react
Remove JsonValidator (#25381)
The @alpha API JsonValidator has been removed: its replacement FormatValidator must now be used.
As part of this:
typeboxValidatorhas been replaced withFormatValidatorBasic.noopValidatorhas been replaced withFormatValidatorNoOp.
Change details
Commit: 64a9b88
Affected packages:
- fluid-framework
- @fluidframework/tree
Add configuredSharedTreeBeta (#25531)
A limited subset of the options from the existing @alpha configuredSharedTree API have been stabilized to @beta in the form of configuredSharedTreeBeta.
import {
configuredSharedTreeBeta,
ForestTypeExpensiveDebug,
} from "fluid-framework/beta";
const SharedTree = configuredSharedTreeBeta({
forest: ForestTypeExpensiveDebug,
});Change details
Commit: 1e2d48f
Affected packages:
- fluid-framework
- @fluidframework/tree
⚠️ Deprecations
asTreeViewAlpha has been deprecated in favor of asAlpha. ([#25512](#255...
Fluid Framework v2.61.0 (minor)
Contents
✨ New Features
minVersionForCollab is now available on IFluidDataStoreContext (#25130)
minVersionForCollab is now passed down from the ContainerRuntime to the Datastore layer where it is made available for SharedObject construction. DDSes may optionally consume this value and use it to determine which sets of feature flags should be enabled.
Public type changes
- @fluidframework/datastore:
FluidDataStoreRuntime- ExposesminVersionForCollab. - @fluidframework/runtime-definitions:
IFluidDataStoreContext- Exposes optional memberminVersionForCollab. SeeFluidDataStoreContextfor an example implementation. - @fluidframework/test-runtime-utils:
MockFluidDataStoreContext,MockFluidDataStoreRuntime- ExposesminVersionForCollabeither via a getter or as a readonly field.
Note that the new implementations are optional fields and in some cases accept undefined. This is needed for layer compatibility, and in a future release these members will no longer be optional.
Change details
Commit: 2566772
Affected packages:
- @fluidframework/datastore
- @fluidframework/runtime-definitions
- @fluidframework/test-runtime-utils
⚠️ Deprecations
Deprecate submitMessage on FluidDataStoreRuntime and MockFluidDataStoreRuntime (#25332)
As needed, access submitMessage via IFluidDataStoreContext/IFluidParentContext. See #24406 for details.
Change details
Commit: 687378a
Affected packages:
- @fluidframework/datastore
- @fluidframework/test-runtime-utils
Legacy API Changes
Wrapped errors preserved as cause property (#25485)
IFluidErrorBase (internal basis for FluidFramework client errors) declares cause property matching ES2022 lib (whether targeted or not). When an error is wrapped, cause will be set to the originating error (which may or may not itself be an Error).
Change details
Commit: c69d56d
Affected packages:
- @fluidframework/telemetry-utils
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!
Fluid Framework v2.60.0 (minor)
Contents
- 🚨 Breaking Changes
- 🌳 SharedTree DDS Changes
- Single-node insertion/removal APIs have been removed from TableSchema (alpha) (#25233)
- Range-based row/column removal methods have been added to TableSchema APIs (alpha) (#25235)
- Replace "TreeEncodingOptions.useStoredKeys" with "keys" and "KeyEncodingOptions" (#25263)
- Hoist runTransaction method from TreeViewAlpha to TreeBranch (#25280)
- SchemaFactoryAlpha.recordRecursive now supports metadata (#25289)
- FormatValidator added to replace JsonValidator (#25311)
- Add SchemaFactoryBeta (#25313)
🚨 Breaking Changes
Remove unnecessary and internal APIs in ISequenceIntervalCollection and related interval types (#25244)
The following APIs are now removed:
IInterval.cloneIInterval.modifyIInterval.unionISerializableIntervalSequenceInterval.cloneSequenceInterval.modifySequenceInterval.unionSequenceInterval.serializeSequenceInterval.addPositionChangeListenersSequenceInterval.removePositionChangeListeners
These APIs were never intended for public use. There is no migration path, and any usage is strongly discouraged, as it may result in severe errors or data corruption. Please remove any dependencies on these APIs as soon as possible.
Change details
Commit: 15d476e
Affected packages:
- fluid-framework
- @fluidframework/sequence
🌳 SharedTree DDS Changes
Single-node insertion/removal APIs have been removed from TableSchema (alpha) (#25233)
There is a significant performance benefit to inserting / removing rows / columns in batches. To help encourage more performant usage patterns, single-node insertion and removal APIs have been removed. The APIs that operate on batches should be used instead.
Specifically:
insertColumn- Use
insertColumnsinstead
- Use
insertRow- Use
insertRowsinstead
- Use
removeColumn- Use
removeColumnsinstead
- Use
removeRow- Use
removeRowsinstead
- Use
Change details
Commit: 99281d2
Affected packages:
- @fluidframework/tree
- fluid-framework
Range-based row/column removal methods have been added to TableSchema APIs (alpha) (#25235)
Adds range-based overloads to removeColumns and removeRows for removing contiguous ranges of rows and columns.
The removeAllColumns and removeAllRows methods have been removed, as they can be trivially implemented in terms of the new methods.
Change details
Commit: c803393
Affected packages:
- @fluidframework/tree
- fluid-framework
Replace "TreeEncodingOptions.useStoredKeys" with "keys" and "KeyEncodingOptions" (#25263)
The alpha API TreeEncodingOptions has had its useStoredKeys boolean replaced with keys that takes a KeyEncodingOptions allowing for three options instead of the previous two. With the new API, it is now possible to control, for APIs which support it (like TreeAlpha.exportVerbose), if unknown optional fields will be included when exporting data using stored keys.
Additionally, the relevant options interfaces have been marked as @input, indicating that more options may be added as optional parameters in the future, and that should be considered non-breaking.
Change details
Commit: b65f2a8
Affected packages:
- fluid-framework
- @fluidframework/tree
Hoist runTransaction method from TreeViewAlpha to TreeBranch (#25280)
Transactions are not view-schema-dependent, so it isn't necessary for them to be exclusive to the view type. runTransaction is now available on TreeBranch (alpha). TreeViewAlpha extends TreeBranch, so this change strictly makes the API more accessible.
Change details
Commit: a66b3b7
Affected packages:
- fluid-framework
- @fluidframework/tree
SchemaFactoryAlpha.recordRecursive now supports metadata (#25289)
SchemaFactoryAlpha.recordRecursive now support metadata like SchemaFactoryAlpha.recordAlpha and the other TreeNodeSchema creation methods on SchemaFactoryAlpha (except for SchemaFactoryAlpha.record which does not support metadata).
Change details
Commit: 8324170
Affected packages:
- fluid-framework
- @fluidframework/tree
FormatValidator added to replace JsonValidator (#25311)
The existing @alpha type JsonValidator has a new type-erased alternative, FormatValidator, which is planned to be stabilized to @beta in the future. It replaces JsonValidator in ICodecOptions. Existing code using ICodecOptions should migrate to use FormatValidator, but this is not required for adopting this release as JsonValidator is still supported.
Change details
Commit: 9db6e08
Affected packages:
- fluid-framework
- @fluidframework/tree
Add SchemaFactoryBeta (#25313)
SchemaFactoryBeta is added to provide a place to partially stabilize APIs from SchemaFactoryAlpha. Initially just one APIs is added as @beta: scopedFactory. Users of the existing @alpha scopedFactory API on SchemaFactoryAlpha will need to update to use scopedFactoryAlpha if they require the returned factory to be a SchemaFactoryAlpha instance.
Change details
Commit: dca2361
Affected packages:
- fluid-framework
- @fluidframework/tree
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!
Fluid Framework v2.53.1 (patch)
What's Changed
- fix(directory): Fix
SharedDirectory.forEach()(#25299)#25301 - Bump client patch version to 2.53.1 and update type tests
#25254
Full Changelog: client_v2.53.0...client_v2.53.1
Fluid Framework v2.53.0 (minor)
Contents
✨ New Features
Adds staged allowed types to SchemaFactoryAlpha (#25116)
This adds the staged API to SchemaFactoryAlpha. Staged allowed types can be used for schema evolution to add members to an AllowedTypes while supporting cross version collaboration.
Staged allowed types are allowed types that can be upgraded by schema upgrades. Before being upgraded, any attempt to insert or move a node to a location which requires its type to be upgraded to be valid will throw an error.
To add a new member to an AllowedTypes, add the type wrapped by staged. For example, migrating an array which previously supported only numbers to support both numbers and strings would start by deploying a version of the app using staged:
class TestArray extends schemaFactoryAlpha.arrayAlpha("TestArray", [
SchemaFactoryAlpha.number,
SchemaFactoryAlpha.staged(SchemaFactoryAlpha.string),
]) {}Once enough clients have this code update, it is safe to allow writing strings to the array. To allow writing strings to the array, a code change must be made to remove the staged annotation:
class TestArray extends schemaFactoryAlpha.arrayAlpha("TestArray", [
schemaFactoryAlpha.number,
schemaFactoryAlpha.string,
]) {}Then when opening old documents upgradeSchema is used to upgrade the stored schema:
view.upgradeSchema();The @alpha API extractPersistedSchema now takes the schema as an ImplicitAnnotatedFieldSchema and an additional parameter to filter which staged upgrades it includes.
Below is a full example of how the schema migration process works. This can also be found in the tests.
// Schema A: only number allowed
const schemaA = SchemaFactoryAlpha.optional([SchemaFactoryAlpha.number]);
// Schema B: number or string (string is staged)
const schemaB = SchemaFactoryAlpha.optional([
SchemaFactoryAlpha.number,
SchemaFactoryAlpha.staged(SchemaFactoryAlpha.string),
]);
// Schema C: number or string, both fully allowed
const schemaC = SchemaFactoryAlpha.optional([
SchemaFactoryAlpha.number,
SchemaFactoryAlpha.string,
]);
// Initialize with schema A.
const configA = new TreeViewConfiguration({
schema: schemaA,
});
const viewA = treeA.viewWith(configA);
viewA.initialize(5);
// Since we are running all the different versions of the app in the same process making changes synchronously,
// an explicit flush is needed to make them available to each other.
synchronizeTrees();
assert.deepEqual(viewA.root, 5);
// View the same document with a second tree using schema B.
const configB = new TreeViewConfiguration({
schema: schemaB,
});
const viewB = treeB.viewWith(configB);
// B cannot write strings to the root.
assert.throws(() => (viewB.root = "test"));
// View the same document with a third tree using schema C.
const configC = new TreeViewConfiguration({
schema: schemaC,
});
const viewC = treeC.viewWith(configC);
// Upgrade to schema C
viewC.upgradeSchema();
// Use the newly enabled schema.
viewC.root = "test";
synchronizeTrees();
// View A is now incompatible with the stored schema:
assert.equal(viewA.compatibility.canView, false);
// View B can still read the document, and now sees the string root which relies on the staged schema.
assert.deepEqual(viewB.root, "test");Change details
Commit: 59baf03
Affected packages:
- fluid-framework
- @fluidframework/tree
StateFactory.latestMap now accepts a validator parameter (#25172)
The StateFactory.latestMap API now accepts a validator argument. The validator is a function that will be called at runtime to verify that the data is valid. This is especially useful when changing the schema of presence data.
See the presence documentation for more details.
Change details
Commit: cac39eb
Affected packages:
- @fluidframework/presence
🌳 SharedTree DDS Changes
TableSchema's "removeColumn" API now removes corresponding cells (alpha) (#25213)
Previously, the removeColumn API on Table nodes derived from TableSchema (alpha) only removed the Column node from the list of columns tracked by the table. To also remove the corresponding cells from the table (which are stored on the Row nodes), the user was required to write a custom transaction that removed the column and cells.
The motivation for this design was due to performance concerns with transactions. Those concerns are still relevant, but the data leak risk of dropping columns without removing corresponding cells seems a greater risk, and we have plans to address the performance issues with transactions.
Change details
Commit: b665ba8
Affected packages:
- fluid-framework
- @fluidframework/tree
Export TreeNode not only as a type (#25226)
TreeNode can now be used as a runtime object. This enables checking if an object is a TreeNode with instanceof. TreeNode has customized instanceof support so it can detect TreeNode instances, even if they hide their prototype like POJO mode nodes do.
Change details
Commit: eefb952
Affected packages:
- @fluidframework/tree
- fluid-framework
🐛 Bug Fixes
Allow edits in arrays to be concurrent to dependent edits of transactions with violated constraints (#25191)
Before this release, making concurrent edits to an array could lead to assertion error 0x8a2 being thrown if the following conditions were met:
- Some edit
e1was a transaction with a constraint that turned out to be violated by edits concurrent to (and sequenced before)e1 - Some edit
e2was dependent one1(from before the violation of its constraint) - Some edit
e3was concurrent to and sequenced after bothe1ande2 e3was either concurrent to or the revert of some other edite0that predatede1,e2, ande3.e0ande2made edits to the same gap (that is, in the same space between nodes) in the sequence/array.
After this release, these scenarios will work as expected (that is, no assertion error thrown).
Change details
Commit: ef64bae
Affected packages:
- @fluidframework/tree
- fluid-framework
[⬆️ Table ...
Fluid Framework v2.52.0 (minor)
Contents
- 🌳 SharedTree DDS Changes
- Change and clarify limitations related to alpha features allowUnknownOptionalFields and importVerbose (#25074)
- Fix independentInitializedView when used with ForestTypeExpensiveDebug (#25083)
- Allow attaching a SharedTree to an already attached container (#25102)
- Support unknown optional fields in TreeBeta.clone, TreeAlpha.exportConcise and TreeAlpha.exportVerbose (#25106)
⚠️ Deprecations- Legacy API Changes
- Introduced new interface "IRuntimeStorageService" to replace "IDocumentStorageService" between Runtime and DataStore layers (#25057)
- Introduced new interface "IContainerStorageService" to replace "IDocumentStorageService" between Loader and Runtime layers (#25057)
- New ILayerCompatDetails property on LocalDocumentServiceFactory and OdspDocumentServiceFactoryCore (#25120)
🌳 SharedTree DDS Changes
Change and clarify limitations related to alpha features allowUnknownOptionalFields and importVerbose (#25074)
allowUnknownOptionalFields currently has some limitations. To mitigate some bugs, importVerbose has dropped support for unknown optional fields. Previously importVerbose would tolerate some unknown optional fields, but could not validate they comply with the document stored schema. This could cause some crashes, and likely document corruption. This support has been removed: now trying to create nodes containing unknown optional fields via importVerbose will throw a UsageError. There is no longer a way to create and insert nodes which contain subtrees for which there is no schema.
Ideally exportVerbose and importVerbose could be used to round trip data while optionally preserving unknown optional fields, but this is currently not working and thus not supported.
If exporting using useStoredKeys, the unknown optional fields will be preserved but may not be able to be imported. If exporting not using useStoredKeys, unknown optional fields will be omitted.
Change details
Commit: 62dc0b0
Affected packages:
- @fluidframework/tree
- fluid-framework
Fix independentInitializedView when used with ForestTypeExpensiveDebug (#25083)
Previously, when using independentInitializedView with ForestTypeExpensiveDebug and the root schema was not an optional field, an error was thrown about the tree being out of schema. This has been fixed.
Change details
Commit: 2570b65
Affected packages:
- @fluidframework/tree
- fluid-framework
Allow attaching a SharedTree to an already attached container (#25102)
Before this release, attaching a SharedTree instance to an already attached container would fail with assert code 0x88f if that instance needed to include data about removed nodes in its attach summary. (This is the case when nodes are removed before attaching and there is a local branch that forks from a commit that made such a removal or from an earlier commit. This is also the case when retaining Revertible objects for those commits).
After this release, the behavior depends on the CodecWriteOptions.oldestCompatibleClient value:
- For values <
FluidClientVersion.v2_52, the behavior is the same. - For values >=
FluidClientVersion.v2_52, the attach will succeed, but use a newer storage format.
Applications should take care to saturate their clients with FF version 2.52 (or greater) before using a CodecWriteOptions.oldestCompatibleClient that is equal to or greater than FluidClientVersion.v2_52. Failure to do so may lead clients with CodecWriteOptions.oldestCompatibleClient equal to or greater than FluidClientVersion.v2_52 to attach SharedTree instances using a storage format that is not supported by FF versions before 2.52. This means that application versions using FF versions before 2.52 will be unable to open documents where such an operation has happened.
Change details
Commit: 3e03f94
Affected packages:
- @fluidframework/tree
- fluid-framework
Support unknown optional fields in TreeBeta.clone, TreeAlpha.exportConcise and TreeAlpha.exportVerbose (#25106)
Trees with unknown optional fields are now supported in TreeBeta.clone, TreeBeta.exportConcise and TreeBeta.exportVerbose.
Previously, attempts to clone or export such nodes could error in some cases, but should now work robustly.
Change details
Commit: e3a126f
Affected packages:
- @fluidframework/tree
- fluid-framework
⚠️ Deprecations
Moved MinimumVersionForCollab to @fluidframework/runtime-definitions (#25059)
MinimumVersionForCollab has been moved from @fluidframework/container-runtime to @fluidframework/runtime-definitions. The export in @fluidframework/container-runtime is now deprecated and will be removed in a future version. Consumers should import it from @fluidframework/runtime-definitions going forward.
Change details
Commit: 4a7b370
Affected packages:
- @fluidframework/container-runtime
- @fluidframework/runtime-utils
Legacy API Changes
Introduced new interface "IRuntimeStorageService" to replace "IDocumentStorageService" between Runtime and DataStore layers (#25057)
Added an interface IRuntimeStorageService which will replace IDocumentStorageService in the DataStore layer. This is exposed by the Runtime layer to the DataStore layer. This new interface will only contain properties that are needed and used by the DataStore layer.
The following properties from IRuntimeStorageService are deprecated as they are not needed by the DataStore layer. These be removed in a future release:
disposeddisposepoliciesgetSnapshotTreegetSnapshotgetVersionscreateBlobuploadSummaryWithContextdownloadSummary
Change details
Commit: 92012de
Affected packages:
- @fluidframework/runtime-...
Fluid Framework v2.51.0 (minor)
Contents
🚨 Breaking Changes
@fluid-experimental/attributable-map package removed (#25019)
The @fluid-experimental/attributable-map package has been removed without replacement.
Change details
Commit: c8bbd6d
Affected packages:
- @fluid-experimental/attributable-map
🌳 SharedTree DDS Changes
Fix adaptEnum's handling of numeric enums (#24957)
Enum entries whose values are numeric get additional properties on TypeScript's generated Enum object. These values were getting treated like enum entries at runtime by adaptEnum (@beta). This has been fixed and the runtime behavior now matches the types in this case.
If any documents were created with this API which were impacted by this bug and keeping them openable is required, they will need a workaround. Impacted schema using the union from adaptEnum can need to be updated to explicitly include the previously erroneously generated schema.
Before:
enum Mode {
a = 1,
}
const ModeNodes = adaptEnum(schemaFactory, Mode);
const union = ModeNodes.schema;After:
enum Mode {
a = 1,
}
const ModeNodes = adaptEnum(schemaFactory, Mode);
// Bugged version of adaptEnum used to include this: it should not be used but must be included in the schema for legacy document compatibility.
class Workaround extends schemaFactory.object("a", {}) {}
const union = [...ModeNodes.schema, Workaround] as const;To help detect when schema contain unexpected content, and to ensure workarounds like this are implemented properly, applications should include tests which check the schema for compatibility. See tree-cli-app's schema tests for an example of how to do this.
The schema returned by adaptEnum have also been updated to toString to include the value of the particular enum entry: this has no effect on the nodes, just the schema.
Change details
Commit: 7535d31
Affected packages:
- @fluidframework/tree
- fluid-framework
"rootChanged" event is no longer skipped if first change is setting the root to undefined (#24994)
A bug has been fixed where rootChanged would not be fired if the change is the first change since the TreeView became in-schema, and the change was setting the document root to undefined.
Change details
Commit: e6f2587
Affected packages:
- @fluidframework/tree
- fluid-framework
Make POJO mode TreeArrayNodes report the array constructor as their constructor (#24988)
Make POJO mode TreeArrayNode's inherited constructor property report Array instead of the TreeNodeSchema class. This is necessary to make TreeArrayNodes appear equal to arrays according to NodeJS's assert.strict.deepEqual in NodeJS 22.
Change details
Commit: 7b4d0ab
Affected packages:
- @fluidframework/tree
- fluid-framework
Legacy API Changes
New "getSnapshotTree" API on "IChannelStorageService" (#24970)
A new API, getSnapshotTree has been added to IChannelStorageService. It should return the snapshot tree for a channel. This will help channels examine their snapshot when it consists of dynamic trees and blobs, i.e., when the number of tree and blobs and / or their keys are not known in advance. This is optional for backwards compatibility, and will become required in the future.
Change details
Commit: d1241a4
Affected packages:
- @fluidframework/datastore-definitions
New "PureDataObject" implementation "TreeDataObject" added (#25025)
A new implementation of PureDataObject has been added: TreeDataObject. Where DataObject stores its contents in a SharedDirectory, TreeDataObject stores its contents in a SharedTree.
Change details
Commit: f4fbd2f
Affected packages:
- @fluidframework/aqueduct
Add API for tree-based root data object (#25030)
Adds createTreeContainerRuntimeFactory which can be used to create runtime factories which construct containers with an entry point containing a single tree-based root data object.
Change details
Commit: 30a0d7e
Affected packages:
- @fluidframework/fluid-static
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!
Fluid Framework v2.50.0 (minor)
Contents
🚨 Breaking Changes
IFluidHandleInternal.bind (deprecated) has been removed (#24974)
IFluidHandleInternal.bind was deprecated in 2.40 and has now been removed. See release notes entry for more details.
Change details
Commit: 07e1837
Affected packages:
- @fluidframework/core-interfaces
- @fluidframework/datastore
- @fluidframework/runtime-utils
- @fluidframework/test-runtime-utils
🌳 SharedTree DDS Changes
Record node kind was added (alpha) (#24908)
Adds a new kind of node to SharedTree that models a TypeScript record. As is the case with map nodes, record nodes only support string keys.
class MyRecord extends schemaFactory.record("my-record", [
schemaFactory.number,
schemaFactory.string,
]) {}
const myRecord = new MyRecord({
foo: 42,
bar: "Hello world!",
});
const foo = myRecord.foo; // 42
delete myRecord.foo;
myRecord.baz = 37;
const keys = Object.keys(myRecord); // ["bar", "baz"]
const values = Object.values(myRecord); // ["Hello world!", 37]
const entries = Object.entries(myRecord); // [["bar", "Hello world!"], ["baz", 37]]NodeKind enum update
This change includes the addition of a new flag to the NodeKind enum. This API notes in its documentation that users should not treat its flags as an exhaustive set.
This change may break code that treats it that way. We recommend updating your code to be more tolerant of unknown node kinds going forward.
Also see alternative options for schema-agnostic tree traversal if needed:
Additional features
In addition to the operations afforded by TypeScript records, SharedTree record nodes can be iterated (equivalent to Object.entries).
class MyRecord extends schemaFactory.record("my-record", [schemaFactory.number, schemaFactory.string]) {}
const myRecord = new MyRecord({
foo: 42,
bar: "Hello world!"
});
for (const [key, value] of myRecord) {
...
}
const a = { ...myRecord }; // { foo: 42, bar: "Hello world!" }
const b = [...myRecord]; // [["foo", 42], ["bar, "Hello world!"]]Recursive records
Recursive record schema can be defined using recordRecursive on SchemaFactoryAlpha.
class MyRecord extends schemaFactory.recordRecursive("my-record", [
schemaFactory.string,
() => MyRecord,
]) {}
const myRecord = new MyRecord({
foo: "Hello world!",
bar: new MyRecord({
x: "foo",
y: new MyRecord({}),
}),
});TableSchema update (alpha)
The TableSchema APIs have been updated to use record nodes in the schema they generate. Specifically, the Row representation now uses a record to store its column-cell pairs, rather than a map.
The node types derived from these APIs model their data in a row-major format. That is, each row in the table contains the set of cells that belong to that row, where each cell is indexed by its corresponding column.
Previously, this was modeled using a MapNode. This format proved cumbersome to interop with popular table rendering libraries like tanstack, which expect a record-like format.
The persisted format of documents containing trees derived from these APIs is the same, so this change is forward and backward compatible.
JsonDomainSchema update (alpha)
JsonObject has been updated to a record rather than a map.
The persisted format of documents containing trees derived from these APIs is the same, so this change is forward and backward compatible.
Change details
Commit: b25667b
Affected packages:
- @fluidframework/tree
- fluid-framework
Other Changes
StateFactory.latest now accepts a validator parameter (#24432)
The StateFactory.latest API now accepts a validator argument. The validator is a function that will be called at runtime to verify that the data is valid. This is especially useful when changing the schema of presence data.
See the presence documentation for more details.
Change details
Commit: ffe5d0b
Affected packages:
- @fluidframework/presence
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!
Fluid Framework v2.43.0 (minor)
Contents
🚨 Breaking Changes
The reason parameter on the disconnect event now accepts undefined to allow for clean, non-error disconnections (#24840)
To enable better handling of intentional disconnects (for example Container.dispose()), the reason parameter of the disconnect event on IDocumentDeltaConnectionEvents now accepts undefined as a valid value.
Old signature:
listener: (reason: IAnyDriverError) => voidNew signature:
listener: (reason: IAnyDriverError | undefined) => voidDevelopers with listeners for the disconnect event should update their implementations to handle cases where the reason parameter is undefined.
This indicates a clean disconnect, which should not be treated as an error.
The breaking change is scheduled to be released in version 2.60.
Change details
Commit: 82a1c5a
Affected packages:
- @fluidframework/container-loader
- @fluidframework/driver-base
- @fluidframework/driver-definitions
✨ New Features
Persisted metadata for Shared Tree schemas (Alpha) (#24812)
The persisted metadata feature for Shared Tree allows an application author to write document-persisted metadata along with the schema. This feature is supported for both node and field schemas.
Using the persisted metadata feature
As of now, persisted metadata support is available via the SchemaFactoryAlpha API:
// Construct a schema factory with alpha APIs
const schemaFactory = new SchemaFactoryAlpha("com.example");Persisted metadata can take the shape of any JSON-serializable object, e.g.:
const persistedMetadata = { a: 2 };Feature flag
To enable persisted metadata, use configuredSharedTree to specify the format version. The tree that is returned can be substituted in place of the default SharedTree object exported by the Fluid Framework. For example:
const tree = configuredSharedTree({
formatVersion: SharedTreeFormatVersion.v5,
}).create(runtime);
export const MyContainerSchema = {
initialObjects: {
appData: tree,
},
} satisfies ContainerSchema;Examples
Field schemas with persisted metadata
// Construct a schema factory with alpha APIs
const schemaFactory = new SchemaFactoryAlpha("com.example");
// Define metadata. This can take the shape of any JSON-serializable object.
const persistedMetadata = { a: 2 };
// Foo is an object type with metadata
class Foo extends schemaFactory.objectAlpha(
"Foo",
{
// Metadata for a required number field
bar: schemaFactory.required(schemaFactory.number, { persistedMetadata }),
// Metadata for an optional string field
baz: schemaFactory.optional(schemaFactory.string, { persistedMetadata }),
// Metadata for the object type Foo
},
{ persistedMetadata },
) {}Recursive field schemas
// Construct a schema factory with alpha APIs
const schemaFactory = new SchemaFactoryAlpha("com.example");
// Define metadata. This can take the shape of any JSON-serializable object.
const persistedMetadata = { a: 2 };
// Recursive object schema with persisted metadata
class RecursiveObject extends schemaFactory.objectRecursive(
"RecursiveObject",
{
x: [() => RecursiveObject, schemaFactory.number],
},
{ persistedMetadata },
) {}
// Recursive field schema with metadata
const recursiveField = schemaFactory.optionalRecursive(
[() => RecursiveObject, schemaFactory.number],
{ persistedMetadata },
);Recursive object schemas
// Construct a schema factory with alpha APIs
const schemaFactory = new SchemaFactoryAlpha("com.example");
// Define metadata. This can take the shape of any JSON-serializable object.
const persistedMetadata = { a: 2 };
// Recursive array schema
class Foos extends schemaFactory.arrayRecursive("FooList", [() => Foo], {
persistedMetadata,
}) {}
// Recursive object schema
class Foo extends schemaFactory.objectRecursive(
"Foo",
{ fooList: Foos },
{ persistedMetadata },
) {}
// Recursive map schema
class FooMap extends schemaFactory.mapRecursive("FooMap", [() => Foo], {
persistedMetadata,
}) {}Change details
Commit: 3f81ab5
Affected packages:
- fluid-framework
- @fluidframework/tree
🌳 SharedTree DDS Changes
Tree's enum schema utility are now beta (#24749)
The functions singletonSchema, adaptEnum and enumFromStrings are now @beta instead of @alpha.
Change details
Commit: a23bc9e
Affected packages:
- fluid-framework
- @fluidframework/tree
Add TreeAlpha.child and TreeAlpha.children APIs for generic tree traversal (#24723)
TreeAlpha.child
Access a child node or value of a TreeNode by its property key.
class MyObject extends schemaFactory.object("MyObject", {
foo: schemaFactory.string;
bar: schemaFactory.optional(schemaFactory.string);
}) {}
const myObject = new MyObject({
foo: "Hello world!"
});
const foo = TreeAlpha.child(myObject, "foo"); // "Hello world!"
const bar = TreeAlpha.child(myObject, "bar"); // undefined
const baz = TreeAlpha.child(myObject, "baz"); // undefinedclass MyArray extends schemaFactory.array("MyArray", schemaFactory.string) {}
const myArray = new MyArray("Hello", "World");
const child0 = TreeAlpha.child(myArray, 0); // "Hello"
const child1 = TreeAlpha.child(myArray, 1); // "World
const child2 = TreeAlpha.child(myArray, 2); // undefinedTreeAlpha.children
Get all child nodes / values of a TreeNode, keyed by their property keys.
class MyObject extends schemaFactory.object("MyObject", {
foo: schemaFactory.string;
bar: schemaFactory.optional(schemaFactory.string);
baz: schemaFactory.optional(schemaFactory.number);
}) {}
const myObject = new MyObject({
foo: "Hello world!",
baz: 42,
});
const children = TreeAlpha.children(myObject); // [["foo", "Hello world!"], ["baz", 42]]class MyArray extends schemaFactory.array("MyArray", schemaFactory.string) {}
const myArray = new MyArray("Hello", "World");
const children ...