fluid-framework 2.113.1 → 2.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,315 @@
1
1
  # fluid-framework
2
2
 
3
+ ## 2.115.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Promote FormattedText APIs to alpha ([#27843](https://github.com/microsoft/FluidFramework/pull/27843)) [71895da92a](https://github.com/microsoft/FluidFramework/commit/71895da92ad598a366694ae6cd2cd8c296533804)
8
+
9
+ The [`FormattedText`](https://fluidframework.com/docs/api/fluid-framework/formattedtext-namespace) namespace is now available from the `fluid-framework/alpha` entrypoint.
10
+ 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.
11
+
12
+ Use `FormattedText.createSchema` to generate a text schema for your chosen formatting, then treat the resulting node like a formatted string.
13
+
14
+ ```typescript
15
+ import { SchemaFactory } from "fluid-framework";
16
+ import { SchemaFactoryBeta } from "fluid-framework/beta";
17
+ import { FormattedText } from "fluid-framework/alpha";
18
+
19
+ // Note that a beta schema factory is currently required for use with `FormattedText`
20
+ const schemaFactory = new SchemaFactoryBeta("com.example.doc");
21
+
22
+ // Describe the formatting associated with each character.
23
+ class CharacterFormat extends schemaFactory.object("CharacterFormat", {
24
+ bold: SchemaFactory.boolean,
25
+ italic: SchemaFactory.boolean,
26
+ }) {}
27
+
28
+ // Generate the formatted-text schema. The last argument is the format applied
29
+ // to text inserted through the non-formatted APIs (for example `fromString`).
30
+ class RichText extends FormattedText.createSchema(
31
+ schemaFactory,
32
+ CharacterFormat,
33
+ [], // No extra embedded atom types.
34
+ { bold: false, italic: false },
35
+ ) {}
36
+ ```
37
+
38
+ Once you have a schema, you can construct and edit formatted text:
39
+
40
+ ```typescript
41
+ // Create some text using the default format.
42
+ const text = RichText.fromString("hello world");
43
+
44
+ // Append more text with an explicit format.
45
+ text.insertAt(text.characterCount(), "!", { bold: true, italic: false });
46
+
47
+ // Bold everything from index 0 up to (but not including) index 5.
48
+ text.formatRange(0, 5, { bold: true });
49
+
50
+ // Read back the content with its associated formatting.
51
+ for (const atom of text.charactersWithFormatting()) {
52
+ console.log(atom.content, atom.format.bold, atom.format.italic);
53
+ }
54
+ ```
55
+
56
+ `FormattedText` is currently surfaced as an alpha API and is subject to change.
57
+
58
+ - Array node deltas now cover the complete array ([#27809](https://github.com/microsoft/FluidFramework/pull/27809)) [6af2aba044](https://github.com/microsoft/FluidFramework/commit/6af2aba044e050b20386ae9e67111693117da5bd)
59
+
60
+ `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.
61
+
62
+ Text deltas inherit the same complete-coverage behavior.
63
+
64
+ 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.
65
+
66
+ - Settled change notification ([#27814](https://github.com/microsoft/FluidFramework/pull/27814)) [73360b3d70](https://github.com/microsoft/FluidFramework/commit/73360b3d7036a5ce8be1f88a1ada3cfbe680541b)
67
+
68
+ `LocalChangeMetadata` now exposes an `events: Listenable<LocalCommitEvents>` property that fires a `"settled"` event once a commit has been ordered by the sequencing service.
69
+
70
+ Once a commit is sequenced, the following guarantees hold:
71
+ 1. The changes carried by the commit have been persisted and other peers are able to see them.
72
+ 2. There can be no more concurrent changes sequenced before this commit, which means this commit has reached its settled form.
73
+
74
+ The `"settled"` event provides details about the outcome of applying this settled form.
75
+ This can be used by an application to determine whether any constraints associated with the commits were violated.
76
+
77
+ 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`).
78
+ 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.
79
+
80
+ Example:
81
+
82
+ ```typescript
83
+ // Use `asAlpha` API to access the settled event API
84
+ const view = asAlpha(tree.viewWith(config));
85
+
86
+ // Function to clear all contents of the tree, with a precondition that no changes have occurred.
87
+ const clearAllContents = () => {
88
+ view.runTransaction(
89
+ () => {
90
+ // Remove all contents at the root
91
+ view.root.removeRange();
92
+ },
93
+ { preconditions: [{ type: "noChange" }] },
94
+ );
95
+ };
96
+
97
+ // Register the logic for notifying the user of the outcome and allow them to retry
98
+ view.events.on("changed", (metadata) => {
99
+ if (metadata.isLocal) {
100
+ metadata.events.on("settled", (outcome) => {
101
+ if (outcome === CommitOutcome.FullyApplied) {
102
+ alert("Clear operation succeeded.");
103
+ } else {
104
+ const shouldTryAgain = confirm(
105
+ "The contents have changed. Do you still want to clear everything?",
106
+ );
107
+ if (shouldTryAgain) {
108
+ // It is invalid to make edits during the event callback, so we schedule the retry to occur asynchronously.
109
+ setTimeout(clearAllContents);
110
+ } else {
111
+ alert("Clear operation aborted.");
112
+ }
113
+ }
114
+ });
115
+ }
116
+ });
117
+
118
+ // First attempt to clear all contents.
119
+ // This will synchronously trigger the changed "event" and register the listener for the settled event.
120
+ clearAllContents();
121
+ ```
122
+
123
+ - SharedTree now emits telemetry when it heals an unresolvable identifier on decode ([#27756](https://github.com/microsoft/FluidFramework/pull/27756)) [e5ada10ff4](https://github.com/microsoft/FluidFramework/commit/e5ada10ff41a959abe599bb407300e42e1008be3)
124
+
125
+ When [`SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode`](https://fluidframework.com/docs/api/tree/sharedtreeoptionsbeta-interface#healunresolvableidentifiersondecode-propertysignature) 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.
126
+
127
+ 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.
128
+
129
+ - Simplify creating attached Fluid containers with ServiceClient ([#27789](https://github.com/microsoft/FluidFramework/pull/27789)) [1671447b57](https://github.com/microsoft/FluidFramework/commit/1671447b57852875ec90a9d1c16b3817ce25e004)
130
+
131
+ Added `ServiceClient.createAttachedContainer` which creates and attaches a Fluid container in one operation.
132
+ It is a convenient shorthand for calling `createContainer` followed by `attach` when detached-container access is not needed.
133
+
134
+ ```typescript
135
+ const container = await client.createAttachedContainer(dataStoreKind);
136
+ ```
137
+
138
+ - Array insertion anchors now track their index from change deltas ([#27697](https://github.com/microsoft/FluidFramework/pull/27697)) [99d71d7054](https://github.com/microsoft/FluidFramework/commit/99d71d7054dd3ae5b2451c902bb7f7daf925c9f3)
139
+
140
+ The `@alpha` [`ArrayPlaceAnchor`](https://fluidframework.com/docs/api/tree/arrayplaceanchor-interface) returned by [`createArrayInsertionAnchor`](https://fluidframework.com/docs/api/tree/#createarrayinsertionanchor-function) 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.
141
+
142
+ 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.
143
+
144
+ 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.
145
+
146
+ ```typescript
147
+ const anchor = createArrayInsertionAnchor(array, 1);
148
+ // ... use anchor.index as content is inserted and removed around it ...
149
+ anchor.dispose(); // release the subscription when done
150
+ ```
151
+
152
+ - Rename the TextAsTree domain to PlainText ([#27853](https://github.com/microsoft/FluidFramework/pull/27853)) [cd26d781f4](https://github.com/microsoft/FluidFramework/commit/cd26d781f436da469abb79c9d63e40d3c3b8c5dc)
153
+
154
+ The experimental (`@alpha`) text domain namespace exported from `@fluidframework/tree` has been renamed: `TextAsTree` is now `PlainText`.
155
+ This is a breaking rename.
156
+
157
+ Consumers should update their imports and usages accordingly. For example:
158
+
159
+ ```typescript
160
+ // Before
161
+ import { TextAsTree } from "@fluidframework/tree/alpha";
162
+ const node = TextAsTree.Tree.fromString("hello");
163
+
164
+ // After
165
+ import { PlainText } from "@fluidframework/tree/alpha";
166
+ const node = PlainText.Tree.fromString("hello");
167
+ ```
168
+
169
+ The persisted schema identifiers for this domain are unchanged, so existing documents remain compatible.
170
+
171
+ - Add getOrInsert and getOrInsertComputed methods to TreeMapNodeAlpha ([#27787](https://github.com/microsoft/FluidFramework/pull/27787)) [e84cb5f754](https://github.com/microsoft/FluidFramework/commit/e84cb5f754d4e916b02896c128c11b6526c8cef3)
172
+
173
+ [`TreeMapNodeAlpha`](https://fluidframework.com/docs/api/fluid-framework/treemapnodealpha-interface) now has `getOrInsert` and `getOrInsertComputed` methods, further aligning it with JavaScript's built-in Map API.
174
+ 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.
175
+
176
+ When the fallback value is inserted and is not already a [`TreeNode`](https://fluidframework.com/docs/api/fluid-framework/treenode-class), the inserted and returned value is the result of implicitly constructing a node from it.
177
+
178
+ 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`.
179
+
180
+ ```typescript
181
+ const schemaFactory = new SchemaFactoryAlpha("example");
182
+ class Inventory extends schemaFactory.mapAlpha(
183
+ "Inventory",
184
+ schemaFactory.number,
185
+ ) {}
186
+
187
+ const inventory = new Inventory(
188
+ new Map([
189
+ ["apples", 5],
190
+ ["pears", 3],
191
+ ]),
192
+ );
193
+
194
+ inventory.getOrInsert("apples", 10); // 5 (existing value returned, not overwritten)
195
+ inventory.getOrInsert("oranges", 10); // 10 (inserted and returned)
196
+
197
+ inventory.getOrInsertComputed("pears", () => computeRestockAmount()); // 3 (existing value returned, callback not invoked)
198
+ inventory.getOrInsertComputed("plums", () => computeRestockAmount()); // inserts and returns the computed value
199
+
200
+ inventory.size; // 4
201
+ ```
202
+
203
+ ## 2.114.0
204
+
205
+ ### Minor Changes
206
+
207
+ - Add FluidReadonlyArray type independent of TypeScript lib ([#27747](https://github.com/microsoft/FluidFramework/pull/27747)) [040d35bc29](https://github.com/microsoft/FluidFramework/commit/040d35bc29901d58e9e778f5f2e75ba581a80dc0)
208
+
209
+ `FluidReadonlyArray<T>` provides an equivalent of the built-in `ReadonlyArray` type that is independent of TypeScript [`lib`](https://www.typescriptlang.org/tsconfig/#lib), following the same pattern as `FluidReadonlyMap` and `FluidMap`.
210
+ The interface includes stable methods through ES2023 (`at()`, `findLast()`, `findLastIndex()`) but excludes newer copy-on-write methods (`toReversed()`, `toSorted()`, `toSpliced()`, `with()`) that Fluid Framework implementations don't yet support.
211
+ This ensures these types remain safe to implement without `lib` changes breaking them.
212
+
213
+ - Add clear method to TreeMapNodeAlpha ([#27765](https://github.com/microsoft/FluidFramework/pull/27765)) [30c889b99c](https://github.com/microsoft/FluidFramework/commit/30c889b99caca3d6ad1ab276761092d94118eab1)
214
+
215
+ [`TreeMapNodeAlpha`](https://fluidframework.com/docs/api/fluid-framework/treemapnodealpha-interface) now has a `clear` method, further aligning it with JavaScript's built-in Map API. It removes all elements from the map.
216
+
217
+ The merge semantics of `clear` are loosely specified: either of the following may occur:
218
+ - `clear` may remove all elements that were in the map when the edit was authored, even if some of those elements have since been moved elsewhere in the tree (in which case they are removed from their new location).
219
+ - `clear` may remove all elements that are in the map when the edit is sequenced, even if some of those elements were not yet in the map when the edit was authored.
220
+
221
+ This method is available on `TreeMapNodeAlpha`, which can be obtained from an existing `TreeMapNode` via `asAlpha`, or by declaring the schema with `SchemaFactoryAlpha`'s `mapAlpha`.
222
+
223
+ ```typescript
224
+ const schemaFactory = new SchemaFactoryAlpha("example");
225
+ class Inventory extends schemaFactory.mapAlpha(
226
+ "Inventory",
227
+ schemaFactory.number,
228
+ ) {}
229
+
230
+ const inventory = new Inventory(
231
+ new Map([
232
+ ["apples", 5],
233
+ ["pears", 3],
234
+ ]),
235
+ );
236
+
237
+ inventory.size; // 2
238
+ inventory.clear();
239
+ inventory.size; // 0
240
+ ```
241
+
242
+ - Promote Fluid container type interfaces to public ([#27746](https://github.com/microsoft/FluidFramework/pull/27746)) [33e014ac63](https://github.com/microsoft/FluidFramework/commit/33e014ac636d43a5f90b1ce1f64b95e60aaf2bca)
243
+
244
+ `FluidIterable`, `FluidIterableIterator`, `FluidReadonlyMap`, `FluidMap`, and `FluidReadonlyArray` are promoted from `@beta` to `@public`.
245
+ These sealed interfaces provide equivalents of the built-in `Iterable`, `IterableIterator`, `ReadonlyMap`, `Map`, and `ReadonlyArray` types that are independent of TypeScript [`lib`](https://www.typescriptlang.org/tsconfig/#lib).
246
+ They can now be used in public API surfaces.
247
+
248
+ - Add new @alpha ServiceClient API for creating and loading Fluid containers ([#27693](https://github.com/microsoft/FluidFramework/pull/27693)) [ee47192d4a](https://github.com/microsoft/FluidFramework/commit/ee47192d4ae91bc28f9154c4d1ead2acad762f3c)
249
+
250
+ This introduces an experimental (`@alpha`), service-agnostic API for working with Fluid containers whose root is an arbitrary data store, along with an in-memory implementation for testing.
251
+
252
+ The new surface is made up of:
253
+ - `ServiceClient` (`@fluidframework/driver-definitions`): the entry point for creating and loading containers. Along with it come the supporting container types (`FluidContainer`, `FluidContainerWithService`, `FluidContainerAttached`), the data store model (`DataStoreKind`, `DataStoreKey`, `DataStoreRegistry`, `DataStoreCreator`), and the generic registry primitives (`Registry`, `RegistryKey`, `lookupInRegistry`, `createBasicRegistryKey`).
254
+ - `defineDataStore` and `sharedObjectRegistryFromIterable` (`@fluidframework/shared-object-base`): build a `DataStoreKind` from a root shared object and a registry of shared object kinds.
255
+ - `defineTreeDataStore` and `instantiateTreeFirstTime` (`@fluidframework/tree`): a SharedTree-specific convenience wrapper that produces a `DataStoreKind` backed by a `TreeView`.
256
+ - `startEphemeralService` (`@fluidframework/local-driver`): starts an in-memory `EphemeralService` for tests. The service owns the lifetime of the in-memory documents and resources, and produces `ServiceClient`s connected to it (via `EphemeralService.newClient` or `EphemeralService.defaultClient`). The helpers `cleanupEphemeralService` and `getDefaultEphemeralService` manage an optional default service instance.
257
+
258
+ Apart from the `@fluidframework/local-driver` helpers (which come from `@fluidframework/local-driver/alpha`), these APIs are also re-exported from `fluid-framework`. None reference any `@legacy` types.
259
+
260
+ Example:
261
+
262
+ ```typescript
263
+ import { startEphemeralService } from "@fluidframework/local-driver/alpha";
264
+ import {
265
+ ServiceClient,
266
+ defineTreeDataStore,
267
+ TreeViewConfiguration,
268
+ SchemaFactory,
269
+ } from "fluid-framework/alpha";
270
+ import { strict as assert } from "node:assert";
271
+
272
+ // Start an ephemeral in-memory service and get a ServiceClient connected to it.
273
+ const service = startEphemeralService();
274
+ const client: ServiceClient = service.defaultClient;
275
+ // Define a DataStoreKind which uses a SharedTree.
276
+ // In this case the schema is for a single number with an initializer that starts the it at 1.
277
+ // This schema is captures in the type allowing for strongly typed access to the data in the tree,
278
+ // where the type matches the schema based runtime enforcement of the schema.
279
+ const numberStore = defineTreeDataStore({
280
+ type: "my-app-root",
281
+ config: new TreeViewConfiguration({ schema: SchemaFactory.number }),
282
+ initializer: () => 1,
283
+ });
284
+
285
+ // Create a container in the service with the above DataStoreKind.
286
+ // Ideally this creation would use a service independent API, and only the attach call would be service dependent,
287
+ // but that is not supported yet.
288
+ const detachedContainer1 = await client.createContainer(numberStore);
289
+ const container1 = await detachedContainer1.attach();
290
+
291
+ // We now have easy and type safe access to the data in the tree, which will be synced over the service.
292
+ assert.equal(container1.data.root, 1);
293
+
294
+ // A second client can load the same container from the service, and will see the same data.
295
+ const container2 = await client.loadContainer(container1.id, numberStore);
296
+ assert.equal(container2.data.root, 1);
297
+
298
+ // Both clients can modify the data, and the changes will be synced over the service.
299
+ container2.data.root = 2;
300
+ // Since we are using an ephemeral service, we can await the synchronization using service.synchronize.
301
+ await service.synchronize();
302
+
303
+ // And now the changes are visible for all clients.
304
+ assert.equal(container1.data.root, 2);
305
+ assert.equal(container2.data.root, 2);
306
+ ```
307
+
308
+ Note that this example does a couple of things which are difficult to do with the other API surfaces:
309
+ 1. It creates a container, then loads a second copy of it, allowing for collaboration. There is currently no non-legacy API surface which allows this without spawning a server process. This is also cleaner than the exacting legacy API options, and can replace the test specific APIs for this as well.
310
+ 2. It creates a container which has a SharedTree at the root, and nothing else. This avoids depending on legacy DDS implementations, which is great for long-term document support and bundle size. This is currently impossible using `fluid-static`, which forces a special root data store. It is also impossible if using `aqueduct`, which forces a root directory in every data store. It can be done using the low level legacy APIs directly, but this new API for it is much simpler.
311
+ 3. There is a common interface all services implement (`ServiceClient`), making the container creation part of the code work for any service implementation.
312
+
3
313
  ## 2.113.0
4
314
 
5
315
  ### Minor Changes