Contents
- ✨ New Features
- Synchronous Child Datastore Creation (#23143)
- Presence-related events now support the
off
event deregistration pattern (#23196) - Presence updates are now grouped and throttled (#23075)
- 🌳 SharedTree DDS Changes
- ✨ New! Alpha APIs for indexing (#22491)
- Providing unused properties in object literals for building empty ObjectNodes no longer compiles (#23162)
- Revertible objects can now be cloned using
RevertibleAlpha.clone()
(#23044)
- Other Changes
- API clarifications for devtools packages (#23165)
✨ New Features
Synchronous Child Datastore Creation (#23143)
Overview
This feature introduces a new pattern for creating datastores synchronously within the Fluid Framework. It allows for the synchronous creation of a child datastore from an existing datastore, provided that the child datastore is available synchronously via the existing datastore's registry and that the child's factory supports synchronous creation. This method also ensures strong typing for the consumer.
In this context, "child" refers specifically to the organization of factories and registries, not to any hierarchical or hosting relationship between datastores. The parent datastore does not control the runtime behaviors of the child datastore beyond its creation.
The synchronous creation of child datastores enhances the flexibility of datastore management within the Fluid Framework. It ensures type safety and provides a different way to manage datastores within a container. However, it is important to consider the overhead associated with datastores, as they are stored, summarized, garbage collected, loaded, and referenced independently. This overhead should be justified by the scenario's requirements.
Datastores offer increased capabilities, such as the ability to reference them via handles, allowing multiple references to exist and enabling those references to be moved, swapped, or changed. Additionally, datastores are garbage collected after becoming unreferenced, which can simplify final cleanup across clients. This is in contrast to subdirectories in a shared directory, which do not have native capabilities for referencing or garbage collection but are very low overhead to create.
Synchronous creation relies on both the factory and the datastore to support it. This means that asynchronous operations, such as resolving handles, some browser API calls, consensus-based operations, or other asynchronous tasks, cannot be performed during the creation flow. Therefore, synchronous child datastore creation is best limited to scenarios where the existing asynchronous process cannot be used, such as when a new datastore must be created in direct response to synchronous user input.
Key Benefits
- Synchronous Creation: Allows for the immediate creation of child datastores without waiting for asynchronous operations.
- Strong Typing: Ensures type safety and better developer experience by leveraging TypeScript's type system.
Use Cases
Example 1: Creating a Child Datastore
In this example, we demonstrate how to support creating a child datastore synchronously from a parent datastore.
/**
* This is the parent DataObject, which is also a datastore. It has a
* synchronous method to create child datastores, which could be called
* in response to synchronous user input, like a key press.
*/
class ParentDataObject extends DataObject {
createChild(name: string): ChildDataStore {
assert(
this.context.createChildDataStore !== undefined,
"this.context.createChildDataStore",
);
const { entrypoint } = this.context.createChildDataStore(
ChildDataStoreFactory.instance,
);
const dir = this.root.createSubDirectory("children");
dir.set(name, entrypoint.handle);
entrypoint.setProperty("childValue", name);
return entrypoint;
}
getChild(name: string): IFluidHandle<ChildDataStore> | undefined {
const dir = this.root.getSubDirectory("children");
return dir?.get<IFluidHandle<ChildDataStore>>(name);
}
}
For a complete example see the following test: https://github.com/microsoft/FluidFramework/blob/main/packages/test/local-server-tests/src/test/synchronousDataStoreCreation.spec.ts
Change details
Commit: 3426b43
Affected packages:
- @fluidframework/container-runtime
- @fluidframework/runtime-definitions
⬆️ Table of contents
Presence-related events now support the off
event deregistration pattern (#23196)
Event subscriptions within @fluidframework/presence
may now use off
to deregister event listeners, including initial listeners provided to Notifications
.
Some type names have shifted within the API though no consumers are expected to be using those types directly. The most visible rename is NotificationSubscribable
to NotificationListenable
. Other shifts are to use types now exported through @fluidframework/core-interfaces
where the most notable is ISubscribable
that is now Listenable
.
Change details
Commit: f7be965
Affected packages:
- @fluidframework/presence
⬆️ Table of contents
Presence updates are now grouped and throttled (#23075)
Presence updates are grouped together and throttled to prevent flooding the network with messages when presence values are rapidly updated. This means the presence infrastructure will not immediately broadcast updates but will broadcast them after a configurable delay.
The allowableUpdateLatencyMs
property configures how long a local update may be delayed under normal circumstances, enabling grouping with other updates. The default allowableUpdateLatencyMs
is 60 milliseconds but may be (1) specified during configuration of a States Workspace or Value Manager and/or (2) updated later using the controls
member of a Workspace or Value Manager. The States Workspace configuration applies when a Value Manager does not have its own setting.
Notifications are never queued; they effectively always have an allowableUpdateLatencyMs
of 0. However, they may be grouped with other updates that were already queued.
Note that due to throttling, clients receiving updates may not see updates for all values set by another. For example, with Latest*ValueManagers
, the only value sent is the value at the time the outgoing grouped message is sent. Previous values set by the client will not be broadcast or seen by other clients.
Example
You can configure the grouping and throttling behavior using the allowableUpdateLatencyMs
property as in the following example:
// Create and configure a states workspace
const stateWorkspace = presence.getStates(
"app:v1states",
{
// This value manager has an allowable latency of 100ms.
position: Latest({ x: 0, y: 0 }, { allowableUpdateLatencyMs: 100 }),
// This value manager uses the workspace default allowable latency of 60ms.
count: Latest({ num: 0 }),
},
// Set the default allowable latency for all value managers in this workspace to 200ms,
// overriding the default value of 60ms.
{ allowableUpdateLatencyMs: 200 },
);
// Temporarily set count updates to send as soon as possible.
const countState = stateWorkspace.props.count;
countState.controls.allowableUpdateLatencyMs = 0;
countState.local = { num: 5000 };
// Reset the update latency to the workspace default of 60ms.
countState.controls.allowableUpdateLatencyMs = undefined;
Change details
Commit: abde76d
Affected packages:
- @fluidframework/presence
⬆️ Table of contents
🌳 SharedTree DDS Changes
✨ New! Alpha APIs for indexing (#22491)
SharedTree now supports indexing via two new APIs, createSimpleTreeIndex
and createIdentifierIndex
.
createSimpleTreeIndex
is used to create a SimpleTreeIndex
which indexes nodes based on their schema. Depending on the schema, the user specifies which field to key the node on.
The following example indexes IndexableParent
s and IndexableChild
s and returns the first node of a particular key:
function isStringKey(key: TreeIndexKey): key is string {
return typeof key === "string";
}
const index = createSimpleTreeIndex(
view,
new Map([
[IndexableParent, parentKey],
[IndexableChild, childKey],
]),
(nodes) => nodes[0],
isStringKey,
[IndexableParent, IndexableChild],
);
createIdentifierIndex
is used to create an IdentifierIndex
which provides an efficient way to retrieve nodes using the node identifier.
Example:
const identifierIndex = createIdentifierIndex(view);
const node = identifierIndex.get("node12345");
Change details
Commit: cd95357
Affected packages:
- fluid-framework
⬆️ Table of contents
Providing unused properties in object literals for building empty ObjectNodes no longer compiles (#23162)
ObjectNodes with no fields will now emit a compiler error if constructed from an object literal with fields. This matches the behavior of non-empty ObjectNodes which already gave errors when unexpected properties were provided.
class A extends schemaFactory.object("A", {}) {}
const a = new A({ thisDoesNotExist: 5 }); // This now errors.
Change details
Commit: dc3c300
Affected packages:
- fluid-framework
- @fluidframework/tree
⬆️ Table of contents
Revertible objects can now be cloned using RevertibleAlpha.clone()
(#23044)
The DisposableRevertible
interface has been replaced with RevertibleAlpha
. The new RevertibleAlpha
interface extends Revertible
and includes a clone(branch: TreeBranch)
method to facilitate cloning a Revertible to a specified target branch. The source branch where the RevertibleAlpha
was created must share revision logs with the target branch where the RevertibleAlpha
is being cloned. If this condition is not met, the operation will throw an error.
Change details
Commit: 5abfa01
Affected packages:
- fluid-framework
- @fluidframework/tree
⬆️ Table of contents
Other Changes
API clarifications for devtools packages (#23165)
APIs that were never intended for direct consumer use have been marked as @system
. These are:
- HasContainerKey
APIs that were not intended to be extended by consumers have been marked as @sealed
. These are:
- ContainerDevtoolsProps
- DevtoolsProps
- HasContainerKey
- IDevtools
Additionally, interface properties have been marked as readonly
.
Change details
Commit: cea34d1
Affected packages:
- @fluidframework/devtools
- @fluidframework/devtools-core
⬆️ Table of contents
🛠️ Start Building Today!
Please continue to engage with us on GitHub Discussion and Issue pages as you adopt Fluid Framework!