diff --git a/.changeset/clever-birds-wear.md b/.changeset/clever-birds-wear.md deleted file mode 100644 index 9e0596d6efe6..000000000000 --- a/.changeset/clever-birds-wear.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"@fluidframework/merge-tree": minor -"@fluidframework/tree": minor -"fluid-framework": minor ---- ---- -"section": fix ---- - -Fix compiler errors when building with libCheck - -Compiling code using Fluid Framework when using TypeScript's `libCheck` (meaning without [skipLibCheck](https://www.typescriptlang.org/tsconfig/#skipLibCheck)), two compile errors can be encountered: - -``` -> tsc - -node_modules/@fluidframework/merge-tree/lib/client.d.ts:124:18 - error TS2368: Type parameter name cannot be 'undefined'. - -124 walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; - ~~~~~~~~~ - -node_modules/@fluidframework/tree/lib/util/utils.d.ts:5:29 - error TS7016: Could not find a declaration file for module '@ungap/structured-clone'. 'node_modules/@ungap/structured-clone/esm/index.js' implicitly has an 'any' type. - Try `npm i --save-dev @types/ungap__structured-clone` if it exists or add a new declaration (.d.ts) file containing `declare module '@ungap/structured-clone';` - -5 import structuredClone from "@ungap/structured-clone"; - ~~~~~~~~~~~~~~~~~~~~~~~~~ -``` - -The first error impacts projects using TypeScript 5.5 or greater and either of the `fluid-framework` or `@fluidframework/merge-tree` packages. -The second error impacts projects using the `noImplicitAny` tsconfig setting and the `fluid-framework` or `@fluidframework/tree` packages. - -Both have been fixed. - -This should allow `libCheck` to be reenabled in any impacted projects. diff --git a/.changeset/few-cobras-sing.md b/.changeset/few-cobras-sing.md deleted file mode 100644 index 1e99de0af439..000000000000 --- a/.changeset/few-cobras-sing.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"fluid-framework": minor -"@fluidframework/tree": minor ---- ---- -"section": tree ---- - -Add `.schema` member to alpha enum schema APIs - -The return value from `@alpha` APIs `enumFromStrings` and `adaptEnum` now has a property named `schema` which can be used to include it in a parent schema. -This replaces the use of `typedObjectValues` which has been removed. - -Use of these APIs now look like: - -```typescript -const schemaFactory = new SchemaFactory("com.myApp"); -const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); -type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; -class Parent extends schemaFactory.object("Parent", { mode: Mode.schema }) {} -``` - - -Previously the last two lines would have been: - -```typescript -type Mode = NodeFromSchema<(typeof Mode)[keyof typeof Mode]>; // This no longer works -class Parent extends schemaFactory.object("Parent", { mode: typedObjectValues(Mode) }) {} // This no longer works -``` diff --git a/.changeset/fifty-showers-divide.md b/.changeset/fifty-showers-divide.md new file mode 100644 index 000000000000..9968101be377 --- /dev/null +++ b/.changeset/fifty-showers-divide.md @@ -0,0 +1,34 @@ +--- +"fluid-framework": minor +"@fluidframework/merge-tree": minor +"@fluidframework/sequence": minor +--- +--- +"section": deprecation +--- + +Unsupported merge-tree types and related exposed internals have been removed + +As part of ongoing improvements, several internal types and related APIs have been removed. These types are unnecessary for any supported scenarios and could lead to errors if used. Since directly using these types would likely result in errors, these changes are not likely to impact any Fluid Framework consumers. + +Removed types: +- IMergeTreeTextHelper +- MergeNode +- ObliterateInfo +- PropertiesManager +- PropertiesRollback +- SegmentGroup +- SegmentGroupCollection + +In addition to removing the above types, they are no longer exposed through the following interfaces and their implementations: `ISegment`, `ReferencePosition`, and `ISerializableInterval`. + +Removed functions: +- addProperties +- ack + +Removed properties: +- propertyManager +- segmentGroups + +The initial deprecations of the now changed or removed types were announced in Fluid Framework v2.2.0: +[Fluid Framework v2.2.0](https://github.com/microsoft/FluidFramework/blob/main/RELEASE_NOTES/2.2.0.md) diff --git a/.changeset/floppy-forks-attack.md b/.changeset/floppy-forks-attack.md deleted file mode 100644 index f4f3f295d02f..000000000000 --- a/.changeset/floppy-forks-attack.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"fluid-framework": minor -"@fluidframework/tree": minor ---- ---- -"section": tree ---- - -`TreeNodeSchemaClass` now specifies its `TNode` as `TreeNode` - -`TreeNodeSchemaClass`'s `TNode` parameter used to be `unknown` and was recently improved to be the more specific `TreeNode | TreeLeafValue`. -This change further narrows this to `TreeNode`. - -`TreeNodeSchema`, which is more commonly used, still permits `TNode` of `TreeNode | TreeLeafValue`, so this change should have little impact on most code, but in some edge cases it can result in slightly more specific typing. diff --git a/.changeset/fruity-sites-boil.md b/.changeset/fruity-sites-boil.md new file mode 100644 index 000000000000..e34fe4563482 --- /dev/null +++ b/.changeset/fruity-sites-boil.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/runtime-definitions": minor +--- +--- +"section": other +--- + +Changes to the batchBegin and batchEnd events on ContainerRuntime + +The 'batchBegin'/'batchEnd' events on ContainerRuntime indicate when a batch is beginning or finishing being processed. The `contents` property on the event argument `op` is not useful or relevant when reasoning over incoming changes at the batch level. Accordingly, it has been removed from the `op` event argument. diff --git a/.changeset/hot-falcons-grab.md b/.changeset/hot-falcons-grab.md deleted file mode 100644 index f02279bffe21..000000000000 --- a/.changeset/hot-falcons-grab.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@fluidframework/datastore-definitions": minor -"@fluidframework/runtime-definitions": minor ---- ---- -"section": other ---- - -Change when the `op` event on `IFluidDataStoreRuntimeEvents` and `IContainerRuntimeBaseEvents` is emitted - -Previous behavior - It was emitted immediately after an op was processed and before the next op was processed. - -New behavior - It will still be emitted after an op is processed but it may not be immediate. Other ops in a batch may be processed before the op event is emitted for an op. diff --git a/.changeset/icy-webs-help.md b/.changeset/icy-webs-help.md new file mode 100644 index 000000000000..395bc6c8e385 --- /dev/null +++ b/.changeset/icy-webs-help.md @@ -0,0 +1,14 @@ +--- +"@fluidframework/container-runtime": minor +"@fluidframework/runtime-definitions": minor +"@fluidframework/test-runtime-utils": minor +--- +--- +"section": legacy +--- + +"Remove `IFluidParentContext.ensureNoDataModelChanges` and its implementations + +- `IFluidParentContext.ensureNoDataModelChanges` has been removed. [prior deprecation commit](https://github.com/microsoft/FluidFramework/commit/c9d156264bdfa211a3075bdf29cde442ecea234c) + +- `MockFluidDataStoreContext.ensureNoDataModelChanges` has also been removed. diff --git a/.changeset/lazy-onions-know.md b/.changeset/lazy-onions-know.md deleted file mode 100644 index 4f2e8ba17be4..000000000000 --- a/.changeset/lazy-onions-know.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -"fluid-framework": minor -"@fluidframework/tree": minor ---- ---- -"section": tree ---- - -Array and Map nodes can now be explicitly constructed with undefined or no argument - -The input parameter to the constructor and `create` methods of Array and Map nodes is now optional. When the optional parameter is omitted, an empty map or array will be created. - -```typescript -class Schema extends schemaFactory.array("x", schemaFactory.number) {} - -// Existing support -const _fromIterable: Schema = new Schema([]); - -// New -const _fromUndefined: Schema = new Schema(undefined); -const _fromNothing: Schema = new Schema(); -``` - -```typescript -class Schema extends schemaFactory.map("x", schemaFactory.number) {} - -// Existing support -const _fromIterable: Schema = new Schema([]); -const _fromObject: Schema = new Schema({}); - -// New -const _fromUndefined: Schema = new Schema(undefined); -const _fromNothing: Schema = new Schema(); -``` - -```typescript -const Schema = schemaFactory.array( schemaFactory.number); -type Schema = NodeFromSchema; - -// Existing support -const _fromIterable: Schema = Schema.create([]); - -// New -const _fromUndefined: Schema = Schema.create(undefined); -const _fromNothing: Schema = Schema.create(); -``` - -```typescript -const Schema = schemaFactory.map(schemaFactory.number); -type Schema = NodeFromSchema; -// Existing support -const _fromIterable: Schema = Schema.create([]); -const _fromObject: Schema = Schema.create({}); - -// New -const _fromUndefined: Schema = Schema.create(undefined); -const _fromNothing: Schema = Schema.create(); -``` diff --git a/.changeset/loud-lemons-switch.md b/.changeset/loud-lemons-switch.md new file mode 100644 index 000000000000..147f054585dc --- /dev/null +++ b/.changeset/loud-lemons-switch.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/runtime-utils": minor +--- +--- +"section": feature +--- + +New compareFluidHandle function for comparing FluidHandles + +The new `compareFluidHandle` function has been added to allow comparing handles without having to inspect their internals. diff --git a/.changeset/lovely-taxis-notice.md b/.changeset/lovely-taxis-notice.md new file mode 100644 index 000000000000..7b382d050175 --- /dev/null +++ b/.changeset/lovely-taxis-notice.md @@ -0,0 +1,27 @@ +--- +"fluid-framework": minor +"@fluidframework/tree": minor +--- +--- +"section": tree +--- + +SharedTree event listeners that implement `Listenable` now allow deregistration of event listeners via an `off()` function. + +The ability to deregister events via a callback returned by `on()` remains the same. +Both strategies will remain supported and consumers of SharedTree events may choose which method of deregistration they prefer in a given instance. + +```typescript +// The new behavior +function deregisterViaOff(view: TreeView): { + const listener = () => { /* ... */ }; + view.events.on("commitApplied", listener); // Register + view.events.off("commitApplied", listener); // Deregister +} + +// The existing behavior (still supported) +function deregisterViaCallback(view: TreeView): { + const off = view.events.on("commitApplied", () => { /* ... */ }); // Register + off(); // Deregister +} +``` diff --git a/.changeset/moody-flies-fly.md b/.changeset/moody-flies-fly.md new file mode 100644 index 000000000000..75a5dd57fed3 --- /dev/null +++ b/.changeset/moody-flies-fly.md @@ -0,0 +1,26 @@ +--- +"fluid-framework": minor +"@fluidframework/tree": minor +--- +--- +"section": tree +--- + +Allow constructing recursive maps from objects + +Previously only non-recursive maps could be constructed from objects. +Now all maps nodes can constructed from objects: + +```typescript +class MapRecursive extends sf.mapRecursive("Map", [() => MapRecursive]) {} +{ + type _check = ValidateRecursiveSchema; +} +// New: +const fromObject = new MapRecursive({ x: new MapRecursive() }); +// Existing: +const fromIterator = new MapRecursive([["x", new MapRecursive()]]); +const fromMap = new MapRecursive(new Map([["x", new MapRecursive()]])); +const fromNothing = new MapRecursive(); +const fromUndefined = new MapRecursive(undefined); +``` diff --git a/.changeset/neat-lights-worry.md b/.changeset/neat-lights-worry.md deleted file mode 100644 index c31ef4e30996..000000000000 --- a/.changeset/neat-lights-worry.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@fluidframework/datastore-definitions": minor -"@fluidframework/runtime-definitions": minor -"@fluidframework/test-runtime-utils": minor ---- ---- -"section": deprecation ---- - -The function `process` on `IFluidDataStoreChannel`, `IDeltaHandler`, `MockFluidDataStoreRuntime` and `MockDeltaConnection` is now deprecated - -A new function `processMessages` has been added in its place which will be called to process multiple messages instead of a single one on the channel. This is part of a feature called "Op bunching" where contiguous ops of a given type and to a given data store / DDS are bunched and sent together for processing. - -Implementations of `IFluidDataStoreChannel` and `IDeltaHandler` must now also implement `processMessages`. For reference implementations, see `FluidDataStoreRuntime::processMessages` and `SharedObjectCore::attachDeltaHandler`. diff --git a/.changeset/nice-flies-brake.md b/.changeset/nice-flies-brake.md new file mode 100644 index 000000000000..f371e5a54182 --- /dev/null +++ b/.changeset/nice-flies-brake.md @@ -0,0 +1,32 @@ +--- +"@fluidframework/aqueduct": minor +"@fluidframework/container-definitions": minor +"@fluidframework/container-loader": minor +"@fluidframework/container-runtime": minor +"@fluidframework/container-runtime-definitions": minor +"@fluidframework/datastore": minor +"@fluidframework/devtools-core": minor +"@fluidframework/fluid-static": minor +"@fluidframework/runtime-definitions": minor +"@fluidframework/runtime-utils": minor +"@fluid-private/test-end-to-end-tests": minor +"@fluidframework/test-runtime-utils": minor +"@fluidframework/test-utils": minor +--- +--- +"section": legacy +--- + +The inbound and outbound properties have been removed from IDeltaManager + +The inbound and outbound properties were [deprecated in version 2.0.0-rc.2.0.0](https://github.com/microsoft/FluidFramework/blob/main/RELEASE_NOTES/2.0.0-rc.2.0.0.md#container-definitions-deprecate-ideltamanagerinbound-and-ideltamanageroutbound) and have been removed from `IDeltaManager`. + +`IDeltaManager.inbound` contained functionality that could break core runtime features such as summarization and processing batches if used improperly. Data loss or corruption could occur when `IDeltaManger.inbound.pause()` or `IDeltaManager.inbound.resume()` were called. + +Similarly, `IDeltaManager.outbound` contained functionality that could break core runtime features such as generation of batches and chunking. Data loss or corruption could occur when `IDeltaManger.inbound.pause()` or `IDeltaManager.inbound.resume()` were called. + +#### Alternatives + +- Alternatives to `IDeltaManager.inbound.on("op", ...)` are `IDeltaManager.on("op", ...)` +- Alternatives to calling `IDeltaManager.inbound.pause`, `IDeltaManager.outbound.pause` for `IContainer` disconnect use `IContainer.disconnect`. +- Alternatives to calling `IDeltaManager.inbound.resume`, `IDeltaManager.outbound.resume` for `IContainer` reconnect use `IContainer.connect`. diff --git a/.changeset/rude-views-bake.md b/.changeset/rude-views-bake.md new file mode 100644 index 000000000000..10b32cc277f0 --- /dev/null +++ b/.changeset/rude-views-bake.md @@ -0,0 +1,30 @@ +--- +"fluid-framework": minor +"@fluidframework/merge-tree": minor +"@fluidframework/sequence": minor +--- +--- +"section": legacy +--- + +MergeTree `Client` Legacy API Removed + +The `Client` class in the merge-tree package has been removed. Types that directly or indirectly expose the merge-tree `Client` class have also been removed. + +The removed types were not meant to be used directly, and direct usage was not supported: + +- AttributionPolicy +- IClientEvents +- IMergeTreeAttributionOptions +- SharedSegmentSequence +- SharedStringClass + +Some classes that referenced the `Client` class have been transitioned to interfaces. Direct instantiation of these classes was not supported or necessary for any supported scenario, so the change to an interface should not impact usage. This applies to the following types: + +- SequenceInterval +- SequenceEvent +- SequenceDeltaEvent +- SequenceMaintenanceEvent + +The initial deprecations of the now changed or removed types were announced in Fluid Framework v2.4.0: +[Several MergeTree Client Legacy APIs are now deprecated](https://github.com/microsoft/FluidFramework/blob/main/RELEASE_NOTES/2.4.0.md#several-mergetree-client-legacy-apis-are-now-deprecated-22629) diff --git a/.changeset/shiny-swans-relax.md b/.changeset/shiny-swans-relax.md deleted file mode 100644 index 35abfb7008c5..000000000000 --- a/.changeset/shiny-swans-relax.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -"fluid-framework": minor -"@fluidframework/tree": minor ---- ---- -"section": tree ---- - -Improve typing when exact TypeScript type for a schema is not provided - -The Tree APIs are designed to be used in a strongly typed way, with the full TypeScript type for the schema always being provided. -Due to limitations of the TypeScript language, there was no practical way to prevent less descriptive types, like `TreeNodeSchema` or `ImplicitFieldSchema`, from being used where the type of a specific schema was intended. -Code which does this will encounter several issues with tree APIs, and this change fixes some of those issues. -This change mainly fixes that `NodeFromSchema` used to return `unknown` and now returns `TreeNode | TreeLeafValue`. - -This change by itself seems mostly harmless, as it just improves the precision of the typing in this one edge case. -Unfortunately, there are other typing bugs which complicate the situation, causing APIs for inserting data into the tree to also behave poorly when given non-specific types like `TreeNodeSchema`. -These APIs include cases like `TreeView.initialize`. - -This incorrectly allowed some usage like taking a type-erased schema and initial tree pair, creating a view of type `TreeView`, then initializing it. -With the typing being partly fixed, some unsafe inputs are still allowed when trying to initialize such a view, but some are now prevented. - -This use-case of modifying trees in code not that is not strongly typed by the exact schema was not intended to be supported. -Despite this, it did mostly work in some cases, and has some real use-cases (like tests looping over test data consisting of pairs of schema and initial trees). -To help mitigate the impact of this change, some experimental `@alpha` APIs have been introduced to help address these previously unsupported but somewhat working use-cases. - -Before this change: - -```typescript -import { TinyliciousClient } from "@fluidframework/tinylicious-client"; -import { - SchemaFactory, - SharedTree, - TreeViewConfiguration, - type TreeNodeSchema, -} from "fluid-framework"; - -// Create a ITree instance -const tinyliciousClient = new TinyliciousClient(); -const { container } = await tinyliciousClient.createContainer({ initialObjects: {} }, "2"); -const tree = await container.create(SharedTree); - -const schemaFactory = new SchemaFactory("demo"); - -// Bad: This loses the schema aware type information. `: TreeNodeSchema` should be omitted to preserve strong typing. -const schema: TreeNodeSchema = schemaFactory.array(schemaFactory.number); -const config = new TreeViewConfiguration({ schema }); - -// This view is typed as `TreeView`, which does not work well since it's missing the actual schema type information. -const view = tree.viewWith(config); -// Root is typed as `unknown` allowing invalid assignment operations. -view.root = "invalid"; -view.root = {}; -// Since all assignments are allowed, valid ones still work: -view.root = []; -``` - -After this change: - - -```typescript -// Root is now typed as `TreeNode | TreeLeafValue`, still allowing some invalid assignment operations. -// In the future this should be prevented as well, since the type of the setter in this case should be `never`. -view.root = "invalid"; -// This no longer compiles: -view.root = {}; -// This also no longer compiles despite being valid at runtime: -view.root = []; -``` - -For code that wants to continue using an unsafe API, which can result in runtime errors if the data does not follow the schema, a new alternative has been added to address this use-case. A special type `UnsafeUnknownSchema` can now be used to opt into allowing all valid trees to be provided. -Note that this leaves ensuring the data is in schema up to the user. -For now these adjusted APIs can be accessed by casting the view to `TreeViewAlpha`. -If stabilized, this option will be added to `TreeView` directly. - -```typescript -const viewAlpha = view as TreeViewAlpha; -viewAlpha.initialize([]); -viewAlpha.root = []; -``` - -Additionally, this seems to have negatively impacted co-recursive schema which declare a co-recursive array as the first schema in the co-recursive cycle. -Like the TypeScript language our schema system is built on, we don't guarantee exactly which recursive type will compile, but will do our best to ensure useful recursive schema can be created easily. -In this case a slight change may be required to some recursive schema to get them to compile again: - -For example this schema used to compile: - - -```typescript -class A extends sf.arrayRecursive("A", [() => B]) {} -{ - type _check = ValidateRecursiveSchema; -} -// Used to work, but breaks in this update. -class B extends sf.object("B", { x: A }) {} -``` - -But now you must use the recursive functions like `objectRecursive` for types which are co-recursive with an array in some cases. -In our example, it can be fixed as follows: - -```typescript -class A extends sf.arrayRecursive("A", [() => B]) {} -{ - type _check = ValidateRecursiveSchema; -} -// Fixed corecursive type, using "Recursive" method variant to declare schema. -class B extends sf.objectRecursive("B", { x: A }) {} -{ - type _check = ValidateRecursiveSchema; -} -``` - -Note: while the following pattern may still compile, we recommend using the previous pattern instead since the one below may break in the future. - -```typescript -class B extends sf.objectRecursive("B", { x: [() => A] }) {} -{ - type _check = ValidateRecursiveSchema; -} -// Works, for now, but not recommended. -class A extends sf.array("A", B) {} -``` diff --git a/.changeset/shy-dots-prove.md b/.changeset/shy-dots-prove.md deleted file mode 100644 index ffe2d2c40e66..000000000000 --- a/.changeset/shy-dots-prove.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -"fluid-framework": minor -"@fluidframework/tree": minor ---- ---- -"section": tree ---- - -Improve strictness of input tree types when non-exact schema are provided - -Consider the following code where the type of the schema is not exactly specified: - -```typescript -const schemaFactory = new SchemaFactory("com.myApp"); -class A extends schemaFactory.object("A", {}) {} -class B extends schemaFactory.array("B", schemaFactory.number) {} - -// Gives imprecise type (typeof A | typeof B)[]. The desired precise type here is [typeof A, typeof B]. -const schema = [A, B]; - -const config = new TreeViewConfiguration({ schema }); -const view = sharedTree.viewWith(config); - -// Does not compile since setter for root is typed `never` due to imprecise schema. -view.root = []; -``` - -The assignment of `view.root` is disallowed since a schema with type `(typeof A | typeof B)[]` could be any of: - -```typescript -const schema: (typeof A | typeof B)[] = [A]; -``` - -```typescript -const schema: (typeof A | typeof B)[] = [B]; -``` - -```typescript -const schema: (typeof A | typeof B)[] = [A, B]; -``` - -The attempted assignment is not compatible with all of these (specifically it is incompatible with the first one) so performing this assignment could make the tree out of schema and is thus disallowed. - -To avoid this ambiguity and capture the precise type of `[typeof A, typeof B]`, use one of the following patterns: - -```typescript -const schema = [A, B] as const; -const config = new TreeViewConfiguration({ schema }); -``` - -```typescript -const config = new TreeViewConfiguration({ schema: [A, B] }); -``` - -To help update existing code which accidentally depended on this bug, an `@alpha` API `unsafeArrayToTuple` has been added. -Many usages of this API will produce incorrectly typed outputs. -However, when given `AllowedTypes` arrays which should not contain any unions, but that were accidentally flattened to a single union, it can fix them: - -```typescript -// Gives imprecise type (typeof A | typeof B)[] -const schemaBad = [A, B]; -// Fixes the type to be [typeof A, typeof B] -const schema = unsafeArrayToTuple(schemaBad); - -const config = new TreeViewConfiguration({ schema }); -``` diff --git a/.changeset/stinky-cars-fart.md b/.changeset/stinky-cars-fart.md new file mode 100644 index 000000000000..e5f782783403 --- /dev/null +++ b/.changeset/stinky-cars-fart.md @@ -0,0 +1,11 @@ +--- +"@fluidframework/tree": minor +--- +--- +"section": tree +--- + +Provide more comprehensive replacement to the `commitApplied` event + +Adds a new `changed` event to the (currently alpha) `TreeBranchEvents` that replaces the `commitApplied` event on `TreeViewEvents`. +This new event is fired for both local and remote changes and maintains the existing functionality of `commitApplied` that is used for obtaining `Revertibles`. diff --git a/.changeset/tough-crabs-check.md b/.changeset/tough-crabs-check.md deleted file mode 100644 index e04ba068cc74..000000000000 --- a/.changeset/tough-crabs-check.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@fluidframework/container-runtime": minor ---- ---- -"section": other ---- - -Reformat Signal telemetry events details - -Properties of `eventName`s beginning "fluid:telemetry:ContainerRuntime:Signal" are updated to use `details` for all event specific information. Additional per-event changes: -- SignalLatency: shorten names now that data is packed into details. Renames: - - `signalsSent` -> `sent` - - `signalsLost` -> `lost` - - `outOfOrderSignals` -> `outOfOrder` -- SignalLost/SignalOutOfOrder: rename `trackingSequenceNumber` to `expectedSequenceNumber` -- SignalOutOfOrder: rename `type` to `contentsType` and only emit it some of the time - -Reminder: Naming and structure of telemetry events are not considered a part of API and may change at any time. diff --git a/.changeset/twenty-cameras-take.md b/.changeset/twenty-cameras-take.md new file mode 100644 index 000000000000..7c4cadc72b28 --- /dev/null +++ b/.changeset/twenty-cameras-take.md @@ -0,0 +1,10 @@ +--- +"@fluid-experimental/presence": minor +--- +--- +"section": other +--- + +Untangle `PresenceStates` methods and properties + +`PresenceStatesEntries` which represent each of the states in `PresenceStates` schema have been relocated from directly within `PresenceStates` to under property names `prop`. Only the `add` method remains. (Type `PresenceStatesMethods` has been removed.) diff --git a/.github/workflows/pr-check-changeset.yml b/.github/workflows/pr-check-changeset.yml index 402e2c8f4fc3..4777882e9a78 100644 --- a/.github/workflows/pr-check-changeset.yml +++ b/.github/workflows/pr-check-changeset.yml @@ -71,7 +71,8 @@ jobs: echo $(jq -c '. += {required: false}' changeset-metadata.json) > changeset-metadata.json - name: Upload changeset metadata - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: changeset-metadata path: ./changeset-metadata.json @@ -96,7 +97,8 @@ jobs: echo $(jq -c '. += { pr: "${{ github.event.number }}" }' changeset-metadata.json) > changeset-metadata.json - name: Upload changeset metadata - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: changeset-metadata path: ./changeset-metadata.json diff --git a/.github/workflows/push-tag-create-release.yml b/.github/workflows/push-tag-create-release.yml index a70da20a60c8..ff438e3ffc11 100644 --- a/.github/workflows/push-tag-create-release.yml +++ b/.github/workflows/push-tag-create-release.yml @@ -69,7 +69,8 @@ jobs: run: | flub release fromTag $TAG_NAME --json | jq -c > release-metadata.json - name: Upload release metadata JSON - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: release-metadata path: release-metadata.json @@ -98,7 +99,8 @@ jobs: mkdir reports flub release report -g ${{ fromJson(env.RELEASE_JSON).packageOrReleaseGroup }} -o reports - name: Upload release reports - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: release-reports path: reports diff --git a/.github/workflows/release-branches.yml b/.github/workflows/release-branches.yml index 0528ae01e841..9fee11d1b18f 100644 --- a/.github/workflows/release-branches.yml +++ b/.github/workflows/release-branches.yml @@ -79,8 +79,8 @@ jobs: run: echo ${{ github.event.pull_request.head.sha }} > ./artifacts/commit_sha - name: Upload artifact - # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.0 - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # ratchet:actions/upload-artifact@v4.4.0 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: release-branch-pr-metadata path: ./artifacts diff --git a/.github/workflows/website-validation.yml b/.github/workflows/website-validation.yml index 9abae7a197d7..e92d23faffdd 100644 --- a/.github/workflows/website-validation.yml +++ b/.github/workflows/website-validation.yml @@ -38,7 +38,8 @@ jobs: pnpm i --frozen-lockfile npm run ci:build - name: Upload site artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: fluidframework-site path: docs/public @@ -80,7 +81,8 @@ jobs: run: mkdir -p ./results - name: Download site artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3 + # release notes: https://github.com/actions/download-artifact/releases/tag/v4.1.8 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # ratchet:actions/download-artifact@v4 with: name: fluidframework-site path: docs/public @@ -102,7 +104,8 @@ jobs: cat ./results/linkcheck-output.txt | tee -a ./results/linkcheck echo -e "\n\`\`\`" | tee -a ./results/linkcheck - name: Upload results artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # ratchet:actions/upload-artifact@v3 + # release notes: https://github.com/actions/upload-artifact/releases/tag/v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # ratchet:actions/upload-artifact@v4 with: name: website-validation-results path: ./docs/results diff --git a/RELEASE_NOTES/2.5.0.md b/RELEASE_NOTES/2.5.0.md new file mode 100644 index 000000000000..e63b34e3facf --- /dev/null +++ b/RELEASE_NOTES/2.5.0.md @@ -0,0 +1,549 @@ + + +# Fluid Framework v2.5.0 + +## Contents + +- [✨ New Features](#-new-features) + - [ISessionClient now exposes connectivity information (#22973)](#isessionclient-now-exposes-connectivity-information-22973) +- [🌳 SharedTree DDS changes](#-sharedtree-dds-changes) + - [✨ New! Alpha APIs for tree data import and export (#22566)](#-new-alpha-apis-for-tree-data-import-and-export-22566) + - [Typing has been improved when an exact TypeScript type for a schema is not provided (#22763)](#typing-has-been-improved-when-an-exact-typescript-type-for-a-schema-is-not-provided-22763) + - [A `.schema` member has been added to the alpha enum schema APIs (#22874)](#a-schema-member-has-been-added-to-the-alpha-enum-schema-apis-22874) + - [The strictness of input tree types when inexact schemas are provided has been improved (#22874)](#the-strictness-of-input-tree-types-when-inexact-schemas-are-provided-has-been-improved-22874) + - [TreeNodeSchemaClass now specifies its TNode as TreeNode (#22938)](#treenodeschemaclass-now-specifies-its-tnode-as-treenode-22938) + - [Array and Map nodes can now be explicitly constructed with undefined or no argument (#22946)](#array-and-map-nodes-can-now-be-explicitly-constructed-with-undefined-or-no-argument-22946) + - [SharedTree branching API has been improved (#22970)](#sharedtree-branching-api-has-been-improved-22970) +- [🐛 Bug Fixes](#-bug-fixes) + - [Compilation no longer fails when building with TypeScript's libCheck option (#22923)](#compilation-no-longer-fails-when-building-with-typescripts-libcheck-option-22923) +- [⚠️ Deprecations](#️-deprecations) + - [The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated (#22840)](#the-process-function-on-ifluiddatastorechannel-ideltahandler-mockfluiddatastoreruntime-and-mockdeltaconnection-is-now-deprecated-22840) +- [Other Changes](#other-changes) + - [Signal telemetry events details (#22804)](#signal-telemetry-events-details-22804) + - [The op event on IFluidDataStoreRuntimeEvents and IContainerRuntimeBaseEvents is emitted at a different time (#22840)](#the-op-event-on-ifluiddatastoreruntimeevents-and-icontainerruntimebaseevents-is-emitted-at-a-different-time-22840) + +## ✨ New Features + +### ISessionClient now exposes connectivity information ([#22973](https://github.com/microsoft/FluidFramework/issues/22973)) + +1. `ISessionClient` has a new method, `getConnectionStatus()`, with two possible states: `Connected` and `Disconnected`. +2. `ISessionClient`'s `connectionId()` member has been renamed to `getConnectionId()` for consistency. +3. `IPresence` event `attendeeDisconnected` is now implemented. + +#### Change details + +Commit: [`6096657`](https://github.com/microsoft/FluidFramework/commit/609665762050b5f3baf737d752fc87ef962b3a21) + +Affected packages: + +- @fluid-experimental/presence + +[⬆️ Table of contents](#contents) + +## 🌳 SharedTree DDS changes + +### ✨ New! Alpha APIs for tree data import and export ([#22566](https://github.com/microsoft/FluidFramework/issues/22566)) + +A collection of new `@alpha` APIs for importing and exporting tree content and schema from SharedTrees has been added to `TreeAlpha`. These include import and export APIs for `VerboseTree`, `ConciseTree` and compressed tree formats. + +`TreeAlpha.create` is also added to allow constructing trees with a more general API instead of having to use the schema constructor directly (since that doesn't handle polymorphic roots, or non-schema aware code). + +The function `independentInitializedView` has been added to provide a way to combine data from the existing `extractPersistedSchema` and new `TreeAlpha.exportCompressed` back into a `TreeView` in a way which can support safely importing data which could have been exported with a different schema. This allows replicating the schema evolution process for Fluid documents stored in a service, but entirely locally without involving any collaboration services. `independentView` has also been added, which is similar but handles the case of creating a new view without an existing schema or tree. + +Together these APIs address several use-cases: + +1. Using SharedTree as an in-memory non-collaborative datastore. +2. Importing and exporting data from a SharedTree to and from other services or storage locations (such as locally saved files). +3. Testing various scenarios without relying on a service. +4. Using SharedTree libraries for just the schema system and encode/decode support. + +#### Change details + +Commit: [`18a23e8`](https://github.com/microsoft/FluidFramework/commit/18a23e8816467f2ed0c9d6d8637b70d99aa48b7a) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### Typing has been improved when an exact TypeScript type for a schema is not provided ([#22763](https://github.com/microsoft/FluidFramework/issues/22763)) + +The Tree APIs are designed to be used in a strongly typed way, with the full TypeScript type for the schema always being provided. Due to limitations of the TypeScript language, there was no practical way to prevent less descriptive types, like `TreeNodeSchema` or `ImplicitFieldSchema`, from being used where the type of a specific schema was intended. Code which does this will encounter several issues with tree APIs, and this change fixes some of those issues. This change mainly fixes that `NodeFromSchema` used to return `unknown` and now returns `TreeNode | TreeLeafValue`. + +This change by itself seems mostly harmless, as it just improves the precision of the typing in this one edge case. Unfortunately, there are other typing bugs which complicate the situation, causing APIs for inserting data into the tree to also behave poorly when given non-specific types like `TreeNodeSchema`. These APIs include cases like `TreeView.initialize`. + +This incorrectly allowed some usage like taking a type-erased schema and initial tree pair, creating a view of type `TreeView`, then initializing it. With the typing being partly fixed, some unsafe inputs are still allowed when trying to initialize such a view, but some are now prevented. + +This use-case of modifying trees in code not that is not strongly typed by the exact schema was not intended to be supported. Despite this, it did mostly work in some cases, and has some real use-cases (like tests looping over test data consisting of pairs of schema and initial trees). To help mitigate the impact of this change, some experimental `@alpha` APIs have been introduced to help address these previously unsupported but somewhat working use-cases. + +Before this change: + +```typescript +import { TinyliciousClient } from "@fluidframework/tinylicious-client"; +import { + SchemaFactory, + SharedTree, + TreeViewConfiguration, + type TreeNodeSchema, +} from "fluid-framework"; + +// Create a ITree instance +const tinyliciousClient = new TinyliciousClient(); +const { container } = await tinyliciousClient.createContainer( + { initialObjects: {} }, + "2", +); +const tree = await container.create(SharedTree); + +const schemaFactory = new SchemaFactory("demo"); + +// Bad: This loses the schema aware type information. `: TreeNodeSchema` should be omitted to preserve strong typing. +const schema: TreeNodeSchema = schemaFactory.array(schemaFactory.number); +const config = new TreeViewConfiguration({ schema }); + +// This view is typed as `TreeView`, which does not work well since it's missing the actual schema type information. +const view = tree.viewWith(config); +// Root is typed as `unknown` allowing invalid assignment operations. +view.root = "invalid"; +view.root = {}; +// Since all assignments are allowed, valid ones still work: +view.root = []; +``` + +After this change: + +```typescript +// Root is now typed as `TreeNode | TreeLeafValue`, still allowing some invalid assignment operations. +// In the future this should be prevented as well, since the type of the setter in this case should be `never`. +view.root = "invalid"; +// This no longer compiles: +view.root = {}; +// This also no longer compiles despite being valid at runtime: +view.root = []; +``` + +For code that wants to continue using an unsafe API, which can result in runtime errors if the data does not follow the schema, a new alternative has been added to address this use-case. A special type `UnsafeUnknownSchema` can now be used to opt into allowing all valid trees to be provided. Note that this leaves ensuring the data is in schema up to the user. For now these adjusted APIs can be accessed by casting the view to `TreeViewAlpha`. If stabilized, this option will be added to `TreeView` directly. + +```typescript +const viewAlpha = view as TreeViewAlpha; +viewAlpha.initialize([]); +viewAlpha.root = []; +``` + +Additionally, this seems to have negatively impacted co-recursive schema which declare a co-recursive array as the first schema in the co-recursive cycle. Like the TypeScript language our schema system is built on, we don't guarantee exactly which recursive type will compile, but will do our best to ensure useful recursive schema can be created easily. In this case a slight change may be required to some recursive schema to get them to compile again: + +For example this schema used to compile: + +```typescript +class A extends sf.arrayRecursive("A", [() => B]) {} +{ + type _check = ValidateRecursiveSchema; +} +// Used to work, but breaks in this update. +class B extends sf.object("B", { x: A }) {} +``` + +But now you must use the recursive functions like `objectRecursive` for types which are co-recursive with an array in some cases. In our example, it can be fixed as follows: + +```typescript +class A extends sf.arrayRecursive("A", [() => B]) {} +{ + type _check = ValidateRecursiveSchema; +} +// Fixed corecursive type, using "Recursive" method variant to declare schema. +class B extends sf.objectRecursive("B", { x: A }) {} +{ + type _check = ValidateRecursiveSchema; +} +``` + +Note: while the following pattern may still compile, we recommend using the previous pattern instead since the one below may break in the future. + +```typescript +class B extends sf.objectRecursive("B", { x: [() => A] }) {} +{ + type _check = ValidateRecursiveSchema; +} +// Works, for now, but not recommended. +class A extends sf.array("A", B) {} +``` + +#### Change details + +Commit: [`05197d6`](https://github.com/microsoft/FluidFramework/commit/05197d6d3f0189ecd61fd74ec55f6836e6797249) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### A `.schema` member has been added to the alpha enum schema APIs ([#22874](https://github.com/microsoft/FluidFramework/issues/22874)) + +The return value from `@alpha` APIs `enumFromStrings` and `adaptEnum` now has a property named `schema` which can be used to include it in a parent schema. This replaces the use of `typedObjectValues` which has been removed. + +Use of these APIs now look like: + +```typescript +const schemaFactory = new SchemaFactory("com.myApp"); +const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); +type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; +class Parent extends schemaFactory.object("Parent", { mode: Mode.schema }) {} +``` + +Previously, the last two lines would have been: + +```typescript +type Mode = NodeFromSchema<(typeof Mode)[keyof typeof Mode]>; // This no longer works +class Parent extends schemaFactory.object("Parent", { + mode: typedObjectValues(Mode), +}) {} // This no longer works +``` + +#### Change details + +Commit: [`645b9ed`](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### The strictness of input tree types when inexact schemas are provided has been improved ([#22874](https://github.com/microsoft/FluidFramework/issues/22874)) + +Consider the following code where the type of the schema is not exactly specified: + +```typescript +const schemaFactory = new SchemaFactory("com.myApp"); +class A extends schemaFactory.object("A", {}) {} +class B extends schemaFactory.array("B", schemaFactory.number) {} + +// Gives imprecise type (typeof A | typeof B)[]. The desired precise type here is [typeof A, typeof B]. +const schema = [A, B]; + +const config = new TreeViewConfiguration({ schema }); +const view = sharedTree.viewWith(config); + +// Does not compile since setter for root is typed `never` due to imprecise schema. +view.root = []; +``` + +The assignment of `view.root` is disallowed since a schema with type `(typeof A | typeof B)[]` could be any of: + +```typescript +const schema: (typeof A | typeof B)[] = [A]; +``` + +```typescript +const schema: (typeof A | typeof B)[] = [B]; +``` + +```typescript +const schema: (typeof A | typeof B)[] = [A, B]; +``` + +The attempted assignment is not compatible with all of these (specifically it is incompatible with the first one) so performing this assignment could make the tree out of schema and is thus disallowed. + +To avoid this ambiguity and capture the precise type of `[typeof A, typeof B]`, use one of the following patterns: + +```typescript +const schema = [A, B] as const; +const config = new TreeViewConfiguration({ schema }); +``` + +```typescript +const config = new TreeViewConfiguration({ schema: [A, B] }); +``` + +To help update existing code which accidentally depended on this bug, an `@alpha` API `unsafeArrayToTuple` has been added. Many usages of this API will produce incorrectly typed outputs. However, when given `AllowedTypes` arrays which should not contain any unions, but that were accidentally flattened to a single union, it can fix them: + +```typescript +// Gives imprecise type (typeof A | typeof B)[] +const schemaBad = [A, B]; +// Fixes the type to be [typeof A, typeof B] +const schema = unsafeArrayToTuple(schemaBad); + +const config = new TreeViewConfiguration({ schema }); +``` + +#### Change details + +Commit: [`645b9ed`](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### TreeNodeSchemaClass now specifies its TNode as TreeNode ([#22938](https://github.com/microsoft/FluidFramework/issues/22938)) + +`TreeNodeSchemaClass`'s `TNode` parameter was formerly `unknown` and has been improved to be the more specific `TreeNode | TreeLeafValue`. This change further narrows this to `TreeNode`. + +`TreeNodeSchema`, which is more commonly used, still permits `TNode` of `TreeNode | TreeLeafValue`, so this change should have little impact on most code, but in some edge cases it can result in slightly more specific typing. + +#### Change details + +Commit: [`b669a6e`](https://github.com/microsoft/FluidFramework/commit/b669a6efdba685c71897cade4f907304f1a73910) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### Array and Map nodes can now be explicitly constructed with undefined or no argument ([#22946](https://github.com/microsoft/FluidFramework/issues/22946)) + +The input parameter to the constructor and `create` methods of Array and Map nodes is now optional. When the optional parameter is omitted, an empty map or array will be created. + +#### Examples + +```typescript +class Schema extends schemaFactory.array("x", schemaFactory.number) {} + +// Existing support +const _fromIterable: Schema = new Schema([]); + +// New +const _fromUndefined: Schema = new Schema(undefined); +const _fromNothing: Schema = new Schema(); +``` + +```typescript +class Schema extends schemaFactory.map("x", schemaFactory.number) {} + +// Existing support +const _fromIterable: Schema = new Schema([]); +const _fromObject: Schema = new Schema({}); + +// New +const _fromUndefined: Schema = new Schema(undefined); +const _fromNothing: Schema = new Schema(); +``` + +```typescript +const Schema = schemaFactory.array(schemaFactory.number); +type Schema = NodeFromSchema; + +// Existing support +const _fromIterable: Schema = Schema.create([]); + +// New +const _fromUndefined: Schema = Schema.create(undefined); +const _fromNothing: Schema = Schema.create(); +``` + +```typescript +const Schema = schemaFactory.map(schemaFactory.number); +type Schema = NodeFromSchema; +// Existing support +const _fromIterable: Schema = Schema.create([]); +const _fromObject: Schema = Schema.create({}); + +// New +const _fromUndefined: Schema = Schema.create(undefined); +const _fromNothing: Schema = Schema.create(); +``` + +#### Change details + +Commit: [`176335c`](https://github.com/microsoft/FluidFramework/commit/176335ce88d005159819c559b445a1655ec429d5) + +Affected packages: + +- fluid-framework +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +### SharedTree branching API has been improved ([#22970](https://github.com/microsoft/FluidFramework/issues/22970)) + +The alpha SharedTree branching API has been updated to be more accessible and intuitive. The branching functions (`branch`, `merge`, `rebaseOnto`, etc.) are now directly available on the view object rather than a separate object. In particular, `TreeViewAlpha` is now a `TreeBranch`, which exposes the methods to coordinate branches. + +The existing `TreeBranch` type has been renamed to `BranchableTree` and is now **deprecated**. + +See the `TreeBranch` interface for more details. + +The new API is used e.g. as follows: + +```typescript +const sf = new SchemaFactory("example"); +class StringArray extends sf.array("StringArray", sf.string) {} + +function example(view: TreeViewAlpha): void { + // Create a branch + const branch = view.fork(); + // Modify the branch rather than the main view + branch.root.insertAtEnd("new string"); + // `view` does not yet contain "new string" + // ... + // Later, merge the branch into the main view + view.merge(branch); + // `view` now contains "new string" +} +``` + +Here is the equivalent behavior with the previous API, for reference: + +```typescript +const sf = new SchemaFactory("example"); +class StringArray extends sf.array("StringArray", sf.string) {} + +function example(view: TreeViewAlpha): void { + // Get the branch for the view + const branch = getBranch(view); + const fork = branch.branch(); + // Modify the branch rather than the main view + fork.root.insertAtEnd("new string"); + // `view` does not yet contain "new string" + // ... + // Later, merge the branch into the main view + branch.merge(fork); + // `view` now contains "new string" +} +``` + +Additionally, there is a new API to acquire the branch from a node: + +```typescript +// All nodes that have been inserted into the tree belong to a branch - this retrieves that branch +const branch = TreeAlpha.branch(node); +``` + +To convert the branch object to a view with a known schema, use: + +```typescript +if (branch.hasRootSchema(MySchema)) { + const view = branch; // `branch` is now typed as a `TreeViewAlpha` +} +``` + +Use the following function to expose the alpha APIs on a `TreeView` that is not typed as a `TreeViewAlpha`: + +```typescript +const viewAlpha = asTreeViewAlpha(view); +``` + +#### Change details + +Commit: [`80ed028`](https://github.com/microsoft/FluidFramework/commit/80ed0284f01107d2ba8bcf2f3ebaf6175367603a) + +Affected packages: + +- @fluidframework/tree + +[⬆️ Table of contents](#contents) + +## 🐛 Bug Fixes + +### Compilation no longer fails when building with TypeScript's libCheck option ([#22923](https://github.com/microsoft/FluidFramework/issues/22923)) + +When compiling code using Fluid Framework with TypeScript's `libCheck` (meaning without [skipLibCheck](https://www.typescriptlang.org/tsconfig/#skipLibCheck)), two compile errors can be encountered: + +``` +> tsc + +node_modules/@fluidframework/merge-tree/lib/client.d.ts:124:18 - error TS2368: Type parameter name cannot be 'undefined'. + +124 walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; + ~~~~~~~~~ + +node_modules/@fluidframework/tree/lib/util/utils.d.ts:5:29 - error TS7016: Could not find a declaration file for module '@ungap/structured-clone'. 'node_modules/@ungap/structured-clone/esm/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/ungap__structured-clone` if it exists or add a new declaration (.d.ts) file containing `declare module '@ungap/structured-clone';` + +5 import structuredClone from "@ungap/structured-clone"; + ~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +The first error impacts projects using TypeScript 5.5 or greater and either of the `fluid-framework` or `@fluidframework/merge-tree` packages. The second error impacts projects using the `noImplicitAny` tsconfig setting and the `fluid-framework` or `@fluidframework/tree` packages. + +Both errors have been fixed. + +This should allow `libCheck` to be reenabled in any impacted projects. + +#### Change details + +Commit: [`a1b4cdd`](https://github.com/microsoft/FluidFramework/commit/a1b4cdd45ee9812e2598ab8d2854333d26a06eb4) + +Affected packages: + +- @fluidframework/merge-tree +- @fluidframework/tree +- fluid-framework + +[⬆️ Table of contents](#contents) + +## ⚠️ Deprecations + +### The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated ([#22840](https://github.com/microsoft/FluidFramework/issues/22840)) + +The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated. It has been replaced with a new function `processMessages`, which will be called to process multiple messages instead of a single one on the channel. This is part of a feature called "Op bunching", where contiguous ops of a given type and to a given data store / DDS are bunched and sent together for processing. + +Implementations of `IFluidDataStoreChannel` and `IDeltaHandler` must now also implement `processMessages`. For reference implementations, see `FluidDataStoreRuntime::processMessages` and `SharedObjectCore::attachDeltaHandler`. + +#### Change details + +Commit: [`2e5b969`](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + +Affected packages: + +- @fluidframework/datastore-definitions +- @fluidframework/runtime-definitions +- @fluidframework/test-runtime-utils + +[⬆️ Table of contents](#contents) + +## Other Changes + +### Signal telemetry events details ([#22804](https://github.com/microsoft/FluidFramework/issues/22804)) + +Properties of `eventName`s beginning "fluid:telemetry:ContainerRuntime:Signal" are updated to use `details` for all event specific information. Additional per-event changes: + +- SignalLatency: shorten names now that data is packed into details. Renames: + - `signalsSent` -> `sent` + - `signalsLost` -> `lost` + - `outOfOrderSignals` -> `outOfOrder` +- SignalLost/SignalOutOfOrder: rename `trackingSequenceNumber` to `expectedSequenceNumber` +- SignalOutOfOrder: rename `type` to `contentsType` and only emit it some of the time + +> \[!IMPORTANT] +> +> Reminder: the naming and structure of telemetry events are not considered a part of the public API and may change at any time. + +#### Change details + +Commit: [`e6566f6`](https://github.com/microsoft/FluidFramework/commit/e6566f6358551b5e579637de6c111d42281f7716) + +Affected packages: + +- @fluidframework/container-runtime + +[⬆️ Table of contents](#contents) + +### The op event on IFluidDataStoreRuntimeEvents and IContainerRuntimeBaseEvents is emitted at a different time ([#22840](https://github.com/microsoft/FluidFramework/issues/22840)) + +Previously, in versions 2.4 and below, the `op` event was emitted immediately after an op was processed and before the next op was processed. + +In versions 2.5.0 and beyond, the `op` event will be emitted after an op is processed, but it may not be immediate. In addition, other ops in a batch may be processed before the op event is emitted for a particular op. + +#### Change details + +Commit: [`2e5b969`](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + +Affected packages: + +- @fluidframework/datastore-definitions +- @fluidframework/runtime-definitions + +[⬆️ Table of contents](#contents) + +### 🛠️ Start Building Today! + +Please continue to engage with us on GitHub [Discussion](https://github.com/microsoft/FluidFramework/discussions) and [Issue](https://github.com/microsoft/FluidFramework/issues) pages as you adopt Fluid Framework! diff --git a/_repoLayout.config.cjs b/_buildProject.config.cjs similarity index 98% rename from _repoLayout.config.cjs rename to _buildProject.config.cjs index 3b521bb44787..4d4db2ef08f7 100644 --- a/_repoLayout.config.cjs +++ b/_buildProject.config.cjs @@ -26,11 +26,11 @@ const fluidScopes = [ /** * The settings in this file configure the repo layout used by build-tools, such as fluid-build and flub. * - * @type {import("@fluid-tools/build-infrastructure").IFluidRepoLayout} + * @type {import("@fluid-tools/build-infrastructure").IBuildProjectConfig} */ module.exports = { version: 1, - repoLayout: { + buildProject: { workspaces: { "client": { directory: ".", diff --git a/azure/packages/azure-local-service/CHANGELOG.md b/azure/packages/azure-local-service/CHANGELOG.md index 5ee355a32dcf..34816bb686af 100644 --- a/azure/packages/azure-local-service/CHANGELOG.md +++ b/azure/packages/azure-local-service/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/azure-local-service +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/azure/packages/azure-local-service/package.json b/azure/packages/azure-local-service/package.json index 73e02d407cf3..3c908f40c6e9 100644 --- a/azure/packages/azure-local-service/package.json +++ b/azure/packages/azure-local-service/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-local-service", - "version": "2.5.0", + "version": "2.10.0", "description": "Local implementation of the Azure Fluid Relay service for testing/development use", "homepage": "https://fluidframework.com", "repository": { @@ -38,7 +38,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "eslint": "~8.55.0", "eslint-config-prettier": "~9.0.0", diff --git a/azure/packages/azure-service-utils/CHANGELOG.md b/azure/packages/azure-service-utils/CHANGELOG.md index 007b2a65c0d8..ccb2d4d99a93 100644 --- a/azure/packages/azure-service-utils/CHANGELOG.md +++ b/azure/packages/azure-service-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/azure-service-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/azure/packages/azure-service-utils/package.json b/azure/packages/azure-service-utils/package.json index 32291b6e65c3..cb13a01c02e2 100644 --- a/azure/packages/azure-service-utils/package.json +++ b/azure/packages/azure-service-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-service-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Helper service-side utilities for connecting to Azure Fluid Relay service", "homepage": "https://fluidframework.com", "repository": { @@ -96,10 +96,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", - "@fluidframework/azure-service-utils-previous": "npm:@fluidframework/azure-service-utils@~2.4.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluidframework/azure-service-utils-previous": "npm:@fluidframework/azure-service-utils@~2.5.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/jsrsasign": "^10.5.12", diff --git a/azure/packages/test/scenario-runner/.eslintrc.cjs b/azure/packages/test/scenario-runner/.eslintrc.cjs index 19ee963bd5c6..e763cfe15d7b 100644 --- a/azure/packages/test/scenario-runner/.eslintrc.cjs +++ b/azure/packages/test/scenario-runner/.eslintrc.cjs @@ -12,6 +12,7 @@ module.exports = { "prefer-arrow-callback": "off", "@typescript-eslint/strict-boolean-expressions": "off", // requires strictNullChecks=true in tsconfig "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], diff --git a/azure/packages/test/scenario-runner/CHANGELOG.md b/azure/packages/test/scenario-runner/CHANGELOG.md index 5d36beedda74..0132dcd1ee39 100644 --- a/azure/packages/test/scenario-runner/CHANGELOG.md +++ b/azure/packages/test/scenario-runner/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/azure-scenario-runner +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/azure/packages/test/scenario-runner/package.json b/azure/packages/test/scenario-runner/package.json index e3d563c9e0c4..605c6acc3a99 100644 --- a/azure/packages/test/scenario-runner/package.json +++ b/azure/packages/test/scenario-runner/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/azure-scenario-runner", - "version": "2.5.0", + "version": "2.10.0", "description": "Azure client end to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -93,9 +93,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/js-yaml": "^4.0.5", "@types/mocha": "^9.1.1", diff --git a/azure/packages/test/scenario-runner/src/packageVersion.ts b/azure/packages/test/scenario-runner/src/packageVersion.ts index 6fc1e49e30f7..1e57dee7cab3 100644 --- a/azure/packages/test/scenario-runner/src/packageVersion.ts +++ b/azure/packages/test/scenario-runner/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/azure-scenario-runner"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/biome.jsonc b/biome.jsonc index 154bff87904f..e59b39cb4091 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -58,6 +58,8 @@ "packages/tools/fluid-runner/src/test/localOdspSnapshots/**", "packages/tools/fluid-runner/src/test/telemetryExpectedOutputs/**", "tools/api-markdown-documenter/src/test/snapshots/**", + // TODO: why does examples/apps/tree-cli-app/*.json not work? + "**/data/*.json", // Generated type-tests "**/*.generated.ts", diff --git a/build-tools/CHANGELOG.md b/build-tools/CHANGELOG.md index 2bdf22d63b06..a0bff2ec4c23 100644 --- a/build-tools/CHANGELOG.md +++ b/build-tools/CHANGELOG.md @@ -1,3 +1,679 @@ +## [0.50.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.49.0...build-tools_v0.50.0) (2024-11-04) + + +### Features + +* **build-infrastructure:** Add default implementations for core interfaces ([#22865](https://github.com/microsoft/FluidFramework/issues/22865)) ([199b9d0](https://github.com/microsoft/FluidFramework/commit/199b9d051239d8dba8215153e565c2cadbc1ecef)) +* **build-tools:** Add build-infrastructure package ([#22853](https://github.com/microsoft/FluidFramework/issues/22853)) ([b8e887e](https://github.com/microsoft/FluidFramework/commit/b8e887ead4e4a10c537e12fbe43d66ea83f7e25a)) +* **build-tools:** Add generate:node10Entrypoints command ([#22937](https://github.com/microsoft/FluidFramework/issues/22937)) ([533de79](https://github.com/microsoft/FluidFramework/commit/533de791802eaa0eb0b55f8a222d38e9a0822741)) + + +### Bug Fixes + +* **build-tools:** Run install with `--no-frozen-lockfile` ([#22814](https://github.com/microsoft/FluidFramework/issues/22814)) ([0334d00](https://github.com/microsoft/FluidFramework/commit/0334d003b0e4876e4d3925002c863eeaa78177fb)) +* **fluid-build:** Load default config when no config is found ([#22825](https://github.com/microsoft/FluidFramework/issues/22825)) ([8884365](https://github.com/microsoft/FluidFramework/commit/88843657f4be9fa1fa4fe5e0370b6f120ee3b090)) +* **generate:changelog:** Calculate correct changeset version ([#22796](https://github.com/microsoft/FluidFramework/issues/22796)) ([91ace91](https://github.com/microsoft/FluidFramework/commit/91ace91767a56814ac51411eeb6d051c111adb20)) +* **release:** Check release notes and changelog generation in release tool ([#22811](https://github.com/microsoft/FluidFramework/issues/22811)) ([2d98e6c](https://github.com/microsoft/FluidFramework/commit/2d98e6cc681ac39786510e659f62fa7606a1edff)) +* Update transitive dependencies on `braces` to address CVE ([#22768](https://github.com/microsoft/FluidFramework/issues/22768)) ([4228a21](https://github.com/microsoft/FluidFramework/commit/4228a21d96a141f43dc24c74e22ca49cd8e14407)) + + +### Build System + +* **build-tools:** Upgrade danger to 12.x ([#22904](https://github.com/microsoft/FluidFramework/issues/22904)) ([0ec024d](https://github.com/microsoft/FluidFramework/commit/0ec024d3669adc4d75afd24daae9017593153db2)) + +## [0.49.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.48.0...build-tools_v0.49.0) (2024-10-14) + + +### Bug Fixes + +* **generate:changelog:** Calculate correct changeset version ([#22796](https://github.com/microsoft/FluidFramework/issues/22796)) ([91ace91](https://github.com/microsoft/FluidFramework/commit/91ace91767a56814ac51411eeb6d051c111adb20)) +* Update transitive dependencies on `braces` to address CVE ([#22768](https://github.com/microsoft/FluidFramework/issues/22768)) ([4228a21](https://github.com/microsoft/FluidFramework/commit/4228a21d96a141f43dc24c74e22ca49cd8e14407)) + +## [0.48.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.47.0...build-tools_v0.48.0) (2024-10-08) + + +### Features + +* **fluid-build:** Add support for declarative tasks ([#22663](https://github.com/microsoft/FluidFramework/issues/22663)) ([082c72d](https://github.com/microsoft/FluidFramework/commit/082c72d4162619e8438e8adb42e07af1ce7f10eb)) + +## [0.47.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.46.0...build-tools_v0.47.0) (2024-10-04) + + +### Features + +* **build-cli:** add check latestVersions command to build-tools ([#22252](https://github.com/microsoft/FluidFramework/issues/22252)) ([fc486fe](https://github.com/microsoft/FluidFramework/commit/fc486fe352b7f0fb192fcf1602160ee89c1248fd)) +* **build-cli:** New command transform:releaseNotes ([#22466](https://github.com/microsoft/FluidFramework/issues/22466)) ([d2995da](https://github.com/microsoft/FluidFramework/commit/d2995daf42160d6bff68b53dc228b719d357d274)) + + +### Bug Fixes + +* **build-cli:** Load interdependency ranges from fluid-build config for back-compat ([#22628](https://github.com/microsoft/FluidFramework/issues/22628)) ([ad79bf6](https://github.com/microsoft/FluidFramework/commit/ad79bf6e63e4da44ee22625021f7ddacf6c4f3d3)), closes [#22630](https://github.com/microsoft/FluidFramework/issues/22630) [#21967](https://github.com/microsoft/FluidFramework/issues/21967) [#21967](https://github.com/microsoft/FluidFramework/issues/21967) [0#diff-9a0994a9d2ddb86f6f1e53fef4e8de22cf87ced6dd81bd0e8866784c2679f450L330](https://github.com/microsoft/0/issues/diff-9a0994a9d2ddb86f6f1e53fef4e8de22cf87ced6dd81bd0e8866784c2679f450L330) +* **build-tools:** correct deleted file status ([#22586](https://github.com/microsoft/FluidFramework/issues/22586)) ([40630fa](https://github.com/microsoft/FluidFramework/commit/40630fadb082f539819f4a702457c0926930d5b5)), closes [AB#6588](https://github.com/microsoft/AB/issues/6588) +* **build-tools:** use JSON5 to allow comments ([#22498](https://github.com/microsoft/FluidFramework/issues/22498)) ([11fe079](https://github.com/microsoft/FluidFramework/commit/11fe079b2a355ea9692d87e0565f645294e46bf3)) +* **fluid-build:** Sort donefile file hashes deterministically ([#22665](https://github.com/microsoft/FluidFramework/issues/22665)) ([a9a07c6](https://github.com/microsoft/FluidFramework/commit/a9a07c66c9d920dadaba26b32884ae830c6a4ec2)), closes [#22663](https://github.com/microsoft/FluidFramework/issues/22663) [#22663](https://github.com/microsoft/FluidFramework/issues/22663) + +## [0.46.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.45.0...build-tools_v0.46.0) (2024-09-13) + + +### Bug Fixes + +* **generate:typetests:** Resolve symlinks to previous versions ([#22494](https://github.com/microsoft/FluidFramework/issues/22494)) ([90991c9](https://github.com/microsoft/FluidFramework/commit/90991c938b670b1ab219b36380ecf211e09087aa)) + +## [0.45.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.44.0...build-tools_v0.45.0) (2024-09-11) + + +### Features + +* **build-cli:** New command check:prApproval ([#22302](https://github.com/microsoft/FluidFramework/issues/22302)) ([497501c](https://github.com/microsoft/FluidFramework/commit/497501c70366de78cfa79d0d560faaad3acd4b95)), closes [AB#8814](https://github.com/microsoft/AB/issues/8814) +* **build-cli:** New promote:package command ([#22305](https://github.com/microsoft/FluidFramework/issues/22305)) ([d545dd8](https://github.com/microsoft/FluidFramework/commit/d545dd8f71920e7f6a69145e5a37545020a7f0ef)) +* **build-cli:** New re-usable flag for parsing version strings ([#22360](https://github.com/microsoft/FluidFramework/issues/22360)) ([0309a04](https://github.com/microsoft/FluidFramework/commit/0309a04ef6ec2c263283f54e2e969215b7899848)) +* **generate:releaseNotes:** Add inline links to headings ([#22415](https://github.com/microsoft/FluidFramework/issues/22415)) ([1a4b95f](https://github.com/microsoft/FluidFramework/commit/1a4b95f7bfdba30e3b32b6efb0c89298dc6d30ed)), closes [AB#14174](https://github.com/microsoft/AB/issues/14174) +* **generate:typetests:** Add per-package typetest entrypoint config ([#22131](https://github.com/microsoft/FluidFramework/issues/22131)) ([e23e509](https://github.com/microsoft/FluidFramework/commit/e23e509d58a6a69be5f8b811a290ff3cea4fc56b)), closes [AB#7875](https://github.com/microsoft/AB/issues/7875) + + +### Bug Fixes + +* **build-tools:** restore support for older git versions ([#22437](https://github.com/microsoft/FluidFramework/issues/22437)) ([1eee5d8](https://github.com/microsoft/FluidFramework/commit/1eee5d854dad73bad01740347e1b54548a54439b)), closes [AB#14894](https://github.com/microsoft/AB/issues/14894) +* **generate:changelog:** Strip additional metadata when loading changesets for changelogs ([#22431](https://github.com/microsoft/FluidFramework/issues/22431)) ([7a1f667](https://github.com/microsoft/FluidFramework/commit/7a1f66746f45fca94c79e74333e53649ea785b47)), closes [AB#14171](https://github.com/microsoft/AB/issues/14171) +* **generate:releaseNotes:** Fix broken TOC links in release notes ([#22464](https://github.com/microsoft/FluidFramework/issues/22464)) ([37bd359](https://github.com/microsoft/FluidFramework/commit/37bd3597965744b174c043e6eec8d74300b80ff9)) +* **generate:typetests:** Move type compat exports back to build-tools ([#22443](https://github.com/microsoft/FluidFramework/issues/22443)) ([3303736](https://github.com/microsoft/FluidFramework/commit/33037369892717a717e68dc29f412741283fbdf9)), closes [#22347](https://github.com/microsoft/FluidFramework/issues/22347) + +## [0.44.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.43.0...build-tools_v0.44.0) (2024-08-22) + + +### Bug Fixes + +* **build-tools:** Filter out empty responses from git ls-files ([#22247](https://github.com/microsoft/FluidFramework/issues/22247)) ([6f4b15d](https://github.com/microsoft/FluidFramework/commit/6f4b15d5c16b6689af8c1ff2d71cef33b2ada738)), closes [#22226](https://github.com/microsoft/FluidFramework/issues/22226) +* **bump:deps:** Use 'dev' dist-tag instead of 'next' ([#22266](https://github.com/microsoft/FluidFramework/issues/22266)) ([1bc4134](https://github.com/microsoft/FluidFramework/commit/1bc4134643ca7c9bf6a712106824f1211a1b493d)) + +## [0.43.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.42.0...build-tools_v0.43.0) (2024-08-16) + + +### Features + +* **build-cli:** New command generate:releaseNotes ([#21951](https://github.com/microsoft/FluidFramework/issues/21951)) ([f85a3d2](https://github.com/microsoft/FluidFramework/commit/f85a3d2e1e4f599194cb85c62ab24f029dcd34b7)) + + +### Bug Fixes + +* **build-cli:** Fix broken release history command ([#22086](https://github.com/microsoft/FluidFramework/issues/22086)) ([8f6428f](https://github.com/microsoft/FluidFramework/commit/8f6428fff55ab66d11c3e9e41e511eed306d8202)) +* **fluid-build:** Fix failures when deleting files ([#22226](https://github.com/microsoft/FluidFramework/issues/22226)) ([610658c](https://github.com/microsoft/FluidFramework/commit/610658c78fe940e1b9485edb12d68c2635818923)), closes [AB#10257](https://github.com/microsoft/AB/issues/10257) [AB#6588](https://github.com/microsoft/AB/issues/6588) + +## [0.42.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.41.0...build-tools_v0.42.0) (2024-07-30) + + +### Bug Fixes + +* **fluid-build:** Fix task caching for flub list tasks ([#21989](https://github.com/microsoft/FluidFramework/issues/21989)) ([b42e663](https://github.com/microsoft/FluidFramework/commit/b42e663f3434683cf8ecf00a15ff819398dd7ba9)), closes [AB#9075](https://github.com/microsoft/AB/issues/9075) +* **version-tools:** Correctly identify test version strings ([#22030](https://github.com/microsoft/FluidFramework/issues/22030)) ([5797dc1](https://github.com/microsoft/FluidFramework/commit/5797dc110621b51767549aac34873520d24601e1)) + +## [0.41.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.40.0...build-tools_v0.41.0) (2024-07-17) + + +### ⚠ BREAKING CHANGES + +* **typetests:** Many typetest changes ([#21876](https://github.com/microsoft/FluidFramework/issues/21876)) ([115c8f4](https://github.com/microsoft/FluidFramework/commit/115c8f4e5c0b9dad79bd3932c417e865784affbc)) + +## Breaking Changes + +Type tests now catch changes to class statics, and broken annotation in +package.json need to have the "Declaration" removed from the names. + +### Features + +* **build-tools:** include peers in `combinedDependencies` ([#21796](https://github.com/microsoft/FluidFramework/issues/21796)) ([d5f7159](https://github.com/microsoft/FluidFramework/commit/d5f71599cfae2df16493f8e05a1abe0cdbf6fc1a)) + + +### Bug Fixes + +* **build-tools:** npm-package-json-scripts-dep alias support ([#21883](https://github.com/microsoft/FluidFramework/issues/21883)) ([33fd5da](https://github.com/microsoft/FluidFramework/commit/33fd5da5b122c7ab81b351c1be388f5934750c95)) + + +* Type test improvements (#21876) ([115c8f4](https://github.com/microsoft/FluidFramework/commit/115c8f4e5c0b9dad79bd3932c417e865784affbc)), closes [#21876](https://github.com/microsoft/FluidFramework/issues/21876) + +## [0.40.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.39.0...build-tools_v0.40.0) (2024-07-03) + + +### Features + +* **build-cli:** Add release:prepare command ([#16686](https://github.com/microsoft/FluidFramework/issues/16686)) ([125d1a2](https://github.com/microsoft/FluidFramework/commit/125d1a2d8b5534ab2993e274d4c7c36fe69971d9)) +* **check:policy:** Add policy to validate repository.directory field in package.json ([#21605](https://github.com/microsoft/FluidFramework/issues/21605)) ([2b2a2f2](https://github.com/microsoft/FluidFramework/commit/2b2a2f2872ef8fb7a30fcde63daf595c466506ef)), closes [#21689](https://github.com/microsoft/FluidFramework/issues/21689) +* **check:policy:** Policy handler to prevent tab indentation in yml files ([#21626](https://github.com/microsoft/FluidFramework/issues/21626)) ([6e13c15](https://github.com/microsoft/FluidFramework/commit/6e13c15ef853ee9b02e75a80e5f6c6ca8e82bee3)) + + +### Bug Fixes + +* **build-cli:** Fix broken filter test ([#21616](https://github.com/microsoft/FluidFramework/issues/21616)) ([d2ba7eb](https://github.com/microsoft/FluidFramework/commit/d2ba7eb2b4175230954f52393240e6667064df98)), closes [#21393](https://github.com/microsoft/FluidFramework/issues/21393) +* **build-cli:** Use release group/package name in all branch names ([#21644](https://github.com/microsoft/FluidFramework/issues/21644)) ([4dd2d49](https://github.com/microsoft/FluidFramework/commit/4dd2d49c53fb1c0c86149860e24ff7404364f3ab)) +* **build-tools:** correct tool ref ([#21763](https://github.com/microsoft/FluidFramework/issues/21763)) ([dc16a91](https://github.com/microsoft/FluidFramework/commit/dc16a91cff9d39c46cc25ffc1fe29c26fa02f66b)) +* **client:** Correct repository.directory field ([#21689](https://github.com/microsoft/FluidFramework/issues/21689)) ([357d30e](https://github.com/microsoft/FluidFramework/commit/357d30e1f20caa75502528f904b60766e44b73fe)), closes [#21605](https://github.com/microsoft/FluidFramework/issues/21605) +* **fluid-tsc:** Make --build fluid-tsc command not fail with no output ([#21734](https://github.com/microsoft/FluidFramework/issues/21734)) ([8b542d3](https://github.com/microsoft/FluidFramework/commit/8b542d3f5f07731c701a38a0eb43d77ed9120d13)) +* Update transitive dependency on socks to address CVE ([#21367](https://github.com/microsoft/FluidFramework/issues/21367)) ([7abbfac](https://github.com/microsoft/FluidFramework/commit/7abbfac4f060e788555d939a0d3520bc75d32b59)) +* **version-tools:** Fix comparison of 2.0 releases to RC builds ([#21641](https://github.com/microsoft/FluidFramework/issues/21641)) ([4fb764f](https://github.com/microsoft/FluidFramework/commit/4fb764fc0ea7d84297f7ea74a2a8fb1a5da84457)) +* **version-tools:** Fix scheme detection and add more test cases ([#21710](https://github.com/microsoft/FluidFramework/issues/21710)) ([6bc082b](https://github.com/microsoft/FluidFramework/commit/6bc082b755f790c483fc3d1ab1035d54b8cc889f)) + +## [0.39.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.38.0...build-tools_v0.39.0) (2024-06-06) + + +### Features + +* **build-cli:** Add support for selecting only changed packages ([#18028](https://github.com/microsoft/FluidFramework/issues/18028)) ([42e01d0](https://github.com/microsoft/FluidFramework/commit/42e01d0f9b0f78d0fb8ab337366a9c05b5d054c8)) +* **build-tools:** alternate tag export support ([#21276](https://github.com/microsoft/FluidFramework/issues/21276)) ([889153e](https://github.com/microsoft/FluidFramework/commit/889153ed49c5cb55555722a838e6931972030dbc)) +* **build-tools:** New command 'publish:tarballs' ([#20934](https://github.com/microsoft/FluidFramework/issues/20934)) ([6fba3ec](https://github.com/microsoft/FluidFramework/commit/6fba3ecb3daf84f986d1f2fcf5ba3128eda948fe)) +* **build-tools:** policy for entrypoint linting ([#21240](https://github.com/microsoft/FluidFramework/issues/21240)) ([bf58e43](https://github.com/microsoft/FluidFramework/commit/bf58e4356bfdf462f447f05b65305b2fc61559c2)), closes [AB#8141](https://github.com/microsoft/AB/issues/8141) +* **build-tools:** wildcard 'concurrently' task support ([#21262](https://github.com/microsoft/FluidFramework/issues/21262)) ([b3c1d66](https://github.com/microsoft/FluidFramework/commit/b3c1d66307d16670b7313eb9013f14084ef13964)) + + +### Bug Fixes + +* **build-tools:** fluid-build-tasks-eslint deps ([#21089](https://github.com/microsoft/FluidFramework/issues/21089)) ([b85c77e](https://github.com/microsoft/FluidFramework/commit/b85c77e8775cb839d22dd478bddf02513edf9434)) +* **build-tools:** gen type tests respecting import order ([#21273](https://github.com/microsoft/FluidFramework/issues/21273)) ([e04b7d8](https://github.com/microsoft/FluidFramework/commit/e04b7d8f0e3c8e6ab123027527b07aeb021e3af0)) +* **build-tools:** ignore cross group deps for policy ([#21238](https://github.com/microsoft/FluidFramework/issues/21238)) ([d6ed4c6](https://github.com/microsoft/FluidFramework/commit/d6ed4c6ad5d4f91b204205a3e638a65f4d7ea14c)) +* **build-tools:** restore some tsc dep checking ([#20971](https://github.com/microsoft/FluidFramework/issues/20971)) ([05b3ebc](https://github.com/microsoft/FluidFramework/commit/05b3ebc2a20c55f7517291da41fd553b737f01b2)) +* **build-tools:** run policy handlers before resolvers ([#21249](https://github.com/microsoft/FluidFramework/issues/21249)) ([d0f4247](https://github.com/microsoft/FluidFramework/commit/d0f4247d17470c04d9d23f2b2491a69a8082c983)) +* **build-tools:** type test incremental build ([#20986](https://github.com/microsoft/FluidFramework/issues/20986)) ([1dba9ca](https://github.com/microsoft/FluidFramework/commit/1dba9ca11b36d14e4a330214d098450109eeb9f8)) +* **check:policy:** Use `createRequire` and `require` to import CommonJS configs ([#21250](https://github.com/microsoft/FluidFramework/issues/21250)) ([4d3db78](https://github.com/microsoft/FluidFramework/commit/4d3db7812ad76f208b8948e86a1e852f12a5540d)), closes [/github.com/microsoft/FluidFramework/pull/21250/files#r1617995976](https://github.com/microsoft//github.com/microsoft/FluidFramework/pull/21250/files/issues/r1617995976) +* **generate:typetests:** Use types/typings field only for public exports when exports map is not defined ([#20989](https://github.com/microsoft/FluidFramework/issues/20989)) ([087e485](https://github.com/microsoft/FluidFramework/commit/087e4854aa12c2c4245c24a1f5ea793c1c093cb9)) + +## [0.38.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.37.0...build-tools_v0.38.0) (2024-05-03) + + +### Features + +* **build-cli:** Add generate:packlist command ([#20723](https://github.com/microsoft/FluidFramework/issues/20723)) ([cb2a6f7](https://github.com/microsoft/FluidFramework/commit/cb2a6f73ca49c9464ec9cdf1628b9e33c633bd37)) +* **build-cli:** Add generate:typetests command ([#20803](https://github.com/microsoft/FluidFramework/issues/20803)) ([feea9e2](https://github.com/microsoft/FluidFramework/commit/feea9e24c40e6023cde36efa24b22475563e6710)), closes [#18700](https://github.com/microsoft/FluidFramework/issues/18700) +* **build-cli:** Allow commands to select all packages by default ([#16544](https://github.com/microsoft/FluidFramework/issues/16544)) ([c956045](https://github.com/microsoft/FluidFramework/commit/c956045abee8dbe4e4f006f30288daca8770cf83)) +* **build-cli:** New command flub modify lockfile ([#20751](https://github.com/microsoft/FluidFramework/issues/20751)) ([b006336](https://github.com/microsoft/FluidFramework/commit/b00633608d75d8e2bd2ee5608ed8d4860e6f2929)) +* **build-tools:** `modify fluid-imports` /legacy support ([#20672](https://github.com/microsoft/FluidFramework/issues/20672)) ([aad5ebb](https://github.com/microsoft/FluidFramework/commit/aad5ebbea82dd4dc9823a3d235852c35e23fc151)) +* **build-tools:** add --watch support to fluid-tsc ([#20947](https://github.com/microsoft/FluidFramework/issues/20947)) ([95e6050](https://github.com/microsoft/FluidFramework/commit/95e605023303bc53cf8afffa0c1ea5e6645f5f3c)) +* **flub:** Add 'Path' to info and --columns filtering ([#20926](https://github.com/microsoft/FluidFramework/issues/20926)) ([096ef79](https://github.com/microsoft/FluidFramework/commit/096ef79d855645fc57c4bb56c21116c51523c47c)) + + +### Bug Fixes + +* **build-cli:** Correct filter flag descriptions ([#20826](https://github.com/microsoft/FluidFramework/issues/20826)) ([2bc2276](https://github.com/microsoft/FluidFramework/commit/2bc22767a8fa6cf834da9081f5c4d662be18e836)) +* **build-cli:** PackageCommands should error if any of the child processes fail ([#20878](https://github.com/microsoft/FluidFramework/issues/20878)) ([3ad4ee1](https://github.com/microsoft/FluidFramework/commit/3ad4ee12a4a067d5d02f4adc23d02800474726db)) +* **build-tools:** Add script to bin/ with shebang for fluid-tsc ([#20714](https://github.com/microsoft/FluidFramework/issues/20714)) ([17570a4](https://github.com/microsoft/FluidFramework/commit/17570a472184e0874abf49437e79810414c7670f)) +* **build-tools:** Update broken tests ([#20700](https://github.com/microsoft/FluidFramework/issues/20700)) ([5aea5e7](https://github.com/microsoft/FluidFramework/commit/5aea5e7418771ebbdd0c61dd238b6f4ce8864149)) +* **fluid-build:** Always consider semantic errors in incremental tsc ([#20887](https://github.com/microsoft/FluidFramework/issues/20887)) ([95d2b89](https://github.com/microsoft/FluidFramework/commit/95d2b898185b2ad8da2cf742ae335534ec4bdab6)) +* **generate:changelog:** Add --(no-)install flag and enhance error reporting ([#20555](https://github.com/microsoft/FluidFramework/issues/20555)) ([46d3823](https://github.com/microsoft/FluidFramework/commit/46d38235e92432c43c46a6d23bd47c9af7b83f2e)) +* **generate:entrypoints:** Enable jsx tsconfig option in ts-morph project ([#20780](https://github.com/microsoft/FluidFramework/issues/20780)) ([b56a949](https://github.com/microsoft/FluidFramework/commit/b56a9495cd7804eb3b75525146c9f797a544d35e)) +* **generate:typetests:** Exit earlier when typetests are disabled ([#20877](https://github.com/microsoft/FluidFramework/issues/20877)) ([7eb8209](https://github.com/microsoft/FluidFramework/commit/7eb820932e656e4bb58ab9c64d07503457864438)), closes [#20878](https://github.com/microsoft/FluidFramework/issues/20878) +* **generate:typetests:** Load source instead of type declarations for current package version ([#20885](https://github.com/microsoft/FluidFramework/issues/20885)) ([d76ba0c](https://github.com/microsoft/FluidFramework/commit/d76ba0c015e61cc00cad26e39a72f5a999c62f56)) +* **modify:fluid-imports:** Use narrower checks for whether an import is a Fluid import ([#20730](https://github.com/microsoft/FluidFramework/issues/20730)) ([8ab7082](https://github.com/microsoft/FluidFramework/commit/8ab70823bde6fa48f8a7180fcad910b0926cd9f5)) + +## [0.37.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.36.0...build-tools_v0.37.0) (2024-04-12) + + +### Features + +* **build-tools:** `generate entrypoints` per package.json with Node10 option ([#20631](https://github.com/microsoft/FluidFramework/issues/20631)) ([521dbd1](https://github.com/microsoft/FluidFramework/commit/521dbd12591e10a737999a7c4ec83791ff3277bc)) + + +### Bug Fixes + +* **build-tools:** generate entrypoints overloaded API ([#20630](https://github.com/microsoft/FluidFramework/issues/20630)) ([a772fbf](https://github.com/microsoft/FluidFramework/commit/a772fbfb8df90172548b19181584df36270c0c5b)) + +## [0.36.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.35.0...build-tools_v0.36.0) (2024-04-10) + + +### Features + +* **generate:entrypoints:** Support output filename customization ([#20593](https://github.com/microsoft/FluidFramework/issues/20593)) ([4e94094](https://github.com/microsoft/FluidFramework/commit/4e94094abc280ceda11a0833c9c109e1c353e91e)) + +## [0.35.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.34.0...build-tools_v0.35.0) (2024-04-09) + + +### Features + +* **build-cli:** Add `modify fluid-imports` command ([#20006](https://github.com/microsoft/FluidFramework/issues/20006)) ([afe35a4](https://github.com/microsoft/FluidFramework/commit/afe35a41fb3998d9df1526ca573840b88e005be2)) +* **build-tools:** `fluid-imports` to read API levels from packages ([#20437](https://github.com/microsoft/FluidFramework/issues/20437)) ([56af782](https://github.com/microsoft/FluidFramework/commit/56af782101fac6b501975a862244cc4140302a99)) +* **build-tools:** New command `generate:entrypoints` ([#20477](https://github.com/microsoft/FluidFramework/issues/20477)) ([e84fbf4](https://github.com/microsoft/FluidFramework/commit/e84fbf49ae781ffce54ac0725a27ff8eb0d6bcf8)) +* **build-tools:** single ts project use ([#20187](https://github.com/microsoft/FluidFramework/issues/20187)) ([2830317](https://github.com/microsoft/FluidFramework/commit/283031722f4e3cc719b5e72577b8eb90eaae70e5)) +* **fluid-build:** Add incremental build support for biome tasks ([#20173](https://github.com/microsoft/FluidFramework/issues/20173)) ([b99f0e0](https://github.com/microsoft/FluidFramework/commit/b99f0e02e0814970c73cc31401994588bc3989e1)) + + +### Bug Fixes + +* **build-tools,client:** api-extractor cleanup and incrementality ([#20394](https://github.com/microsoft/FluidFramework/issues/20394)) ([a6b5f7c](https://github.com/microsoft/FluidFramework/commit/a6b5f7c2cd24e6b4c86be8b62f448ecedf780687)) +* **build-tools:** `modify fluid-imports` ([#20397](https://github.com/microsoft/FluidFramework/issues/20397)) ([a78dc6c](https://github.com/microsoft/FluidFramework/commit/a78dc6c6dd9843009c6f33dfd0b9620fe0093814)) +* **build-tools:** handle special export cases ([#20512](https://github.com/microsoft/FluidFramework/issues/20512)) ([9166910](https://github.com/microsoft/FluidFramework/commit/91669108ccf45512580d6a8f6080798554a14c84)) +* **build-tools:** mixed internal range detection ([#18828](https://github.com/microsoft/FluidFramework/issues/18828)) ([6ecc27e](https://github.com/microsoft/FluidFramework/commit/6ecc27ee08b8d84bc6a8bc32a87ba2f10fda4bb3)) +* **build-tools:** relax fluid-build-tasks-eslint for lint only projects ([#20432](https://github.com/microsoft/FluidFramework/issues/20432)) ([8626477](https://github.com/microsoft/FluidFramework/commit/8626477401160e646cf686e7566cdbd85e79e96d)), closes [AB#7630](https://github.com/microsoft/AB/issues/7630) +* **build-tools:** tsc task policy Windows ([#20172](https://github.com/microsoft/FluidFramework/issues/20172)) ([ae890d3](https://github.com/microsoft/FluidFramework/commit/ae890d3243ebff9836474e0c6a61c386404e3630)), closes [AB#7460](https://github.com/microsoft/AB/issues/7460) +* **flub release:** Account for RC release branch names ([#20229](https://github.com/microsoft/FluidFramework/issues/20229)) ([f0ba3ef](https://github.com/microsoft/FluidFramework/commit/f0ba3ef41c4fe8f4d98e57376c68d0335f9f2a17)) +* **fluid-build:** limit Biome config tracking to repo ([#20296](https://github.com/microsoft/FluidFramework/issues/20296)) ([5c7a249](https://github.com/microsoft/FluidFramework/commit/5c7a2492fc05aa10b3a145f0dea831333796d52e)) +* **fluid-build:** TscTask does not detect incremental changes in some projects ([#20032](https://github.com/microsoft/FluidFramework/issues/20032)) ([6c6a811](https://github.com/microsoft/FluidFramework/commit/6c6a811215491ea69481bc4c03bb9f90000f9b94)) +* **fluid-build:** TscTask use the correct noEmit flag to check for previous errors ([#20040](https://github.com/microsoft/FluidFramework/issues/20040)) ([4909d82](https://github.com/microsoft/FluidFramework/commit/4909d8206bfd74386b5b16902f8ccbc4ebd376cf)) +* **generate:upcoming:** Include all changesets when release type is major ([#20552](https://github.com/microsoft/FluidFramework/issues/20552)) ([92b38b1](https://github.com/microsoft/FluidFramework/commit/92b38b1d878e26c0c6f430cb70e520ec503c34b3)) +* **generate:upcoming:** Use release group-relative paths ([#20015](https://github.com/microsoft/FluidFramework/issues/20015)) ([3a3311f](https://github.com/microsoft/FluidFramework/commit/3a3311fe2ebc9adf276468bc4c8de49631aee750)) + +## [0.34.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.33.0...build-tools_v0.34.0) (2024-02-20) + + +### Features + +* **build-tools:** `fluid-tsc` ([#19698](https://github.com/microsoft/FluidFramework/issues/19698)) ([b9a2751](https://github.com/microsoft/FluidFramework/commit/b9a275124523cf65c6546d775207fa36d477964a)) + +## [0.33.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.32.0...build-tools_v0.33.0) (2024-02-15) + + +### Bug Fixes + +* **ci:** Bring bundle-size-comparison back online ([#19638](https://github.com/microsoft/FluidFramework/issues/19638)) ([337fcd2](https://github.com/microsoft/FluidFramework/commit/337fcd24215be1d5b91a4e2ee3636994377c7292)) + +## [0.32.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.30.0...build-tools_v0.32.0) (2024-02-14) + + +### Bug Fixes + +* **docs:** Wildcard redirects brute force fix ([#19547](https://github.com/microsoft/FluidFramework/issues/19547)) ([79dd64d](https://github.com/microsoft/FluidFramework/commit/79dd64d22a155b17e7d17f678c4d603e402c8e48)) + +## 0.31.0 + +Not released. + +## [0.30.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.30.0...build-tools_v0.30.0) (2024-02-08) + + +### Features + +* **build-tools:** Add policy handler to ensure public packages have required api-extractor scripts and dependency ([#18804](https://github.com/microsoft/FluidFramework/issues/18804)) ([0e93d05](https://github.com/microsoft/FluidFramework/commit/0e93d0519be41c79cc987793e5c93972c0e7682b)) +* **check:policy:** Prevent .js file extension ([#19106](https://github.com/microsoft/FluidFramework/issues/19106)) ([0f2b8da](https://github.com/microsoft/FluidFramework/commit/0f2b8dab89150b5a93905eaf875413d512408b7f)) + + +### Bug Fixes + +* **version-tools:** Detect bump types between RC builds and internal builds correctly ([#19152](https://github.com/microsoft/FluidFramework/issues/19152)) ([aaa4441](https://github.com/microsoft/FluidFramework/commit/aaa444166d86988afb6878949eadfbfa8269f7b5)) + +## [0.29.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.28.0...build-tools_v0.29.0) (2024-01-04) + + +### Features + +* **fluid-build:** Task caching for ts2esm tasks ([#19027](https://github.com/microsoft/FluidFramework/issues/19027)) ([ac5840d](https://github.com/microsoft/FluidFramework/commit/ac5840dbead2f7ab5756b45a873aa60ffb08d319)) +* **generate:buildVersion:** Add support for RC versions ([#18373](https://github.com/microsoft/FluidFramework/issues/18373)) ([f127d00](https://github.com/microsoft/FluidFramework/commit/f127d0019d7c1d8e7ca2195c77d2610663dcaaa3)), closes [AB#6142](https://github.com/microsoft/AB/issues/6142) + + +### Bug Fixes + +* **check:policy:** Exclude scripts that use tsc --watch from "check phase", not just "resolve phase" ([#18529](https://github.com/microsoft/FluidFramework/issues/18529)) ([e89a478](https://github.com/microsoft/FluidFramework/commit/e89a478169f1500c6d4cb156451257a043346c11)) +* **fluid-build:** Fix caching of tsc-multi tasks ([#18957](https://github.com/microsoft/FluidFramework/issues/18957)) ([1196b7a](https://github.com/microsoft/FluidFramework/commit/1196b7a001238549777dfcbf091c0e6d56777a8b)) +* **fluid-build:** incremental ts2esm task ([#19062](https://github.com/microsoft/FluidFramework/issues/19062)) ([49b17c3](https://github.com/microsoft/FluidFramework/commit/49b17c379d04d90650c3c97ca3b9be297a6efbeb)) +* More dual-emit support (mostly for test coverage) ([#18866](https://github.com/microsoft/FluidFramework/issues/18866)) ([938b108](https://github.com/microsoft/FluidFramework/commit/938b1083c415ba16ef5d2d28058570b098f364e7)) + +## [0.28.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.27.0...build-tools_v0.28.0) (2023-11-16) + + +### Features + +* **type-tests:** explicitly return void in type-test function ([#18282](https://github.com/microsoft/FluidFramework/issues/18282)) ([b1165d8](https://github.com/microsoft/FluidFramework/commit/b1165d8e294b6a5303b4da84b9498c945cf85bef)) + + +### Bug Fixes + +* **fluid-build:** Ignore --cache flag in prettier ([#18341](https://github.com/microsoft/FluidFramework/issues/18341)) ([c9da0db](https://github.com/microsoft/FluidFramework/commit/c9da0dbde0d95889f88cb0eb6dea9d86ef1dd829)) +* **fluid-build:** Include source files in tsc-multi done file ([#18292](https://github.com/microsoft/FluidFramework/issues/18292)) ([fdd0941](https://github.com/microsoft/FluidFramework/commit/fdd09413bb8ed288cf7a4a1667f573d97244a7c5)) +* **release:fromTag:** Return correct release dates ([#18254](https://github.com/microsoft/FluidFramework/issues/18254)) ([cd717b1](https://github.com/microsoft/FluidFramework/commit/cd717b1a6829ad6d0ecda9d478886a71edf264f9)) + +## [0.27.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.26.0...build-tools_v0.27.0) (2023-11-10) + + +### Features + +* **build-tools:** Add support for tsc-multi ([#18233](https://github.com/microsoft/FluidFramework/issues/18233)) ([8969798](https://github.com/microsoft/FluidFramework/commit/8969798c7dc1064f1824132e11843ab5f90e9935)) +* **check:policy:** Make policy handlers async ([#17931](https://github.com/microsoft/FluidFramework/issues/17931)) ([6a6da06](https://github.com/microsoft/FluidFramework/commit/6a6da064567454237f6dafc60eee8c4205be28b0)) + + +### Bug Fixes + +* **build-cli:** Fix broken test ([#18105](https://github.com/microsoft/FluidFramework/issues/18105)) ([16ddcf5](https://github.com/microsoft/FluidFramework/commit/16ddcf5e4ac950207d0e3608414bdaf2c290d312)) +* **build-tools:** Use fluid-build task definitions ([#18159](https://github.com/microsoft/FluidFramework/issues/18159)) ([9793744](https://github.com/microsoft/FluidFramework/commit/97937447bc82d5b492b5b180ac74669d4c26ca86)) +* **check:policy:** consistent script arguments ([#18057](https://github.com/microsoft/FluidFramework/issues/18057)) ([8b3da9d](https://github.com/microsoft/FluidFramework/commit/8b3da9d8626a72826a826eddbf09afef9943911e)) +* **check:policy:** Exclude tsc --watch tasks from policy ([#18104](https://github.com/microsoft/FluidFramework/issues/18104)) ([6aae2ce](https://github.com/microsoft/FluidFramework/commit/6aae2ce76e6679fa6ecc94161ab6d5bae698a2dc)) +* **check:policy:** Include the handler name in failure message ([#18102](https://github.com/microsoft/FluidFramework/issues/18102)) ([6a2bda9](https://github.com/microsoft/FluidFramework/commit/6a2bda96c981f8f1d09a58975441db9758fd33b6)) + +## [0.26.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.25.0...build-tools_v0.26.0) (2023-10-25) + + +### Features + +* **build-cli:** Add generate:assertTags command ([#17872](https://github.com/microsoft/FluidFramework/issues/17872)) ([826f779](https://github.com/microsoft/FluidFramework/commit/826f7797043b1d70f80e122f0c1f8aeeb37e300e)) +* **check:policy:** Verify all packages have a types field in package.json ([#17807](https://github.com/microsoft/FluidFramework/issues/17807)) ([8e277f8](https://github.com/microsoft/FluidFramework/commit/8e277f849fdf484f2dbf28dc32c4dff6c40b2ce9)) +* **check:policy:** Verify packages have an exports field in package.json ([#17824](https://github.com/microsoft/FluidFramework/issues/17824)) ([5b580d3](https://github.com/microsoft/FluidFramework/commit/5b580d3b5b86e3070f4c18900fd688bb3219baa6)) +* **fluid-build:** Release group root script support ([#17835](https://github.com/microsoft/FluidFramework/issues/17835)) ([90c7f9d](https://github.com/microsoft/FluidFramework/commit/90c7f9d61f0e0e35e59b884509fd15791d88b03f)), closes [#17837](https://github.com/microsoft/FluidFramework/issues/17837) + + +### Bug Fixes + +* **build-tools:** run.js should set development: false ([#17893](https://github.com/microsoft/FluidFramework/issues/17893)) ([dcea05a](https://github.com/microsoft/FluidFramework/commit/dcea05a8fafe766b68e81ae6a398ebba659c56ac)) +* **build-tools:** Windows compatible clean policy ([#17874](https://github.com/microsoft/FluidFramework/issues/17874)) ([a1fb4e8](https://github.com/microsoft/FluidFramework/commit/a1fb4e869b575a5f991ff3edb51f62938c3f5154)) +* **check:policy:** Add changes that were missed to the exports field policy ([#17886](https://github.com/microsoft/FluidFramework/issues/17886)) ([cbef814](https://github.com/microsoft/FluidFramework/commit/cbef814a836a837621dc31fb657fc6c725aae3e6)), closes [#17824](https://github.com/microsoft/FluidFramework/issues/17824) +* **check:policy:** Use exports.default for CJS- and ESM-only packages ([#17894](https://github.com/microsoft/FluidFramework/issues/17894)) ([30def22](https://github.com/microsoft/FluidFramework/commit/30def221eb3428342d301d8135dbfb7cfb53b3ce)) +* **fluid-build:** Clean up eslint warnings in `build-tools` package ([#17718](https://github.com/microsoft/FluidFramework/issues/17718)) ([ec80944](https://github.com/microsoft/FluidFramework/commit/ec809441c0f61affe62db430f00a6ddbb6123be4)) + +## [0.25.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.24.0...build-tools_v0.25.0) (2023-10-04) + +No recorded changes. + +## [0.24.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.23.0...build-tools_v0.24.0) (2023-09-25) + + +### Bug Fixes + +* **build-cli:** Fetch only from upstream remote ([#17393](https://github.com/microsoft/FluidFramework/issues/17393)) ([de06e2e](https://github.com/microsoft/FluidFramework/commit/de06e2e368230316c59fab3ffc53ed767404fb7f)) +* **bump:deps:** Fix filtering of release groups ([#17055](https://github.com/microsoft/FluidFramework/issues/17055)) ([7829d8a](https://github.com/microsoft/FluidFramework/commit/7829d8a50770989482c5669a6e54352ef657f35b)) +* **fluid-build:** Pass env vars to child processes ([#17440](https://github.com/microsoft/FluidFramework/issues/17440)) ([93f8f89](https://github.com/microsoft/FluidFramework/commit/93f8f89acfc1f9e9185251b08096cf9d6937297f)) + +## [0.23.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.22.0...build-tools_v0.23.0) (2023-08-28) + + +### Features + +* **bump:deps:** Add experimental homegrown update checker ([#16356](https://github.com/microsoft/FluidFramework/issues/16356)) ([45fc83f](https://github.com/microsoft/FluidFramework/commit/45fc83f8aef0134a2897f56643582c254414c195)) +* **check:policy:** Add configurable policy for package names and scopes ([#16863](https://github.com/microsoft/FluidFramework/issues/16863)) ([649d19d](https://github.com/microsoft/FluidFramework/commit/649d19dc35b786b64e04334d932f4a8832a6ec02)) + + +### Bug Fixes + +* **bump:deps:** Add undefined check ([#16937](https://github.com/microsoft/FluidFramework/issues/16937)) ([0e43733](https://github.com/microsoft/FluidFramework/commit/0e4373362dd9f497820c5fe234901f33ec956da2)) +* **bump:deps:** Exclude private packages when checking npm ([#16683](https://github.com/microsoft/FluidFramework/issues/16683)) ([a04331f](https://github.com/microsoft/FluidFramework/commit/a04331f65c2c483faa3c480f27c6f959bc119151)) +* **fluid-build:** fix incremental builds for TS 5.1 ([#16985](https://github.com/microsoft/FluidFramework/issues/16985)) ([f4e37b2](https://github.com/microsoft/FluidFramework/commit/f4e37b203f510479a2a6288cc4e345ca415518ab)) +* **typetests:** Don't fail when packages have no dependencies ([#16717](https://github.com/microsoft/FluidFramework/issues/16717)) ([65adbc2](https://github.com/microsoft/FluidFramework/commit/65adbc27e2afd5b26f72f7b25feaa262c49852d7)) + +## [0.22.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.21.0...build-tools_v0.22.0) (2023-08-02) + + +### Bug Fixes + +* **build-tools:** Support unscoped package names ([#16543](https://github.com/microsoft/FluidFramework/issues/16543)) ([675cc1e](https://github.com/microsoft/FluidFramework/commit/675cc1e9622bd44e813c84524c65b27eebf3e3dd)) +* **bundle-size-tools:** Report size 0 instead of failing for missing asset ([#16564](https://github.com/microsoft/FluidFramework/issues/16564)) ([507dc26](https://github.com/microsoft/FluidFramework/commit/507dc26111df013c40d0710b9dfed9b46f1fb97b)) +* **bundle-size:** Fix NaNs in bundle size comparison ([#16605](https://github.com/microsoft/FluidFramework/issues/16605)) ([2395a24](https://github.com/microsoft/FluidFramework/commit/2395a244ecdf59ca0f1be522c9ae7079c7d8aa01)) +* **fluid-build:** Load server root path from settings ([#16666](https://github.com/microsoft/FluidFramework/issues/16666)) ([d9ba203](https://github.com/microsoft/FluidFramework/commit/d9ba203e796caee95b4a56fff4b9712e2c4be58b)) +* **merge:branches:** Merge source into target branch instead of the other way ([#16496](https://github.com/microsoft/FluidFramework/issues/16496)) ([e45e495](https://github.com/microsoft/FluidFramework/commit/e45e4951cd28d8af0ff14f3d279287c2c479ba93)) +* **merge:branches:** Switch branches before trying to delete the branch ([#16398](https://github.com/microsoft/FluidFramework/issues/16398)) ([e51ba16](https://github.com/microsoft/FluidFramework/commit/e51ba16a5bc58a7023ecc6ed0a93923f645808ab)) +* **merge:branches:** Update merge instructions ([#16500](https://github.com/microsoft/FluidFramework/issues/16500)) ([75e95b4](https://github.com/microsoft/FluidFramework/commit/75e95b4a2add7c8cd1be015bd49d07238eaa42fe)) + +## [0.21.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.20.0...build-tools_v0.21.0) (2023-07-11) + +### ⚠ BREAKING CHANGES + +* **build-tools:** - `flub generate packageJson` has been removed since it is no longer +needed in the pipeline. +- `getIsLatest` and `getSimpleVersion` have moved to the version-tools +package. +- The following exports are removed from build-tools: + - `getVersionsFromStrings` + - `bumpDependencies` + - `bumpRepo` + - `cleanPrereleaseDependencies` + - `createReleaseBump` + - `releaseVersion` + - `generateMonoRepoInstallPackageJson` + - `exec` + - `execNoError` + - `execAsync` + - `execWithErrorAsync` + - `readFileAsync` + - `writeFileAsync` +- The following bin scripts have been removed from build-tools: + - fluid-build-version + - fluid-bump-version + - fluid-collect-bundle-analyses + - fluid-layer-check + - fluid-repo-policy-check + - fluid-run-bundle-analyses +- ~~The collectVersionInfo and collectBumpInfo methods were removed from +the Context class.~~ Deprecated instead. + +### Bug Fixes + +* **bump:deps:** Allow bumping server deps ([#16313](https://github.com/microsoft/FluidFramework/issues/16313)) ([4098adf](https://github.com/microsoft/FluidFramework/commit/4098adf3ef56e3a09e37a7d9292c005f08318080)) +* Correct handling of filter and selectionFlags ([#16254](https://github.com/microsoft/FluidFramework/issues/16254)) ([a8fbb2e](https://github.com/microsoft/FluidFramework/commit/a8fbb2e8765afaaccf2a2ff93cd7c99a9fc2c688)) + + +### Code Refactoring + +* **build-tools:** Delete unused code and exports ([#15079](https://github.com/microsoft/FluidFramework/issues/15079)) ([281ee8d](https://github.com/microsoft/FluidFramework/commit/281ee8d3bbff74ca3af3136812fdaa2c2ae834af)) + +## [0.20.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.19.0...build-tools_v0.20.0) (2023-06-30) + + +### Features + +* **build-tools:** Add "list" command to replace "lerna ls" ([#16114](https://github.com/microsoft/FluidFramework/issues/16114)) ([3cb7cef](https://github.com/microsoft/FluidFramework/commit/3cb7cef88dcfc6b8bb02f13eb9618b00f3d4859e)) +* **build-tools:** Add generate changelog command ([#15949](https://github.com/microsoft/FluidFramework/issues/15949)) ([12a5ec6](https://github.com/microsoft/FluidFramework/commit/12a5ec68b03b38b5ff0456e26ca1df5235b72f3e)), closes [AB#3975](https://github.com/microsoft/AB/issues/3975) +* **changesets:** Prompt to select target branch ([#16141](https://github.com/microsoft/FluidFramework/issues/16141)) ([8f55673](https://github.com/microsoft/FluidFramework/commit/8f5567332c67f69a121f28f2b34ee75c9318d6d3)) + + +### Bug Fixes + +* **bundleStats:** Correctly handle pnpm list output ([#16168](https://github.com/microsoft/FluidFramework/issues/16168)) ([c21253c](https://github.com/microsoft/FluidFramework/commit/c21253c4276e6c3757d4a85885fa4ea1004f1a49)) +* **changesets:** Handle uncommitted changesets ([#16126](https://github.com/microsoft/FluidFramework/issues/16126)) ([f442bfe](https://github.com/microsoft/FluidFramework/commit/f442bfe7804a38b86032f04a0ce7741915c2bd92)) +* **changesets:** Sort changed packages earlier ([#16133](https://github.com/microsoft/FluidFramework/issues/16133)) ([6464d0c](https://github.com/microsoft/FluidFramework/commit/6464d0ce2de75a3f12d524c0e5e9a4a81964e98b)) +* **fluid-build:** Avoid typetests:gen dependency for tsc script on project that has sep… ([#16135](https://github.com/microsoft/FluidFramework/issues/16135)) ([50afbdf](https://github.com/microsoft/FluidFramework/commit/50afbdfaa804e6b4505cb0587ae2b1b128f07ef6)) +* **fluid-build:** Don't run script tasks not in task definition ([#16100](https://github.com/microsoft/FluidFramework/issues/16100)) ([c8d196e](https://github.com/microsoft/FluidFramework/commit/c8d196ef86fe0d7ee7d411682bbfb5a2564daef3)) +* **generate:changeset:** getChangedSinceRef and related functions use remote properly ([#16067](https://github.com/microsoft/FluidFramework/issues/16067)) ([9950467](https://github.com/microsoft/FluidFramework/commit/99504672fed8d70017aaa1d18527c7080a2d0954)) +* **release:** Run install when prerelease dependencies are updated ([#16037](https://github.com/microsoft/FluidFramework/issues/16037)) ([0c2045c](https://github.com/microsoft/FluidFramework/commit/0c2045c089f65ea0ddf2468691bad191cb9351d4)) +* **upcoming:** Don't output the changeset dates ([#16204](https://github.com/microsoft/FluidFramework/issues/16204)) ([ccb3450](https://github.com/microsoft/FluidFramework/commit/ccb3450d2168d62332d6055f426e5b9033ae9eec)) + +## [0.19.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.18.0...build-tools_v0.19.0) (2023-06-16) + + +### Bug Fixes + +* **build-tools:** Don't include dynamic version in readmes ([#16028](https://github.com/microsoft/FluidFramework/issues/16028)) ([1fd70e0](https://github.com/microsoft/FluidFramework/commit/1fd70e0566d33840d87ab0a46bd860e4e25ed8d7)) +* **build-tools:** Fix dependency and task prioritization ([#15835](https://github.com/microsoft/FluidFramework/issues/15835)) ([4eb9a49](https://github.com/microsoft/FluidFramework/commit/4eb9a49cf45fb9fecd6ac3545b55d9e7a2a14dc4)) +* Fixes for build-tools for LTS branch ([#15912](https://github.com/microsoft/FluidFramework/issues/15912)) ([8703507](https://github.com/microsoft/FluidFramework/commit/8703507091bee789aa7908a10b906ba5f6f04bab)) +* **generate:changeset:** Support entering changeset info in CLI ([#15876](https://github.com/microsoft/FluidFramework/issues/15876)) ([2e376e5](https://github.com/microsoft/FluidFramework/commit/2e376e520a84f93bb24e598d8742955f4e919fa5)) +* Pin version for npx lerna in build-tools ([#15923](https://github.com/microsoft/FluidFramework/issues/15923)) ([6ac4182](https://github.com/microsoft/FluidFramework/commit/6ac418261bf63a9c2e857fbdb73cc9f8b0ab46ff)) +* **release:** Handle independent packages with release branches ([#15847](https://github.com/microsoft/FluidFramework/issues/15847)) ([68448c9](https://github.com/microsoft/FluidFramework/commit/68448c9e8522e76d4c71c6672256601a641ea82b)) + +## [0.18.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.17.0...build-tools_v0.18.0) (2023-06-01) + + +### ⚠ BREAKING CHANGES + +* **fluid-build:** The `--script` flag has been removed. Use the `--task` +flag instead. + +### Features + +* Add `changeset add` command ([#15489](https://github.com/microsoft/FluidFramework/issues/15489)) ([be64285](https://github.com/microsoft/FluidFramework/commit/be642852a878c3dfd9212808dea91722f6908160)), closes [AB#3967](https://github.com/microsoft/AB/issues/3967) +* Implement declarative task dependencies in `fluid-build` ([#15589](https://github.com/microsoft/FluidFramework/issues/15589)) ([af627a4](https://github.com/microsoft/FluidFramework/commit/af627a48fab9cea7bb7f14ce4a4886882499df78)) + + +### Bug Fixes + +* Better error message and fix couple of task definitions ([#15609](https://github.com/microsoft/FluidFramework/issues/15609)) ([95ea724](https://github.com/microsoft/FluidFramework/commit/95ea724d4781bbc1d3fcdff4b6dfe589ac1df8b2)) +* fluid-build select precise dependencies ([#15621](https://github.com/microsoft/FluidFramework/issues/15621)) ([1020163](https://github.com/microsoft/FluidFramework/commit/1020163e912ef33d34975e63c7df41fc75d837bb)) + +## [0.17.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.16.0...build-tools_v0.17.0) (2023-05-05) + + +### Features + +* Support JSON output in check:changeset ([#15465](https://github.com/microsoft/FluidFramework/issues/15465)) ([cb98c6e](https://github.com/microsoft/FluidFramework/commit/cb98c6e66c03fca6a1b3ce09b7479d7f9ac399a8)), closes [#15472](https://github.com/microsoft/FluidFramework/issues/15472) + + +### Bug Fixes + +* **fluid-build:** Handle all workspace ranges when checking symlinks ([#15469](https://github.com/microsoft/FluidFramework/issues/15469)) ([0e1e41f](https://github.com/microsoft/FluidFramework/commit/0e1e41ff4064790819a25100c42621d0b09af272)) +* **report:** Handle workspace ranges when generating reports ([#15439](https://github.com/microsoft/FluidFramework/issues/15439)) ([e4b473e](https://github.com/microsoft/FluidFramework/commit/e4b473eaf09ec8b9318199b1ec8991dacadc5462)) + +## [0.16.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.15.0...build-tools_v0.16.0) (2023-05-03) + +### ⚠ BREAKING CHANGES + +* **bump:** The `--exactDepType` flag in the `bump` command no +longer has a default value. It has also been deprecated. It has been +replaced by the `--interdependencyType` flag. The deprecated flag will +be removed in an upcoming release. + +### Features + +* Add check buildVersion command ([#15392](https://github.com/microsoft/FluidFramework/issues/15392)) ([65ed736](https://github.com/microsoft/FluidFramework/commit/65ed7363510bdb8bc1009be345484b196fa0f61d)), closes [/github.com/microsoft/FluidFramework/pull/15381#issuecomment-1530727486](https://github.com/microsoft//github.com/microsoft/FluidFramework/pull/15381/issues/issuecomment-1530727486) +* Add check changeset command ([#15320](https://github.com/microsoft/FluidFramework/issues/15320)) ([3820ffb](https://github.com/microsoft/FluidFramework/commit/3820ffb72fb8f1a839f5fd35d88dae0748b25ac9)) +* Add release fromTag command ([#15287](https://github.com/microsoft/FluidFramework/issues/15287)) ([116ece1](https://github.com/microsoft/FluidFramework/commit/116ece1e0dc2108990aa1eeeec9be21c1270b508)), closes [#15288](https://github.com/microsoft/FluidFramework/issues/15288) +* **bump:** Look up packages by unscoped name ([#15107](https://github.com/microsoft/FluidFramework/issues/15107)) ([31d8b31](https://github.com/microsoft/FluidFramework/commit/31d8b31a95a85c68376bd74d709bd5ba53518174)) +* **bump:** Support workspace protocol in release and bump tools ([#15053](https://github.com/microsoft/FluidFramework/issues/15053)) ([a8c6178](https://github.com/microsoft/FluidFramework/commit/a8c617819781413a1d3154c344450f8ec7a41400)), closes [#15158](https://github.com/microsoft/FluidFramework/issues/15158) [AB#3422](https://github.com/microsoft/AB/issues/3422) +* **info:** Add JSON output support ([#14778](https://github.com/microsoft/FluidFramework/issues/14778)) ([6a4d9b8](https://github.com/microsoft/FluidFramework/commit/6a4d9b8714acd33bb7b66d4c02c4596c0865a326)) + + +### Bug Fixes + +* **build-tools:** Remove readme from plugins list ([#15435](https://github.com/microsoft/FluidFramework/issues/15435)) ([5c69b84](https://github.com/microsoft/FluidFramework/commit/5c69b841d62638eea2b6957f69e5304e40167fe0)) +* **bump:** Fix interdependency range handling ([#15432](https://github.com/microsoft/FluidFramework/issues/15432)) ([ffb4578](https://github.com/microsoft/FluidFramework/commit/ffb45781dcb53d742e7b85e19031e80ca8ccd63c)) +* **release:** Correctly apply workspace interdependencyRanges ([#15420](https://github.com/microsoft/FluidFramework/issues/15420)) ([3543630](https://github.com/microsoft/FluidFramework/commit/3543630d20c3294876d7b78c467c6a3fc725d09a)) +* **release:** Exception when running policy-check tasks ([#15414](https://github.com/microsoft/FluidFramework/issues/15414)) ([0cf7b05](https://github.com/microsoft/FluidFramework/commit/0cf7b051d0e48886ac95f51f65dc390b849d70e3)) + +## [0.15.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.14.0...build-tools_v0.15.0) (2023-04-20) + +### Bug Fixes + +* **release:** Independent packages should use release branches ([#15199](https://github.com/microsoft/FluidFramework/issues/15199)) ([c985f84](https://github.com/microsoft/FluidFramework/commit/c985f8434ea36528e53132b41ed93252afddf22e)) + +## [0.14.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.13.0...build-tools_v0.14.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* Some exports have been removed, changing the API +surface. + +### Features + +* **build-cli:** Add exec command ([#14635](https://github.com/microsoft/FluidFramework/issues/14635)) ([5898496](https://github.com/microsoft/FluidFramework/commit/5898496b743a58357bbaa0011d5754c3cce1758c)) +* **check:policy:** Add setting to ignore single-package pnpm workspaces ([#14656](https://github.com/microsoft/FluidFramework/issues/14656)) ([ad72865](https://github.com/microsoft/FluidFramework/commit/ad72865d909fc7e5bdc7fb89b4d87fd8938a3fe5)) + + +### Bug Fixes + +* **build-cli:** Use shell in exec and bump ([#15117](https://github.com/microsoft/FluidFramework/issues/15117)) ([c6c34d4](https://github.com/microsoft/FluidFramework/commit/c6c34d44138c302c66dc77e327c9e4bdfa91abbf)), closes [AB#4067](https://github.com/microsoft/AB/issues/4067) +* **bump:** Correctly apply exactDepType when bumping ([#14999](https://github.com/microsoft/FluidFramework/issues/14999)) ([61dc925](https://github.com/microsoft/FluidFramework/commit/61dc92504bab0dbff900413cf07b336cf19de248)), closes [AB#2415](https://github.com/microsoft/AB/issues/2415) [#15053](https://github.com/microsoft/FluidFramework/issues/15053) +* **bump:** Correctly save package.json of bumped packages ([#14727](https://github.com/microsoft/FluidFramework/issues/14727)) ([534da6b](https://github.com/microsoft/FluidFramework/commit/534da6b76d808a71370ae4dd0f97b86880a3270d)), closes [#14481](https://github.com/microsoft/FluidFramework/issues/14481) +* **bump:** Fix incorrect paths when bumping release groups ([#15135](https://github.com/microsoft/FluidFramework/issues/15135)) ([4ca9f95](https://github.com/microsoft/FluidFramework/commit/4ca9f95cf0023f9ed69ab3d4d135914ea216d0d4)) +* **bump:** Fix invalid flag configuration ([#14475](https://github.com/microsoft/FluidFramework/issues/14475)) ([e8d0193](https://github.com/microsoft/FluidFramework/commit/e8d0193536a12c5890bdea6ab719e1effa3a9b65)) +* **bump:** Pass allow-same-version to npm version ([#15149](https://github.com/microsoft/FluidFramework/issues/15149)) ([58942b4](https://github.com/microsoft/FluidFramework/commit/58942b4179c712ac3a901a71108e35cbe4bf6ac0)) +* **policy-check:** Use correct package.json indentation ([#14481](https://github.com/microsoft/FluidFramework/issues/14481)) ([2ec5912](https://github.com/microsoft/FluidFramework/commit/2ec5912d0d2feac4237aa2418a60cb740ab9121a)) +* **release:** checkOnReleaseBranch handler ignores CLI argument ([#14872](https://github.com/microsoft/FluidFramework/issues/14872)) ([3f056f0](https://github.com/microsoft/FluidFramework/commit/3f056f02fcb2657049f9310a1c567bd2b5d037a0)) +* **release:** Handle released dependency bumps ([#14669](https://github.com/microsoft/FluidFramework/issues/14669)) ([d33bee7](https://github.com/microsoft/FluidFramework/commit/d33bee7c6510775a84fd268662362ae22a423e8c)) +* **telemetry-generator:** back to npm from pnpm ([#14695](https://github.com/microsoft/FluidFramework/issues/14695)) ([3fcfdca](https://github.com/microsoft/FluidFramework/commit/3fcfdca35752bc3a4a55b44d1d669eaeb12013a2)) + + +### Code Refactoring + +* Remove fs- and child_process-related exports from build-tools ([#15016](https://github.com/microsoft/FluidFramework/issues/15016)) ([a5d29a7](https://github.com/microsoft/FluidFramework/commit/a5d29a7fc36bae0ab90376bc7b63822af278b635)) + +## [0.13.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.12.0...build-tools_v0.13.0) (2023-03-22) + +### Features + +* **build-cli:** Add exec command ([#14635](https://github.com/microsoft/FluidFramework/issues/14635)) ([5898496](https://github.com/microsoft/FluidFramework/commit/5898496b743a58357bbaa0011d5754c3cce1758c)) +* **check:policy:** Add setting to ignore single-package pnpm workspaces ([#14656](https://github.com/microsoft/FluidFramework/issues/14656)) ([ad72865](https://github.com/microsoft/FluidFramework/commit/ad72865d909fc7e5bdc7fb89b4d87fd8938a3fe5)) + + +### Bug Fixes + +* **bump:** Fix invalid flag configuration ([#14475](https://github.com/microsoft/FluidFramework/issues/14475)) ([e8d0193](https://github.com/microsoft/FluidFramework/commit/e8d0193536a12c5890bdea6ab719e1effa3a9b65)) +* **policy-check:** Use correct package.json indentation ([#14481](https://github.com/microsoft/FluidFramework/issues/14481)) ([2ec5912](https://github.com/microsoft/FluidFramework/commit/2ec5912d0d2feac4237aa2418a60cb740ab9121a)) +* **release:** Handle released dependency bumps ([#14669](https://github.com/microsoft/FluidFramework/issues/14669)) ([d33bee7](https://github.com/microsoft/FluidFramework/commit/d33bee7c6510775a84fd268662362ae22a423e8c)) + + +## [0.12.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.10.0...build-tools_v0.12.0) (2023-03-08) + +### Features + +* **release:** Tag asserts separately from policy-check ([#14316](https://github.com/microsoft/FluidFramework/issues/14316)) ([eb5c849](https://github.com/microsoft/FluidFramework/commit/eb5c84979bb364220ab2969d36f5223ebe6cde74)) + +### Bug Fixes + +* **generate:typetests:** Use cached baseline ([#14317](https://github.com/microsoft/FluidFramework/issues/14317)) ([5d90f7c](https://github.com/microsoft/FluidFramework/commit/5d90f7cdd66c71333e09f3e1232115b92218f3cd)) +* **release:** Install dependencies if needed ([#14348](https://github.com/microsoft/FluidFramework/issues/14348)) ([f3e30e5](https://github.com/microsoft/FluidFramework/commit/f3e30e5415df63604a66709b6a0e26f96809801b)) + +### Build System + +* **build-tools:** Remove postinstall step ([#14275](https://github.com/microsoft/FluidFramework/issues/14275)) ([bbee6e9](https://github.com/microsoft/FluidFramework/commit/bbee6e95d1604a63fe55ac1aa3aa6869ffc2e1b2)) + +### Code Refactoring + +* Add new simple type test generator ([#14334](https://github.com/microsoft/FluidFramework/issues/14334)) ([c58c54a](https://github.com/microsoft/FluidFramework/commit/c58c54afae39f91948917d30162350828eb57c17)) + + +## 0.11.0 (NOT RELEASED) + +## [0.10.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.10.0...build-tools_v0.10.0) (2023-02-22) + + +### ⚠ BREAKING CHANGES + +* **generate:typetests:** We now prefer external files for the typetest config, so when running, it will always output to the external config file, and will remove the package.json typeValidation node. + +### Features + +* **build-cli:** Allow ssh git remotes ([#14145](https://github.com/microsoft/FluidFramework/issues/14145)) ([175a51b](https://github.com/microsoft/FluidFramework/commit/175a51baeaf65775b40d3dc2320fa8b3f03ee6b9)) +* **fluid-build:** Accepting monorepo path for build scope on fluid-build command line ([#14071](https://github.com/microsoft/FluidFramework/issues/14071)) ([29ab33c](https://github.com/microsoft/FluidFramework/commit/29ab33c04f55ab40eca45e1d702a157548769549)) +* **bump:** Support interdependency bump types ([#14161](https://github.com/microsoft/FluidFramework/issues/14161)) ([8cc5b1e](https://github.com/microsoft/FluidFramework/commit/8cc5b1e55820896bdb84825f9874ea55bc8a81f3)) +* **fluid-build:** Support external config ([#14215](https://github.com/microsoft/FluidFramework/issues/14215)) ([1fc3cbc](https://github.com/microsoft/FluidFramework/commit/1fc3cbc3e7cf1df5abf49da8665354c03236c929)) +* **generate:typetests:** Move typetest config to external file ([#14222](https://github.com/microsoft/FluidFramework/issues/14222)) ([15f0080](https://github.com/microsoft/FluidFramework/commit/15f0080afc7573380dacb368a36c1eb82c300ca3)) + + +### Bug Fixes + +* **fluid-build:** Fix copyfile command line parsing blocking incremental build ([#14083](https://github.com/microsoft/FluidFramework/issues/14083)) ([72c30a7](https://github.com/microsoft/FluidFramework/commit/72c30a7e901e91e7d5d48560dad5d1a83bdd3f6e)) +* **generate:typetests:** Fix null refs and clean up logging ([#14228](https://github.com/microsoft/FluidFramework/issues/14228)) ([94f39a6](https://github.com/microsoft/FluidFramework/commit/94f39a66ce1c440705b08866818e45485e6be53f)), closes [#14222](https://github.com/microsoft/FluidFramework/issues/14222) + +## [0.9.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.9.0...build-tools_v0.9.0) (2023-02-08) + + +### Features + +* **check:policy:** Add pnpm repo policies ([#14025](https://github.com/microsoft/FluidFramework/issues/14025)) ([4a76a06](https://github.com/microsoft/FluidFramework/commit/4a76a0688bc4f651a8b6d2a3b8dbe94481f4cc12)) +* **fluid-build:** Enforce formatting in lint scripts ([#13735](https://github.com/microsoft/FluidFramework/issues/13735)) ([5b11ee4](https://github.com/microsoft/FluidFramework/commit/5b11ee402b6a5200eb99605b6e81e9b71c029f51)) +* **release:** Include links to ADO pipelines in release tools ([#13764](https://github.com/microsoft/FluidFramework/issues/13764)) ([b65220d](https://github.com/microsoft/FluidFramework/commit/b65220dcb7f7386d03cfb47e601d884a3d09cf04)), closes [AB#2176](https://github.com/microsoft/AB/issues/2176) + + +### Bug Fixes + +* **bump:deps:** Include peer dependencies when bumping deps ([#13761](https://github.com/microsoft/FluidFramework/issues/13761)) ([d1e86ad](https://github.com/microsoft/FluidFramework/commit/d1e86ad9643d94765b93cfe5005478e225f3269a)) +* **generate:typetests:** Skip tests when previousVersion is invalid ([#13999](https://github.com/microsoft/FluidFramework/issues/13999)) ([ad34e58](https://github.com/microsoft/FluidFramework/commit/ad34e58181b180857e6a3dead1aebc5a5dd4e87c)) +* **release:report:** Display dates in full release report ([#13763](https://github.com/microsoft/FluidFramework/issues/13763)) ([330e6b7](https://github.com/microsoft/FluidFramework/commit/330e6b7b786edb59e5a5a43fb2a3862af8863fb6)), closes [AB#2198](https://github.com/microsoft/AB/issues/2198) + + +### Performance Improvements + +* **fluid-build:** Reduce package check noise by scoping to only packages to be built ([#14067](https://github.com/microsoft/FluidFramework/issues/14067)) ([7505327](https://github.com/microsoft/FluidFramework/commit/750532753bf9f2fbcd2e94cab7506fbb7122e698)) + +## [0.8.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.8.0...build-tools_v0.8.0) (2023-01-24) + + +### Bug Fixes + +* **check:policy:** Ignore packages that don't have pre-requisite scripts ([#13699](https://github.com/microsoft/FluidFramework/issues/13699)) ([9a2668c](https://github.com/microsoft/FluidFramework/commit/9a2668c4ddb15d0d3d8481b742fa63f25c28a8f1)) +* **generate:typetests:** Generate using prepped data when branch has no config ([#13674](https://github.com/microsoft/FluidFramework/issues/13674)) ([5c8a2fa](https://github.com/microsoft/FluidFramework/commit/5c8a2fa27f5d65284d606a060a2d25bf9d0537a2)) +* **run:bundleStats:** Remove logging deps ([#13769](https://github.com/microsoft/FluidFramework/issues/13769)) ([112be91](https://github.com/microsoft/FluidFramework/commit/112be919ca76c2e4acf4a0226a7e9a950b87f63b)) + +## [0.7.1](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.7.1...build-tools_v0.7.1) (2023-01-17) + + +### Bug Fixes + +* **check:policy:** fix handling of assert short codes in policy ([#13317](https://github.com/microsoft/FluidFramework/issues/13317)) ([9bfd9e3](https://github.com/microsoft/FluidFramework/commit/9bfd9e39035920e3c144fedac9af92fdd24bdd50)) + +## [0.7.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.7.0...build-tools_v0.7.0) (2022-12-08) + + +### ⚠ BREAKING CHANGES + +* **run:bundleStats:** The `--dirname` argument has been removed. There is now +a `--dangerfile` argument that defaults to the built-in dangerfile but +can be customized if needed. + +### Bug Fixes + +* **build-tools:** Use local policy-check in build-tools ([#13145](https://github.com/microsoft/FluidFramework/issues/13145)) ([e9b8590](https://github.com/microsoft/FluidFramework/commit/e9b8590647d21645dcfd31122e3d3af5763fb0e3)) +* **run:bundleStats:** Take path to dangerfile instead of directory ([#13154](https://github.com/microsoft/FluidFramework/issues/13154)) ([0372fe0](https://github.com/microsoft/FluidFramework/commit/0372fe000991e324907d3e6342d6f72a49dfcb50)) + ## [0.6.0](https://github.com/microsoft/FluidFramework/compare/build-tools_v0.5.0...build-tools_v0.6.0) (2022-11-28) diff --git a/build-tools/lerna.json b/build-tools/lerna.json index d5b966d3d107..66f37166afe6 100644 --- a/build-tools/lerna.json +++ b/build-tools/lerna.json @@ -1 +1 @@ -{ "version": "0.50.0", "npmClient": "pnpm", "useWorkspaces": true } +{ "version": "0.51.0", "npmClient": "pnpm", "useWorkspaces": true } diff --git a/build-tools/package.json b/build-tools/package.json index f718d03063fb..11381e1f6d1f 100644 --- a/build-tools/package.json +++ b/build-tools/package.json @@ -1,6 +1,6 @@ { "name": "build-tools-release-group-root", - "version": "0.50.0", + "version": "0.51.0", "private": true, "homepage": "https://fluidframework.com", "repository": { diff --git a/build-tools/packages/build-cli/docs/generate.md b/build-tools/packages/build-cli/docs/generate.md index e4c3730ed65f..6119b87488f3 100644 --- a/build-tools/packages/build-cli/docs/generate.md +++ b/build-tools/packages/build-cli/docs/generate.md @@ -9,6 +9,7 @@ Generate commands are used to create/update code, docs, readmes, etc. * [`flub generate changelog`](#flub-generate-changelog) * [`flub generate changeset`](#flub-generate-changeset) * [`flub generate entrypoints`](#flub-generate-entrypoints) +* [`flub generate node10Entrypoints`](#flub-generate-node10entrypoints) * [`flub generate packlist`](#flub-generate-packlist) * [`flub generate releaseNotes`](#flub-generate-releasenotes) * [`flub generate typetests`](#flub-generate-typetests) @@ -258,6 +259,25 @@ DESCRIPTION _See code: [src/commands/generate/entrypoints.ts](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/build-cli/src/commands/generate/entrypoints.ts)_ +## `flub generate node10Entrypoints` + +Generates node10 type declaration entrypoints for Fluid Framework API levels (/alpha, /beta, /internal etc.) as found in package.json "exports" + +``` +USAGE + $ flub generate node10Entrypoints [-v | --quiet] + +LOGGING FLAGS + -v, --verbose Enable verbose logging. + --quiet Disable all logging. + +DESCRIPTION + Generates node10 type declaration entrypoints for Fluid Framework API levels (/alpha, /beta, /internal etc.) as found + in package.json "exports" +``` + +_See code: [src/commands/generate/node10Entrypoints.ts](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/build-cli/src/commands/generate/node10Entrypoints.ts)_ + ## `flub generate packlist` Outputs a list of files that will be included in a package based on its 'files' property in package.json and any .npmignore files. diff --git a/build-tools/packages/build-cli/package.json b/build-tools/packages/build-cli/package.json index 0220357d5d34..417cd6aecd9b 100644 --- a/build-tools/packages/build-cli/package.json +++ b/build-tools/packages/build-cli/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/build-cli", - "version": "0.50.0", + "version": "0.51.0", "description": "Build tools for the Fluid Framework", "homepage": "https://fluidframework.com", "repository": { @@ -92,10 +92,9 @@ "@oclif/plugin-not-found": "^3.2.24", "@octokit/core": "^5.2.0", "@octokit/rest": "^21.0.2", - "@rushstack/node-core-library": "^3.66.1", + "@rushstack/node-core-library": "^5.9.0", "async": "^3.2.6", "azure-devops-node-api": "^11.2.0", - "chalk": "^5.3.0", "change-case": "^3.1.0", "cosmiconfig": "^8.3.6", "danger": "^12.3.3", @@ -119,6 +118,7 @@ "minimatch": "^7.4.6", "npm-check-updates": "^16.14.20", "oclif": "^4.15.16", + "picocolors": "^1.1.1", "prettier": "~3.2.5", "prompts": "^2.4.2", "read-pkg-up": "^7.0.1", diff --git a/build-tools/packages/build-cli/src/commands/bump.ts b/build-tools/packages/build-cli/src/commands/bump.ts index b2354151d387..bb100dc90303 100644 --- a/build-tools/packages/build-cli/src/commands/bump.ts +++ b/build-tools/packages/build-cli/src/commands/bump.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { confirm } from "@inquirer/prompts"; import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import * as semver from "semver"; import { FluidRepo, MonoRepo, Package } from "@fluidframework/build-tools"; diff --git a/build-tools/packages/build-cli/src/commands/bump/deps.ts b/build-tools/packages/build-cli/src/commands/bump/deps.ts index 9dbcd4468ebc..60477ada9cb7 100644 --- a/build-tools/packages/build-cli/src/commands/bump/deps.ts +++ b/build-tools/packages/build-cli/src/commands/bump/deps.ts @@ -4,7 +4,7 @@ */ import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import prompts from "prompts"; import stripAnsi from "strip-ansi"; diff --git a/build-tools/packages/build-cli/src/commands/check/changeset.ts b/build-tools/packages/build-cli/src/commands/check/changeset.ts index 805ff3a653c7..3782c436cadf 100644 --- a/build-tools/packages/build-cli/src/commands/check/changeset.ts +++ b/build-tools/packages/build-cli/src/commands/check/changeset.ts @@ -4,7 +4,7 @@ */ import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import { sortPackageJson as sortJson } from "sort-package-json"; import { BaseCommand, Repository } from "../../library/index.js"; diff --git a/build-tools/packages/build-cli/src/commands/generate/changeset.ts b/build-tools/packages/build-cli/src/commands/generate/changeset.ts index 4695c2053df8..c5ed574be6eb 100644 --- a/build-tools/packages/build-cli/src/commands/generate/changeset.ts +++ b/build-tools/packages/build-cli/src/commands/generate/changeset.ts @@ -9,8 +9,8 @@ import { VersionBumpType } from "@fluid-tools/version-tools"; import { Package } from "@fluidframework/build-tools"; import { Flags, ux } from "@oclif/core"; import { PackageName } from "@rushstack/node-core-library"; -import chalk from "chalk"; import { humanId } from "human-id"; +import chalk from "picocolors"; import prompts from "prompts"; import { releaseGroupFlag } from "../../flags.js"; @@ -236,7 +236,7 @@ export default class GenerateChangesetCommand extends BaseCommand< .map((pkg) => { const changed = changedPackages.some((cp) => cp.name === pkg.name); return { - title: changed ? `${pkg.name} ${chalk.red.bold("(changed)")}` : pkg.name, + title: changed ? `${pkg.name} ${chalk.red(chalk.bold("(changed)"))}` : pkg.name, value: pkg, selected: changed, }; @@ -251,7 +251,7 @@ export default class GenerateChangesetCommand extends BaseCommand< } const changed = changedPackages.some((cp) => cp.name === pkg.name); packageChoices.push({ - title: changed ? `${pkg.name} ${chalk.red.bold("(changed)")}` : pkg.name, + title: changed ? `${pkg.name} ${chalk.red(chalk.bold("(changed)"))}` : pkg.name, value: pkg, selected: changed, }); diff --git a/build-tools/packages/build-cli/src/commands/generate/node10Entrypoints.ts b/build-tools/packages/build-cli/src/commands/generate/node10Entrypoints.ts new file mode 100644 index 000000000000..9245a18371ca --- /dev/null +++ b/build-tools/packages/build-cli/src/commands/generate/node10Entrypoints.ts @@ -0,0 +1,128 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import fs from "node:fs/promises"; + +import type { PackageJson } from "@fluidframework/build-tools"; +import { ApiLevel, BaseCommand, knownApiLevels } from "../../library/index.js"; +// AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule. +// eslint-disable-next-line import/no-internal-modules +import { readPackageJson, readTsConfig } from "../../library/package.js"; + +import { + type Node10CompatExportData, + getTypesPathFromPackage, + // AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule. + // eslint-disable-next-line import/no-internal-modules +} from "../../library/packageExports.js"; +import type { CommandLogger } from "../../logging.js"; + +export default class GenerateNode10EntrypointsCommand extends BaseCommand< + typeof GenerateNode10EntrypointsCommand +> { + static readonly description = + `Generates node10 type declaration entrypoints for Fluid Framework API levels (/alpha, /beta, /internal etc.) as found in package.json "exports"`; + + public async run(): Promise { + const packageJson = await readPackageJson(); + + const tsconfig = await readTsConfig(); + + let emitDeclarationOnly = false; + if (tsconfig.compilerOptions?.emitDeclarationOnly !== undefined) { + emitDeclarationOnly = true; + } + + const mapNode10CompatExportPathToData = mapExportPathsFromPackage( + packageJson, + emitDeclarationOnly, + this.logger, + ); + + if (mapNode10CompatExportPathToData.size === 0) { + throw new Error( + 'There are no API level "exports" requiring Node10 type compatibility generation.', + ); + } + + await generateNode10TypeEntrypoints(mapNode10CompatExportPathToData, this.logger); + } +} + +export function mapExportPathsFromPackage( + packageJson: PackageJson, + emitDeclarationOnly: boolean, + log: CommandLogger, +): Map { + const mapKeyToOutput = new Map(); + + // Iterate through exports looking for properties with values matching keys in map. + for (const levels of knownApiLevels) { + // Exclude root "." path as "types" should handle that. + if (levels === ApiLevel.public) { + continue; + } + + const typesPath = getTypesPathFromPackage(packageJson, levels, log); + + if (typesPath === undefined) { + continue; + } + + const node10ExportPath = typesPath + .replace(/\/index(\.d\.[cm]?ts)?$/, "/internal$1") + .replace(/^.*\//, ""); + + mapKeyToOutput.set(node10ExportPath, { + relPath: typesPath, + isTypeOnly: emitDeclarationOnly, + }); + } + + return mapKeyToOutput; +} + +const generatedHeader: string = `/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +/* + * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + * Generated by "flub generate node10Entrypoints" in @fluid-tools/build-cli. + */ + +`; + +async function generateNode10TypeEntrypoints( + mapExportPathToData: Map, + log: CommandLogger, +): Promise { + /** + * List of out file save promises. Used to collect generated file save + * promises so we can await them all at once. + */ + const fileSavePromises: Promise[] = []; + + for (const [outFile, { relPath, isTypeOnly }] of mapExportPathToData.entries()) { + log.info(`\tGenerating ${outFile}`); + const jsImport = relPath.replace(/\.d\.([cm]?)ts/, ".$1js"); + fileSavePromises.push( + fs.writeFile( + outFile, + isTypeOnly + ? `${generatedHeader}export type * from "${relPath}";\n` + : `${generatedHeader}export * from "${jsImport}";\n`, + "utf8", + ), + ); + } + + if (fileSavePromises.length === 0) { + log.info(`\tNo Node10 compat files generated.`); + } + + await Promise.all(fileSavePromises); +} diff --git a/build-tools/packages/build-cli/src/commands/generate/typetests.ts b/build-tools/packages/build-cli/src/commands/generate/typetests.ts index 11517e916f53..a81de0db8918 100644 --- a/build-tools/packages/build-cli/src/commands/generate/typetests.ts +++ b/build-tools/packages/build-cli/src/commands/generate/typetests.ts @@ -16,7 +16,6 @@ import { Flags } from "@oclif/core"; import { PackageName } from "@rushstack/node-core-library"; import * as changeCase from "change-case"; import { readJson } from "fs-extra/esm"; -import * as resolve from "resolve.exports"; import { JSDoc, ModuleKind, @@ -37,6 +36,9 @@ import { } from "../../library/index.js"; // AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule. // eslint-disable-next-line import/no-internal-modules +import { getTypesPathFromPackage } from "../../library/packageExports.js"; +// AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule. +// eslint-disable-next-line import/no-internal-modules import { type TestCaseTypeData, buildTestCase } from "../../typeValidator/testGeneration.js"; // AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule. // eslint-disable-next-line import/no-internal-modules @@ -221,91 +223,6 @@ function getTypesPathWithFallback( return { typesPath: typesPath, entrypointUsed: chosenEntrypoint }; } -/** - * Finds the path to the types of a package using the package's export map or types/typings field. - * If the path is found, it is returned. Otherwise it returns undefined. - * - * This implementation uses resolve.exports to resolve the path to types for a level. - * - * @param packageJson - The package.json object to check for types paths. - * @param level - An API level to get types paths for. - * @returns A package relative path to the types. - * - * @remarks - * - * This implementation loosely follows TypeScript's process for finding types as described at - * {@link https://www.typescriptlang.org/docs/handbook/modules/reference.html#packagejson-main-and-types}. If an export - * map is found, the `types` and `typings` field are ignored. If an export map is not found, then the `types`/`typings` - * fields will be used as a fallback _only_ for the public API level (which corresponds to the default export). - * - * Importantly, this code _does not_ implement falling back to the `main` field when `types` and `typings` are missing, - * nor does it look up types from DefinitelyTyped (i.e. \@types/* packages). This fallback logic is not needed for our - * packages because we always specify types explicitly in the types field, and types are always included in our packages - * (as opposed to a separate \@types package). - */ -export function getTypesPathFromPackage( - packageJson: PackageJson, - level: ApiLevel, - log: Logger, -): string | undefined { - if (packageJson.exports === undefined) { - log.verbose(`${packageJson.name}: No export map found.`); - // Use types/typings field only when the public API level is used and no exports field is found - if (level === ApiLevel.public) { - log.verbose(`${packageJson.name}: Using the types/typings field value.`); - return packageJson.types ?? packageJson.typings; - } - // No exports and a non-public API level, so return undefined. - return undefined; - } - - // Package has an export map, so map the requested API level to an entrypoint and check the exports conditions. - const entrypoint = level === ApiLevel.public ? "." : `./${level}`; - - // resolve.exports sets some conditions by default, so the ones we supply supplement the defaults. For clarity the - // applied conditions are noted in comments. - let typesPath: string | undefined; - try { - // First try to resolve with the "import" condition, assuming the package is either ESM-only or dual-format. - // conditions: ["default", "types", "import", "node"] - const exports = resolve.exports(packageJson, entrypoint, { conditions: ["types"] }); - - // resolve.exports returns a `Exports.Output | void` type, though the documentation isn't clear under what - // conditions `void` would be the return type vs. just throwing an exception. Since the types say exports could be - // undefined or an empty array (Exports.Output is an array type), check for those conditions. - typesPath = exports === undefined || exports.length === 0 ? undefined : exports[0]; - } catch { - // Catch and ignore any exceptions here; we'll retry with the require condition. - log.verbose( - `${packageJson.name}: No types found for ${entrypoint} using "import" condition.`, - ); - } - - // Found the types using the import condition, so return early. - if (typesPath !== undefined) { - return typesPath; - } - - try { - // If nothing is found when using the "import" condition, try the "require" condition. It may be possible to do this - // in a single call to resolve.exports, but the documentation is a little unclear. This seems a safe, if inelegant - // solution. - // conditions: ["default", "types", "require", "node"] - const exports = resolve.exports(packageJson, entrypoint, { - conditions: ["types"], - require: true, - }); - typesPath = exports === undefined || exports.length === 0 ? undefined : exports[0]; - } catch { - // Catch and ignore any exceptions here; we'll retry with the require condition. - log.verbose( - `${packageJson.name}: No types found for ${entrypoint} using "require" condition.`, - ); - } - - return typesPath; -} - /** * Calculates the file path for type validation tests. * @@ -619,10 +536,13 @@ export function generateCompatibilityTestCases( * Returns the name of the type preprocessing type meta-function to use, or undefined if no type test should be generated. */ function selectTypePreprocessor(typeData: TypeData): string | undefined { - if (typeData.tags.has("type-test-minimal")) { + if (typeData.tags.has("system")) { + return undefined; + } + if (typeData.tags.has("typeTestMinimal")) { return "MinimalType"; } - if (typeData.tags.has("type-test-full")) { + if (typeData.tags.has("typeTestFull")) { return "FullType"; } return "TypeOnly"; diff --git a/build-tools/packages/build-cli/src/commands/merge/branches.ts b/build-tools/packages/build-cli/src/commands/merge/branches.ts index f3cf18b44684..173012d336ff 100644 --- a/build-tools/packages/build-cli/src/commands/merge/branches.ts +++ b/build-tools/packages/build-cli/src/commands/merge/branches.ts @@ -6,7 +6,7 @@ import { strict as assert } from "node:assert"; import { Logger } from "@fluidframework/build-tools"; import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import { BaseCommand, diff --git a/build-tools/packages/build-cli/src/commands/merge/info.ts b/build-tools/packages/build-cli/src/commands/merge/info.ts index 2f19b1ea8017..0a95427b56ec 100644 --- a/build-tools/packages/build-cli/src/commands/merge/info.ts +++ b/build-tools/packages/build-cli/src/commands/merge/info.ts @@ -4,7 +4,7 @@ */ import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import { BaseCommand, Repository } from "../../library/index.js"; @@ -100,7 +100,7 @@ export default class MergeInfoCommand extends BaseCommand { static readonly summary = "Releases a package or release group."; - static readonly description = `The release command ensures that a release branch is in good condition, then walks the user through releasing a package or release group. + static readonly description = + `The release command ensures that a release branch is in good condition, then walks the user through releasing a package or release group. The command runs a number of checks automatically to make sure the branch is in a good state for a release. If any of the dependencies are also in the repo, then they're checked for the latest release version. If the dependencies have not yet been released, then the command prompts to perform the release of the dependency, then run the release command again. diff --git a/build-tools/packages/build-cli/src/commands/release/history.ts b/build-tools/packages/build-cli/src/commands/release/history.ts index 98f4a5cdf782..2dae18374090 100644 --- a/build-tools/packages/build-cli/src/commands/release/history.ts +++ b/build-tools/packages/build-cli/src/commands/release/history.ts @@ -4,7 +4,7 @@ */ import { Flags } from "@oclif/core"; -import chalk from "chalk"; +import chalk from "picocolors"; import { table } from "table"; import { @@ -36,7 +36,8 @@ const DEFAULT_MIN_VERSION = "0.0.0"; export default class ReleaseHistoryCommand extends ReleaseReportBaseCommand< typeof ReleaseHistoryCommand > { - static readonly description = `Prints a list of released versions of a package or release group. Releases are gathered from the git tags in repo containing the working directory. + static readonly description = + `Prints a list of released versions of a package or release group. Releases are gathered from the git tags in repo containing the working directory. Use 'npm view' to list published packages based on the public npm registry. @@ -136,7 +137,7 @@ export default class ReleaseHistoryCommand extends ReleaseReportBaseCommand< const bumpType = detectBumpType(displayPreviousVersion, ver.version); const displayBumpType = highlight(`${bumpType}`); - const displayVersionSection = chalk.grey( + const displayVersionSection = chalk.gray( `${highlight(ver.version)} <-- ${displayPreviousVersion}`, ); diff --git a/build-tools/packages/build-cli/src/commands/release/prepare.ts b/build-tools/packages/build-cli/src/commands/release/prepare.ts index 65d7bdef148b..689f649de315 100644 --- a/build-tools/packages/build-cli/src/commands/release/prepare.ts +++ b/build-tools/packages/build-cli/src/commands/release/prepare.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import chalk from "chalk"; +import chalk from "picocolors"; import { findPackageOrReleaseGroup, packageOrReleaseGroupArg } from "../../args.js"; import { BaseCommand } from "../../library/index.js"; @@ -71,14 +71,16 @@ export class ReleasePrepareCommand extends BaseCommand { - static readonly description = `Updates configuration for type tests in package.json files. If the previous version changes after running preparation, then npm install must be run before building. + static readonly description = + `Updates configuration for type tests in package.json files. If the previous version changes after running preparation, then npm install must be run before building. Optionally, any type tests that are marked "broken" in package.json can be reset using the --reset flag during configuration. This is useful when resetting the type tests to a clean state, such as after a release. diff --git a/build-tools/packages/build-cli/src/handlers/doFunctions.ts b/build-tools/packages/build-cli/src/handlers/doFunctions.ts index 8663a5e7f87a..f9dea5083a09 100644 --- a/build-tools/packages/build-cli/src/handlers/doFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/doFunctions.ts @@ -4,8 +4,8 @@ */ import { strict as assert } from "node:assert"; -import chalk from "chalk"; import { Machine } from "jssm"; +import chalk from "picocolors"; import { FluidRepo, MonoRepo } from "@fluidframework/build-tools"; diff --git a/build-tools/packages/build-cli/src/handlers/fluidReleaseStateHandler.ts b/build-tools/packages/build-cli/src/handlers/fluidReleaseStateHandler.ts index 2b486a22daf1..7892c944aa0a 100644 --- a/build-tools/packages/build-cli/src/handlers/fluidReleaseStateHandler.ts +++ b/build-tools/packages/build-cli/src/handlers/fluidReleaseStateHandler.ts @@ -4,8 +4,8 @@ */ import { Command } from "@oclif/core"; -import chalk from "chalk"; import { Machine } from "jssm"; +import chalk from "picocolors"; import { Context } from "../library/index.js"; @@ -54,7 +54,7 @@ import { promptToRunMinorReleaseCommand, promptToRunTypeTests, } from "./promptFunctions.js"; -import { BaseStateHandler } from "./stateHandlers.js"; +import { BaseStateHandler, type StateHandlerFunction } from "./stateHandlers.js"; /** * Data that is passed to all the handling functions for the {@link FluidReleaseMachine}. This data is intended to be @@ -145,6 +145,83 @@ export interface FluidReleaseStateHandlerData { * this class; it only acts as a "router" to route to the correct function based on the current state. */ export class FluidReleaseStateHandler extends InitFailedStateHandler { + /** + * A map of state machine states to the function that should be called to handle that state. + */ + private readonly stateHandlerMap: Map = new Map([ + ["AskForReleaseType", askForReleaseType], + ["CheckAssertTagging", checkAssertTagging], + ["CheckBranchName", checkBranchName], + ["CheckBranchName2", checkBranchName], + ["CheckBranchName3", checkBranchName], + ["CheckBranchUpToDate", checkBranchUpToDate], + ["CheckChangelogs", checkChangelogs], + ["CheckDependenciesInstalled", checkDependenciesInstalled], + ["CheckDoesReleaseFromReleaseBranch", checkDoesReleaseFromReleaseBranch], + ["CheckDoesReleaseFromReleaseBranch2", checkDoesReleaseFromReleaseBranch], + ["CheckDoesReleaseFromReleaseBranch3", checkDoesReleaseFromReleaseBranch], + ["CheckHasRemote", checkHasRemote], + ["CheckMainNextIntegrated", checkMainNextIntegrated], + ["CheckNoPrereleaseDependencies", checkNoPrereleaseDependencies], + ["CheckNoPrereleaseDependencies2", checkNoPrereleaseDependencies], + ["CheckNoPrereleaseDependencies3", checkNoPrereleaseDependencies], + ["CheckOnReleaseBranch", checkOnReleaseBranch], + ["CheckOnReleaseBranch", checkOnReleaseBranch], + ["CheckOnReleaseBranch", checkOnReleaseBranch], + ["CheckOnReleaseBranch", checkOnReleaseBranch], + ["CheckOnReleaseBranch", checkOnReleaseBranch], + ["CheckOnReleaseBranch2", checkOnReleaseBranch], + ["CheckOnReleaseBranch3", checkOnReleaseBranch], + ["CheckPolicy", checkPolicy], + ["CheckReleaseBranchExists", checkReleaseBranchExists], + ["CheckReleaseGroupIsBumped", checkReleaseGroupIsBumped], + ["CheckReleaseGroupIsBumpedMinor", checkReleaseGroupIsBumped], + ["CheckReleaseGroupIsBumpedMinor2", checkReleaseGroupIsBumped], + ["CheckReleaseGroupIsBumpedPatch", checkReleaseGroupIsBumped], + ["CheckReleaseGroupIsBumpedPatch2", checkReleaseGroupIsBumped], + ["CheckReleaseIsDone", checkReleaseIsDone], + ["CheckReleaseIsDone2", checkReleaseIsDone], + ["CheckReleaseIsDone3", checkReleaseIsDone], + ["CheckReleaseNotes", checkReleaseNotes], + ["CheckShouldCommitBump", checkShouldCommit], + ["CheckShouldCommitDeps", checkShouldCommit], + ["CheckShouldCommitReleasedDepsBump", checkShouldCommitReleasedDepsBump], + ["CheckShouldRunOptionalChecks", checkShouldRunOptionalChecks], + ["CheckTypeTestGenerate", checkTypeTestGenerate], + ["CheckTypeTestGenerate2", checkTypeTestGenerate], + ["CheckTypeTestPrepare", checkTypeTestPrepare], + ["CheckTypeTestPrepare2", checkTypeTestPrepare], + ["CheckValidReleaseGroup", checkValidReleaseGroup], + ["DoBumpReleasedDependencies", doBumpReleasedDependencies], + ["DoMajorRelease", handleBumpType], + ["DoMinorRelease", handleBumpType], + ["DoPatchRelease", handleBumpType], + ["DoReleaseGroupBump", doReleaseGroupBump], + ["PromptToCommitBump", promptToCommitChanges], + ["PromptToCommitDeps", promptToCommitChanges], + ["PromptToCommitPolicy", promptToCommitChanges], + ["PromptToCommitReleasedDepsBump", promptToCommitChanges], + ["PromptToCreateReleaseBranch", promptToCreateReleaseBranch], + ["PromptToGenerateChangelogs", promptToGenerateChangelogs], + ["PromptToGenerateReleaseNotes", promptToGenerateReleaseNotes], + ["PromptToIntegrateNext", promptToIntegrateNext], + ["PromptToPRBump", promptToPRBump], + ["PromptToPRDeps", promptToPRDeps], + ["PromptToPRReleasedDepsBump", promptToPRDeps], + ["PromptToRelease", promptToRelease], + ["PromptToReleaseDeps", promptToReleaseDeps], + ["PromptToRunMinorReleaseCommand", promptToRunMinorReleaseCommand], + ["PromptToRunTypeTests", promptToRunTypeTests], + + [ + "ReleaseComplete", + async (_, __, ___, log): Promise => { + log.info(chalk.green("Release complete!")); + return true; + }, + ], + ]); + async handleState( state: MachineState, machine: Machine, @@ -152,235 +229,36 @@ export class FluidReleaseStateHandler extends InitFailedStateHandler { log: CommandLogger, data: FluidReleaseStateHandlerData, ): Promise { - let superShouldHandle = false; - - switch (state) { - case "AskForReleaseType": { - await askForReleaseType(state, machine, testMode, log, data); - break; - } - - case "CheckShouldRunOptionalChecks": { - await checkShouldRunOptionalChecks(state, machine, testMode, log, data); - break; - } - - case "CheckValidReleaseGroup": { - await checkValidReleaseGroup(state, machine, testMode, log, data); - break; - } - - case "CheckPolicy": { - await checkPolicy(state, machine, testMode, log, data); - break; - } - - case "CheckAssertTagging": { - await checkAssertTagging(state, machine, testMode, log, data); - break; - } - - case "CheckReleaseNotes": { - await checkReleaseNotes(state, machine, testMode, log, data); - break; - } - - case "CheckChangelogs": { - await checkChangelogs(state, machine, testMode, log, data); - break; - } - - case "CheckHasRemote": { - await checkHasRemote(state, machine, testMode, log, data); - break; - } - - case "CheckBranchUpToDate": { - await checkBranchUpToDate(state, machine, testMode, log, data); - break; - } - - case "CheckNoPrereleaseDependencies3": - case "CheckNoPrereleaseDependencies2": - case "CheckNoPrereleaseDependencies": { - await checkNoPrereleaseDependencies(state, machine, testMode, log, data); - break; - } - - case "DoPatchRelease": - case "DoMinorRelease": - case "DoMajorRelease": { - if (testMode) return true; - - const { bumpType } = data; - - if (bumpType === undefined) { - BaseStateHandler.signalFailure(machine, state); - } - - BaseStateHandler.signalSuccess(machine, state); - break; - } - - case "CheckBranchName": - case "CheckBranchName2": - case "CheckBranchName3": { - await checkBranchName(state, machine, testMode, log, data); - break; - } - - case "CheckDoesReleaseFromReleaseBranch": - case "CheckDoesReleaseFromReleaseBranch2": - case "CheckDoesReleaseFromReleaseBranch3": { - await checkDoesReleaseFromReleaseBranch(state, machine, testMode, log, data); - break; - } - - case "CheckDependenciesInstalled": { - await checkDependenciesInstalled(state, machine, testMode, log, data); - break; - } - - case "CheckMainNextIntegrated": { - await checkMainNextIntegrated(state, machine, testMode, log, data); - break; - } - - case "CheckOnReleaseBranch": - case "CheckOnReleaseBranch2": - case "CheckOnReleaseBranch3": { - await checkOnReleaseBranch(state, machine, testMode, log, data); - break; - } - - case "CheckReleaseIsDone": - case "CheckReleaseIsDone2": - case "CheckReleaseIsDone3": { - await checkReleaseIsDone(state, machine, testMode, log, data); - break; - } - - case "CheckReleaseGroupIsBumped": - case "CheckReleaseGroupIsBumpedMinor": - case "CheckReleaseGroupIsBumpedMinor2": - case "CheckReleaseGroupIsBumpedPatch": - case "CheckReleaseGroupIsBumpedPatch2": { - await checkReleaseGroupIsBumped(state, machine, testMode, log, data); - break; - } - - case "CheckTypeTestGenerate": - case "CheckTypeTestGenerate2": { - await checkTypeTestGenerate(state, machine, testMode, log, data); - break; - } + const handlerFunction = this.stateHandlerMap.get(state); - case "CheckTypeTestPrepare": - case "CheckTypeTestPrepare2": { - await checkTypeTestPrepare(state, machine, testMode, log, data); - break; - } - - case "DoReleaseGroupBump": { - await doReleaseGroupBump(state, machine, testMode, log, data); - break; - } - - case "DoBumpReleasedDependencies": { - await doBumpReleasedDependencies(state, machine, testMode, log, data); - break; - } - - case "CheckReleaseBranchExists": { - await checkReleaseBranchExists(state, machine, testMode, log, data); - break; - } - - case "CheckShouldCommitBump": - case "CheckShouldCommitDeps": { - await checkShouldCommit(state, machine, testMode, log, data); - break; - } - - case "CheckShouldCommitReleasedDepsBump": { - await checkShouldCommitReleasedDepsBump(state, machine, testMode, log, data); - break; - } - - case "PromptToCreateReleaseBranch": { - await promptToCreateReleaseBranch(state, machine, testMode, log, data); - break; - } - - case "PromptToGenerateChangelogs": { - await promptToGenerateChangelogs(state, machine, testMode, log, data); - break; - } - - case "PromptToGenerateReleaseNotes": { - await promptToGenerateReleaseNotes(state, machine, testMode, log, data); - break; - } - - case "PromptToIntegrateNext": { - await promptToIntegrateNext(state, machine, testMode, log, data); - break; - } - - case "PromptToRelease": { - await promptToRelease(state, machine, testMode, log, data); - break; - } - - case "PromptToPRDeps": - case "PromptToPRReleasedDepsBump": { - await promptToPRDeps(state, machine, testMode, log, data); - break; - } - - case "PromptToPRBump": { - await promptToPRBump(state, machine, testMode, log, data); - break; - } - - case "PromptToCommitBump": - case "PromptToCommitDeps": - case "PromptToCommitPolicy": - case "PromptToCommitReleasedDepsBump": { - await promptToCommitChanges(state, machine, testMode, log, data); - break; - } - - case "PromptToReleaseDeps": { - await promptToReleaseDeps(state, machine, testMode, log, data); - break; - } - - case "PromptToRunMinorReleaseCommand": { - await promptToRunMinorReleaseCommand(state, machine, testMode, log, data); - break; - } - - case "PromptToRunTypeTests": { - await promptToRunTypeTests(state, machine, testMode, log, data); - break; - } - - case "ReleaseComplete": { - log.info(chalk.green("Release complete!")); - break; - } - - default: { - superShouldHandle = true; - } - } - - if (superShouldHandle === true) { + if (handlerFunction === undefined) { const superHandled = await super.handleState(state, machine, testMode, log, data); return superHandled; } + await handlerFunction(state, machine, testMode, log, data); return true; } } + +/** + * Checks if the bump type is defined in the handler data and signals success if it is set and failure otherwise. + */ +const handleBumpType: StateHandlerFunction = async ( + state, + machine, + testMode, + log, + data: FluidReleaseStateHandlerData, +): Promise => { + if (testMode) return true; + + const { bumpType } = data; + + if (bumpType === undefined) { + BaseStateHandler.signalFailure(machine, state); + } else { + BaseStateHandler.signalSuccess(machine, state); + } + return true; +}; diff --git a/build-tools/packages/build-cli/src/handlers/promptFunctions.ts b/build-tools/packages/build-cli/src/handlers/promptFunctions.ts index 325ca9ce6538..b2cc0505c2a3 100644 --- a/build-tools/packages/build-cli/src/handlers/promptFunctions.ts +++ b/build-tools/packages/build-cli/src/handlers/promptFunctions.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. */ -import chalk from "chalk"; import { Machine } from "jssm"; +import chalk from "picocolors"; import { type InstructionalPrompt, mapADOLinks } from "../instructionalPromptWriter.js"; import { diff --git a/build-tools/packages/build-cli/src/instructionalPromptWriter.ts b/build-tools/packages/build-cli/src/instructionalPromptWriter.ts index ce0eb8135923..a4c8f21e7863 100644 --- a/build-tools/packages/build-cli/src/instructionalPromptWriter.ts +++ b/build-tools/packages/build-cli/src/instructionalPromptWriter.ts @@ -4,7 +4,7 @@ */ import { StringBuilder } from "@rushstack/node-core-library"; -import chalk from "chalk"; +import chalk from "picocolors"; // eslint-disable-next-line import/no-deprecated import { MonoRepoKind, indentString } from "./library/index.js"; diff --git a/build-tools/packages/build-cli/src/library/commands/base.ts b/build-tools/packages/build-cli/src/library/commands/base.ts index eccfa07f1f90..90ec32f9aacf 100644 --- a/build-tools/packages/build-cli/src/library/commands/base.ts +++ b/build-tools/packages/build-cli/src/library/commands/base.ts @@ -6,7 +6,7 @@ import { Command, Flags, Interfaces } from "@oclif/core"; // eslint-disable-next-line import/no-internal-modules import type { PrettyPrintableError } from "@oclif/core/errors"; -import chalk from "chalk"; +import chalk from "picocolors"; import { GitRepo, getResolvedFluidRoot } from "@fluidframework/build-tools"; import { CommandLogger } from "../../logging.js"; @@ -279,7 +279,7 @@ export abstract class BaseCommand */ public verbose(message: string | Error | undefined): void { if (this.flags.verbose === true) { - const color = typeof message === "string" ? chalk.grey : chalk.red; + const color = typeof message === "string" ? chalk.gray : chalk.red; this.log(color(`VERBOSE: ${message}`)); } } diff --git a/build-tools/packages/build-cli/src/library/package.ts b/build-tools/packages/build-cli/src/library/package.ts index 4dcdc3db0df0..ebfc17521f55 100644 --- a/build-tools/packages/build-cli/src/library/package.ts +++ b/build-tools/packages/build-cli/src/library/package.ts @@ -4,7 +4,7 @@ */ import { strict as assert } from "node:assert"; -import { writeFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { InterdependencyRange, @@ -28,12 +28,14 @@ import { PackageName } from "@rushstack/node-core-library"; import { compareDesc, differenceInBusinessDays } from "date-fns"; import execa from "execa"; import { readJson, readJsonSync } from "fs-extra/esm"; +import JSON5 from "json5"; import latestVersion from "latest-version"; import ncu from "npm-check-updates"; import type { Index } from "npm-check-updates/build/src/types/IndexType.js"; import type { VersionSpec } from "npm-check-updates/build/src/types/VersionSpec.js"; import * as semver from "semver"; +import type { TsConfigJson } from "type-fest"; import { AllPackagesSelectionCriteria, PackageSelectionCriteria, @@ -936,3 +938,18 @@ export function getTarballName(pkg: PackageJson | string): string { export function getFullTarballName(pkg: PackageJson): string { return `${getTarballName(pkg)}-${pkg?.version ?? 0}.tgz`; } + +/** + * Reads and parses the `package.json` file in the current directory. + * Use this function if you prefer the CLI command not to be implemented as `PackageCommand`command. + */ +export async function readPackageJson(): Promise { + const packageJson = await readFile("./package.json", { encoding: "utf8" }); + return JSON.parse(packageJson) as PackageJson; +} + +// Reads and parses the `tsconfig.json` file in the current directory. +export async function readTsConfig(): Promise { + const tsConfigContent = await readFile("./tsconfig.json", { encoding: "utf8" }); + return JSON5.parse(tsConfigContent); +} diff --git a/build-tools/packages/build-cli/src/library/packageExports.ts b/build-tools/packages/build-cli/src/library/packageExports.ts index 3e9519019ab7..4ee222f4921f 100644 --- a/build-tools/packages/build-cli/src/library/packageExports.ts +++ b/build-tools/packages/build-cli/src/library/packageExports.ts @@ -6,6 +6,8 @@ import path from "node:path"; import type { Logger, PackageJson } from "@fluidframework/build-tools"; +import * as resolve from "resolve.exports"; +import { ApiLevel } from "./apiLevel.js"; /** * Properties for an "exports" leaf entry block in package.json. @@ -244,3 +246,88 @@ export function queryTypesResolutionPathsFromPackageExports( return { mapKeyToOutput, mapNode10CompatExportPathToData, mapTypesPathToExportPaths }; } + +/** + * Finds the path to the types of a package using the package's export map or types/typings field. + * If the path is found, it is returned. Otherwise it returns undefined. + * + * This implementation uses resolve.exports to resolve the path to types for a level. + * + * @param packageJson - The package.json object to check for types paths. + * @param level - An API level to get types paths for. + * @returns A package relative path to the types. + * + * @remarks + * + * This implementation loosely follows TypeScript's process for finding types as described at + * {@link https://www.typescriptlang.org/docs/handbook/modules/reference.html#packagejson-main-and-types}. If an export + * map is found, the `types` and `typings` field are ignored. If an export map is not found, then the `types`/`typings` + * fields will be used as a fallback _only_ for the public API level (which corresponds to the default export). + * + * Importantly, this code _does not_ implement falling back to the `main` field when `types` and `typings` are missing, + * nor does it look up types from DefinitelyTyped (i.e. \@types/* packages). This fallback logic is not needed for our + * packages because we always specify types explicitly in the types field, and types are always included in our packages + * (as opposed to a separate \@types package). + */ +export function getTypesPathFromPackage( + packageJson: PackageJson, + level: ApiLevel, + log: Logger, +): string | undefined { + if (packageJson.exports === undefined) { + log.verbose(`${packageJson.name}: No export map found.`); + // Use types/typings field only when the public API level is used and no exports field is found + if (level === ApiLevel.public) { + log.verbose(`${packageJson.name}: Using the types/typings field value.`); + return packageJson.types ?? packageJson.typings; + } + // No exports and a non-public API level, so return undefined. + return undefined; + } + + // Package has an export map, so map the requested API level to an entrypoint and check the exports conditions. + const entrypoint = level === ApiLevel.public ? "." : `./${level}`; + + // resolve.exports sets some conditions by default, so the ones we supply supplement the defaults. For clarity the + // applied conditions are noted in comments. + let typesPath: string | undefined; + try { + // First try to resolve with the "import" condition, assuming the package is either ESM-only or dual-format. + // conditions: ["default", "types", "import", "node"] + const exports = resolve.exports(packageJson, entrypoint, { conditions: ["types"] }); + + // resolve.exports returns a `Exports.Output | void` type, though the documentation isn't clear under what + // conditions `void` would be the return type vs. just throwing an exception. Since the types say exports could be + // undefined or an empty array (Exports.Output is an array type), check for those conditions. + typesPath = exports === undefined || exports.length === 0 ? undefined : exports[0]; + } catch { + // Catch and ignore any exceptions here; we'll retry with the require condition. + log.verbose( + `${packageJson.name}: No types found for ${entrypoint} using "import" condition.`, + ); + } + + // Found the types using the import condition, so return early. + if (typesPath !== undefined) { + return typesPath; + } + + try { + // If nothing is found when using the "import" condition, try the "require" condition. It may be possible to do this + // in a single call to resolve.exports, but the documentation is a little unclear. This seems a safe, if inelegant + // solution. + // conditions: ["default", "types", "require", "node"] + const exports = resolve.exports(packageJson, entrypoint, { + conditions: ["types"], + require: true, + }); + typesPath = exports === undefined || exports.length === 0 ? undefined : exports[0]; + } catch { + // Catch and ignore any exceptions here; we'll retry with the require condition. + log.verbose( + `${packageJson.name}: No types found for ${entrypoint} using "require" condition.`, + ); + } + + return typesPath; +} diff --git a/build-tools/packages/build-cli/src/stateMachineCommand.ts b/build-tools/packages/build-cli/src/stateMachineCommand.ts index 0fd5e2c41135..ad3d338ee6f2 100644 --- a/build-tools/packages/build-cli/src/stateMachineCommand.ts +++ b/build-tools/packages/build-cli/src/stateMachineCommand.ts @@ -4,8 +4,8 @@ */ import { Command, Flags } from "@oclif/core"; -import chalk from "chalk"; import { Machine } from "jssm"; +import chalk from "picocolors"; import { testModeFlag } from "./flags.js"; import { StateHandler } from "./handlers/index.js"; diff --git a/build-tools/packages/build-cli/src/typeValidator/typeData.ts b/build-tools/packages/build-cli/src/typeValidator/typeData.ts index 01b206c73075..57cc5ecf01ed 100644 --- a/build-tools/packages/build-cli/src/typeValidator/typeData.ts +++ b/build-tools/packages/build-cli/src/typeValidator/typeData.ts @@ -44,16 +44,19 @@ export function toTypeString( // does the type take generics that don't have defaults? // eslint-disable-next-line unicorn/no-lonely-if -- logic is clearer when grouped this way. if ( - node.getTypeParameters().length > 0 && - node.getTypeParameters().some((tp) => tp.getDefault() === undefined) + node + .getTypeParameters() + .some((typeParameter) => typeParameter.getDefault() === undefined) ) { - // it's really hard to build the right type for a generic, - // so for now we'll just pass any, as it will always work - // even though it may defeat the utility of a type or related test. + // In general there is no single correct value to test for the type parameters, + // so for now we'll just pass `never`, as it will always be valid. + // This may result in a type test with very little utility since most APIs aren't intended to be used with "never", + // and doing so is unlikely to test most of the actual use-cases of the generic type. + // `Never` is used instead of `any` since some contravariant generics constrain the input to `never`, and `any` is not assignable to `never`. typeParams = `<${node .getTypeParameters() - .filter((tp) => tp.getDefault() === undefined) - .map(() => "any") + .filter((typeParameter) => typeParameter.getDefault() === undefined) + .map(() => "never") .join(",")}>`; } } diff --git a/build-tools/packages/build-infrastructure/README.md b/build-tools/packages/build-infrastructure/README.md index f98e1d20a2a1..4b101b5e7d7a 100644 --- a/build-tools/packages/build-infrastructure/README.md +++ b/build-tools/packages/build-infrastructure/README.md @@ -5,21 +5,21 @@ This package contains types and helper functions that are used across multiple b The primary purpose of this package is to provide a common way to organize npm packages into groups called release groups, and leverages workspaces functionality provided by package managers like npm, yarn, and pnpm to manage -interdependencies between packages across a Fluid repo. It then provides APIs to select, filter, and work with those +interdependencies between packages across a build project. It then provides APIs to select, filter, and work with those package groups. ## API Overview -The API is built around four key types which form a hierarchy: `IFluidRepo`, `IWorkspace`, `IReleaseGroup`, and -`IPackage`. For the purposes of this documentation, the terms "Fluid repo," "workspace," "release group," and "package" +The API is built around four key types which form a hierarchy: `IBuildProject`, `IWorkspace`, `IReleaseGroup`, and +`IPackage`. For the purposes of this documentation, the terms "build project," "workspace," "release group," and "package" generally refer to these types. -Conceptually, a **Fluid repo** is a way to organize npm packages into groups for versioning, release, and dependency -management. A Fluid repo can contain multiple **workspaces**, each of which may contain one or more **release groups**. +Conceptually, a **build project** is a way to organize npm packages into groups for versioning, release, and dependency +management. A build project can contain multiple **workspaces**, each of which may contain one or more **release groups**. -### The Fluid repo +### The build project -The primary entrypoint for the API is the `IFluidRepo` type. A Fluid repo can contain multiple workspaces and release +The primary entrypoint for the API is the `IBuildProject` type. A build project can contain multiple workspaces and release groups. Both workspaces and release groups represent ways to organize packages in the repo, but their purpose and function are different. @@ -35,10 +35,10 @@ trivial to link multiple packages so they can depend on one another. The `IWorks these package manager features. Importantly, this package does not attempt to re-implement any features provided by workspaces themselves. Users are -expected to configure their package managers' workspace features in addition to the Fluid repo configuration. +expected to configure their package managers' workspace features in addition to the build project configuration. -A Fluid repo will only load packages identified by the package manager's workspace feature. That is, any package in the -repo that is not configured as part of a workspace is invisible to tools using the Fluid repo. +A build project will only load packages identified by the package manager's workspace feature. That is, any package in the +repo that is not configured as part of a workspace is invisible to tools using the build project. ### Release groups @@ -71,9 +71,9 @@ another larger workspace, contained within a single-package release group. ### Git repo capabilities -A Fluid repo is often contained within a Git repository, and some functionality expects to be used within a Git +A build project is often contained within a Git repository, and some functionality expects to be used within a Git repository. Features that need to execute Git operations can asynchronously retrieve the SimpleGit instance using the -`IFluidRepo.getGitRepository` API. If the Fluid repo is not within a Git repo, then that call will throw a +`IBuildProject.getGitRepository` API. If the build project is not within a Git repo, then that call will throw a `NotInGitRepository` exception that callers should handle appropriately. If they don't, though, the exception makes it clear what has happened. @@ -82,33 +82,33 @@ clear what has happened. > This design addresses a major problem with build-tools v0, which was that code often made assumptions that it was > operating within a Git repo. That's often true, and some fetures can and should only work in that context, but the > implementation attempted to load the Git functionality blindly and would fail outright outside a Git context. With -> `IFluidRepo`, the Git integration is more loosely coupled and the APIs make it clearer that it is not safe to assume +> `IBuildProject`, the Git integration is more loosely coupled and the APIs make it clearer that it is not safe to assume > the presence of a Git repo. ### Package selection and filtering APIs -The `IFluidRepo` object provides access to workspaces, release groups, and their constituent packages, but often one wants +The `IBuildProject` object provides access to workspaces, release groups, and their constituent packages, but often one wants to operate on a subset of all packages in the repo. To support this, build-infrastructure provides a selection and filtering API. Packages can be selected based on criteria like workspace and release group, and the lists can be further filtered by scope or private/not private. Advanced filtering not covered by the built-in filters can be implemented using `Array.prototype.filter` on the results of package selection. -### Built-in command-line tool to examine repo layout and config +### Built-in command-line tool to examine project layout and config -The included CLI tool makes it easy to examine the contents and layout of a Fluid repo. See [the CLI +The included CLI tool makes it easy to examine the contents and layout of a build project. See [the CLI documentation](./docs/cli.md) for more information. ### Loading old config formats -The `repoPackages` configuration currently used by fluid-build will be loaded if the newer `repoLayout` config can't be -found. This is for back-compat only and will not be maintained indefinitely. Users should convert to `repoLayout` when +The `repoPackages` configuration currently used by fluid-build will be loaded if the newer `buildProject` config can't be +found. This is for back-compat only and will not be maintained indefinitely. Users should convert to `buildProject` when possible. ## Configuration -Configuration for the repo layout is stored in a config file at the root of the repo. This can either be part of the -`fluidBuild.config.cjs` file in the `repoLayout` property, or in an independent config file named -`repoLayout.config.cjs` (or mjs). +Configuration for the build project is stored in a config file at the root of the repo. This can either be part of the +`fluidBuild.config.cjs` file in the `buildProject` property, or in an independent config file named +`buildProject.config.cjs` (or mjs). ### Example @@ -117,15 +117,15 @@ groups, a workspace with a single release group that contains multiple packages, group that contains a single package. ```js -repoLayout: { +buildProject: { workspaces: { - // This is the name of the workspace which is how it's referenced in the API. All workspaces in a Fluid repo must + // This is the name of the workspace which is how it's referenced in the API. All workspaces in a build project must // have a unique name. "client": { // This workspace is rooted at the root of the Git repo. directory: ".", releaseGroups: { - // This key is the name of the release group. All release groups in a Fluid repo must have a unique name. + // This key is the name of the release group. All release groups in a build project must have a unique name. client: { // The include property can contain package names OR package scopes. If // a scope is provided, all packages with that scope will be a part of @@ -161,7 +161,7 @@ repoLayout: { rootPackageName: "examples-release-group-root", }, // If any packages in the workspace don't match a release group, loading the - // repo layout config will throw an error. + // build project config will throw an error. }, }, "build-tools": { @@ -193,13 +193,13 @@ repoLayout: { } ``` -### Loading a Fluid repo from a configuration file +### Loading a build project from a configuration file -To load a Fluid repo, you use the `loadFluidRepo` function. You can pass in a path to a Git repository root, or if one +To load a build project, you use the `loadBuildProject` function. You can pass in a path to a Git repository root, or if one is not provided, then the Git repository nearest to the working directory can be used. -This function will look for a repo layout configuration in that folder and load the workspaces, release groups, and -packages accordingly and return an `IFluidRepo` object that includes Maps of workspaces, release groups, and packages as +This function will look for a build project configuration in that folder and load the workspaces, release groups, and +packages accordingly and return an `IBuildProject` object that includes Maps of workspaces, release groups, and packages as properties. ## Other APIs @@ -215,17 +215,17 @@ The `PackageBase` abstract class can be used as a base class to create custom `I ## Miscellaneous improvements -### Fluid repos can be rooted anywhere +### Build projects can be rooted anywhere -Fluid repos are rooted where their config file is located, _not_ at the root of a Git repo. There can be multiple Fluid -repos within a Git repo, though this is usually only needed for testing. In typical use only a single Fluid repo per -Git repo is needed. However, the Fluid repo does _not_ need to be rooted at the root of Git repo, and code should not -assume that the root of the Fluid repo is the same as the root of a Git repo. +Build projects are rooted where their config file is located, _not_ at the root of a Git repo. There can be multiple Fluid +repos within a Git repo, though this is usually only needed for testing. In typical use only a single build project per +Git repo is needed. However, the build project does _not_ need to be rooted at the root of Git repo, and code should not +assume that the root of the build project is the same as the root of a Git repo. ### Better testing -There is now a test project within the repo that is a fully functional Fluid repo. There are basic unit tests that verify the -loading of the Fluid repo config and that packages are organized as expected. This is a dramatic improvement from v0 +There is now a test project within the repo that is a fully functional build project. There are basic unit tests that verify the +loading of the build project config and that packages are organized as expected. This is a dramatic improvement from v0 build-tools, in which all package traversal logic was effectively untested. There are also tests for the selection and filtering APIs. diff --git a/build-tools/packages/build-infrastructure/api-report/build-infrastructure.api.md b/build-tools/packages/build-infrastructure/api-report/build-infrastructure.api.md index f651bb726b03..d8e59ef330dc 100644 --- a/build-tools/packages/build-infrastructure/api-report/build-infrastructure.api.md +++ b/build-tools/packages/build-infrastructure/api-report/build-infrastructure.api.md @@ -6,6 +6,7 @@ import type { Opaque } from 'type-fest'; import type { PackageJson as PackageJson_2 } from 'type-fest'; +import { SemVer } from 'semver'; import type { SetRequired } from 'type-fest'; import { SimpleGit } from 'simple-git'; @@ -13,24 +14,11 @@ import { SimpleGit } from 'simple-git'; export type AdditionalPackageProps = Record | undefined; // @public -export function createPackageManager(name: PackageManagerName): IPackageManager; - -// @public -export interface FluidPackageJsonFields { - pnpm?: { - overrides?: Record; - }; -} - -// @public -export const FLUIDREPO_CONFIG_VERSION = 1; - -// @public -export class FluidRepoBase

implements IFluidRepo

{ +export class BuildProject

implements IBuildProject

{ constructor(searchPath: string, upstreamRemotePartialUrl?: string | undefined); protected readonly configFilePath: string; - readonly configuration: IFluidRepoLayout; + readonly configuration: BuildProjectLayout; getGitRepository(): Promise>; getPackageReleaseGroup(pkg: Readonly

): Readonly; get packages(): Map; @@ -43,18 +31,56 @@ export class FluidRepoBase

implements IFluidRepo

{ } // @public -export function getAllDependenciesInRepo(repo: IFluidRepo, packages: IPackage[]): { +export const BUILDPROJECT_CONFIG_VERSION = 1; + +// @public +export interface BuildProjectLayout { + buildProject?: { + workspaces: { + [name: string]: WorkspaceDefinition; + }; + }; + // @deprecated + repoPackages?: IFluidBuildDirs; + version: typeof BUILDPROJECT_CONFIG_VERSION; +} + +// @public +export function createPackageManager(name: PackageManagerName): IPackageManager; + +// @public +export interface FluidPackageJsonFields { + pnpm?: { + overrides?: Record; + }; +} + +// @public +export function getAllDependencies(repo: IBuildProject, packages: IPackage[]): { packages: IPackage[]; releaseGroups: IReleaseGroup[]; workspaces: IWorkspace[]; }; // @public -export function getFluidRepoLayout(searchPath: string, noCache?: boolean): { - config: IFluidRepoLayout; +export function getBuildProjectConfig(searchPath: string, noCache?: boolean): { + config: BuildProjectLayout; configFilePath: string; }; +// @public +export interface IBuildProject

extends Reloadable { + configuration: BuildProjectLayout; + getGitRepository(): Promise>; + getPackageReleaseGroup(pkg: Readonly

): Readonly; + packages: Map; + relativeToRepo(p: string): string; + releaseGroups: Map; + root: string; + upstreamRemotePartialUrl?: string; + workspaces: Map; +} + // @public @deprecated export interface IFluidBuildDir { directory: string; @@ -71,31 +97,6 @@ export interface IFluidBuildDirs { [name: string]: IFluidBuildDirEntry; } -// @public -export interface IFluidRepo

extends Reloadable { - configuration: IFluidRepoLayout; - getGitRepository(): Promise>; - getPackageReleaseGroup(pkg: Readonly

): Readonly; - packages: Map; - relativeToRepo(p: string): string; - releaseGroups: Map; - root: string; - upstreamRemotePartialUrl?: string; - workspaces: Map; -} - -// @public -export interface IFluidRepoLayout { - repoLayout?: { - workspaces: { - [name: string]: WorkspaceDefinition; - }; - }; - // @deprecated - repoPackages?: IFluidBuildDirs; - version: typeof FLUIDREPO_CONFIG_VERSION; -} - // @public export interface Installable { checkInstall(): Promise; @@ -151,8 +152,8 @@ export function isIReleaseGroup(toCheck: Exclude; @@ -162,7 +163,7 @@ export interface IWorkspace extends Installable, Reloadable { } // @public -export function loadFluidRepo

(searchPath: string, upstreamRemotePartialUrl?: string): IFluidRepo

; +export function loadBuildProject

(searchPath: string, upstreamRemotePartialUrl?: string): IBuildProject

; // @public export class NotInGitRepository extends Error { @@ -234,6 +235,9 @@ export interface Reloadable { reload(): void; } +// @public +export function setVersion(packages: IPackage[], version: SemVer): Promise; + // @public export interface WorkspaceDefinition { directory: string; diff --git a/build-tools/packages/build-infrastructure/package.json b/build-tools/packages/build-infrastructure/package.json index f544cdc3982d..c0e461da7b1e 100644 --- a/build-tools/packages/build-infrastructure/package.json +++ b/build-tools/packages/build-infrastructure/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/build-infrastructure", - "version": "0.45.0", + "version": "0.51.0", "private": true, "description": "Fluid build infrastructure", "homepage": "https://fluidframework.com", @@ -26,7 +26,7 @@ }, "main": "lib/index.js", "bin": { - "repo-layout": "./bin/run.mjs" + "buildProject": "./bin/run.mjs" }, "files": [ "/bin", @@ -63,7 +63,7 @@ "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist" }, "dependencies": { - "@fluid-tools/version-tools": "workspace:^", + "@fluid-tools/version-tools": "workspace:~", "@manypkg/get-packages": "^2.2.2", "@oclif/core": "^4.0.30", "cosmiconfig": "^8.3.6", @@ -111,8 +111,8 @@ "unionfs": "^4.5.4" }, "oclif": { - "bin": "repo-layout", - "dirname": "repo-layout", + "bin": "buildProject", + "dirname": "buildProject", "commands": "./lib/commands", "additionalHelpFlags": [ "-h" diff --git a/build-tools/packages/build-infrastructure/src/fluidRepo.ts b/build-tools/packages/build-infrastructure/src/buildProject.ts similarity index 55% rename from build-tools/packages/build-infrastructure/src/fluidRepo.ts rename to build-tools/packages/build-infrastructure/src/buildProject.ts index e8fae51a98ad..3b326c7dfbbb 100644 --- a/build-tools/packages/build-infrastructure/src/fluidRepo.ts +++ b/build-tools/packages/build-infrastructure/src/buildProject.ts @@ -5,13 +5,15 @@ import path from "node:path"; +import type { InterdependencyRange } from "@fluid-tools/version-tools"; +import * as semver from "semver"; import { type SimpleGit, simpleGit } from "simple-git"; -import { type IFluidRepoLayout, getFluidRepoLayout } from "./config.js"; +import { type BuildProjectConfig, getBuildProjectConfig } from "./config.js"; import { NotInGitRepository } from "./errors.js"; import { findGitRootSync } from "./git.js"; import { - type IFluidRepo, + type IBuildProject, type IPackage, type IReleaseGroup, type IWorkspace, @@ -23,18 +25,18 @@ import { Workspace } from "./workspace.js"; import { loadWorkspacesFromLegacyConfig } from "./workspaceCompat.js"; /** - * {@inheritDoc IFluidRepo} + * {@inheritDoc IBuildProject} */ -export class FluidRepo

implements IFluidRepo

{ +export class BuildProject

implements IBuildProject

{ /** - * The absolute path to the root of the FluidRepo. This is the path where the config file is located. + * The absolute path to the root of the build project. This is the path where the config file is located. */ public readonly root: string; /** - * {@inheritDoc IFluidRepo.configuration} + * {@inheritDoc IBuildProject.configuration} */ - public readonly configuration: IFluidRepoLayout; + public readonly configuration: BuildProjectConfig; /** * The absolute path to the config file. @@ -42,37 +44,37 @@ export class FluidRepo

implements IFluidRepo

{ protected readonly configFilePath: string; /** - * @param searchPath - The path that should be searched for a repo layout config file. - * @param gitRepository - A SimpleGit instance rooted in the root of the Git repository housing the FluidRepo. This - * should be set to false if the FluidRepo is not within a Git repository. + * @param searchPath - The path that should be searched for a BuildProject config file. + * @param gitRepository - A SimpleGit instance rooted in the root of the Git repository housing the BuildProject. This + * should be set to false if the BuildProject is not within a Git repository. */ public constructor( searchPath: string, /** - * {@inheritDoc IFluidRepo.upstreamRemotePartialUrl} + * {@inheritDoc IBuildProject.upstreamRemotePartialUrl} */ public readonly upstreamRemotePartialUrl?: string, ) { - const { config, configFilePath } = getFluidRepoLayout(searchPath); + const { config, configFilePath } = getBuildProjectConfig(searchPath); this.root = path.resolve(path.dirname(configFilePath)); this.configuration = config; this.configFilePath = configFilePath; - // Check for the repoLayout config first - if (config.repoLayout === undefined) { - // If there's no `repoLayout` _and_ no `repoPackages`, then we need to error since there's no loadable config. + // Check for the buildProject config first + if (config.buildProject === undefined) { + // If there's no `buildProject` _and_ no `repoPackages`, then we need to error since there's no loadable config. if (config.repoPackages === undefined) { throw new Error(`Can't find configuration.`); } else { console.warn( - `The repoPackages setting is deprecated and will no longer be read in a future version. Use repoLayout instead.`, + `The repoPackages setting is deprecated and will no longer be read in a future version. Use buildProject instead.`, ); this._workspaces = loadWorkspacesFromLegacyConfig(config.repoPackages, this); } } else { this._workspaces = new Map( - Object.entries(config.repoLayout.workspaces).map((entry) => { + Object.entries(config.buildProject.workspaces).map((entry) => { const name = entry[0] as WorkspaceName; const definition = entry[1]; const ws = Workspace.load(name, definition, this.root, this); @@ -96,7 +98,7 @@ export class FluidRepo

implements IFluidRepo

{ private readonly _workspaces: Map; /** - * {@inheritDoc IFluidRepo.workspaces} + * {@inheritDoc IBuildProject.workspaces} */ public get workspaces(): Map { return this._workspaces; @@ -105,14 +107,14 @@ export class FluidRepo

implements IFluidRepo

{ private readonly _releaseGroups: Map; /** - * {@inheritDoc IFluidRepo.releaseGroups} + * {@inheritDoc IBuildProject.releaseGroups} */ public get releaseGroups(): Map { return this._releaseGroups; } /** - * {@inheritDoc IFluidRepo.packages} + * {@inheritDoc IBuildProject.packages} */ public get packages(): Map { const pkgs: Map = new Map(); @@ -130,7 +132,7 @@ export class FluidRepo

implements IFluidRepo

{ } /** - * {@inheritDoc IFluidRepo.relativeToRepo} + * {@inheritDoc IBuildProject.relativeToRepo} */ public relativeToRepo(p: string): string { // Replace \ in result with / in case OS is Windows. @@ -138,7 +140,7 @@ export class FluidRepo

implements IFluidRepo

{ } /** - * Reload the Fluid repo by calling `reload` on each workspace in the repository. + * Reload the BuildProject by calling `reload` on each workspace in the repository. */ public reload(): void { for (const ws of this.workspaces.values()) { @@ -150,7 +152,7 @@ export class FluidRepo

implements IFluidRepo

{ private _checkedForGitRepo = false; /** - * {@inheritDoc IFluidRepo.getGitRepository} + * {@inheritDoc IBuildProject.getGitRepository} */ public async getGitRepository(): Promise> { if (this.gitRepository !== undefined) { @@ -170,7 +172,7 @@ export class FluidRepo

implements IFluidRepo

{ } /** - * {@inheritDoc IFluidRepo.getPackageReleaseGroup} + * {@inheritDoc IBuildProject.getPackageReleaseGroup} */ public getPackageReleaseGroup(pkg: Readonly

): Readonly { const found = this.releaseGroups.get(pkg.releaseGroup); @@ -183,28 +185,28 @@ export class FluidRepo

implements IFluidRepo

{ } /** - * Searches for a Fluid repo config file and loads the repo layout from the config if found. + * Searches for a BuildProject config file and loads the project from the config if found. * * @typeParam P - The type to use for Packages. - * @param searchPath - The path to start searching for a Fluid repo config. + * @param searchPath - The path to start searching for a BuildProject config. * @param upstreamRemotePartialUrl - A partial URL to the upstream repo. This is used to find the local git remote that * corresponds to the upstream repo. - * @returns The loaded Fluid repo. + * @returns The loaded BuildProject. */ -export function loadFluidRepo

( +export function loadBuildProject

( searchPath: string, upstreamRemotePartialUrl?: string, -): IFluidRepo

{ - const repo: IFluidRepo

= new FluidRepo

(searchPath, upstreamRemotePartialUrl); +): IBuildProject

{ + const repo = new BuildProject

(searchPath, upstreamRemotePartialUrl); return repo; } /** * Returns an object containing all the packages, release groups, and workspaces that a given set of packages depends - * on. This function only considers packages in the Fluid repo. + * on. This function only considers packages in the BuildProject repo. */ -export function getAllDependenciesInRepo( - repo: IFluidRepo, +export function getAllDependencies( + repo: IBuildProject, packages: IPackage[], ): { packages: IPackage[]; releaseGroups: IReleaseGroup[]; workspaces: IWorkspace[] } { const dependencyPackages: Set = new Set(); @@ -235,3 +237,56 @@ export function getAllDependenciesInRepo( workspaces: [...workspaces], }; } + +/** + * Sets the dependency range for a group of packages given a group of dependencies to update. + * The changes are written to package.json. After the update, the packages are + * reloaded so the in-memory data reflects the version changes. + * + * @param packagesToUpdate - A list of objects whose version should be updated. + * @param dependencies - A list of objects that the packagesToUpdate depend on that should have updated ranges. + * @param dependencyRange - The new version range to set for the packageToUpdate dependencies. + */ +export async function setDependencyRange

( + packagesToUpdate: Iterable

, + dependencies: Iterable

, + dependencyRange: InterdependencyRange, +): Promise { + const dependencySet = new Set(Array.from(dependencies, (d) => d.name)); + // collect the "save" promises to resolve in parallel + const savePromises: Promise[] = []; + + for (const pkg of packagesToUpdate) { + for (const { name: depName, depKind } of pkg.combinedDependencies) { + if (dependencySet.has(depName)) { + const depRange = + typeof dependencyRange === "string" + ? dependencyRange + : dependencyRange instanceof semver.SemVer + ? dependencyRange.version + : undefined; + + // Check if depRange is defined + if (depRange === undefined) { + throw new Error(`Invalid dependency range: ${dependencyRange}`); + } + + // Update the version in packageJson + if (depKind === "prod" && pkg.packageJson.dependencies !== undefined) { + pkg.packageJson.dependencies[depName] = depRange; + } else if (depKind === "dev" && pkg.packageJson.devDependencies !== undefined) { + pkg.packageJson.devDependencies[depName] = depRange; + } else if (depKind === "peer" && pkg.packageJson.peerDependencies !== undefined) { + pkg.packageJson.peerDependencies[depName] = depRange; + } + } + } + savePromises.push(pkg.savePackageJson()); + } + await Promise.all(savePromises); + + // Reload all packages to refresh the in-memory data + for (const pkg of packagesToUpdate) { + pkg.reload(); + } +} diff --git a/build-tools/packages/build-infrastructure/src/commands/list.ts b/build-tools/packages/build-infrastructure/src/commands/list.ts index 0cb60d6806ab..238648e86e36 100644 --- a/build-tools/packages/build-infrastructure/src/commands/list.ts +++ b/build-tools/packages/build-infrastructure/src/commands/list.ts @@ -6,19 +6,19 @@ import { Command, Flags } from "@oclif/core"; import colors from "picocolors"; -import { getAllDependenciesInRepo, loadFluidRepo } from "../fluidRepo.js"; -import type { IFluidRepo } from "../types.js"; +import { getAllDependencies, loadBuildProject } from "../buildProject.js"; +import type { IBuildProject } from "../types.js"; /** * This command is intended for testing and debugging use only. */ export class ListCommand extends Command { static override description = - "List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY."; + "List objects in the build project, like release groups, workspaces, and packages. USED FOR TESTING ONLY."; static override flags = { path: Flags.directory({ - description: "Path to start searching for the Fluid repo.", + description: "Path to start searching for the Build project configuration.", default: ".", }), full: Flags.boolean({ @@ -30,12 +30,12 @@ export class ListCommand extends Command { const { flags } = await this.parse(ListCommand); const { path: searchPath, full } = flags; - // load the Fluid repo - const repo = loadFluidRepo(searchPath); + // load the BuildProject + const repo = loadBuildProject(searchPath); const _ = full ? await this.logFullReport(repo) : await this.logCompactReport(repo); } - private async logFullReport(repo: IFluidRepo): Promise { + private async logFullReport(repo: IBuildProject): Promise { this.logIndent(colors.underline("Repository layout")); for (const workspace of repo.workspaces.values()) { this.log(); @@ -51,10 +51,7 @@ export class ListCommand extends Command { this.logIndent(pkgMessage, 4); } - const { releaseGroups, workspaces } = getAllDependenciesInRepo( - repo, - releaseGroup.packages, - ); + const { releaseGroups, workspaces } = getAllDependencies(repo, releaseGroup.packages); if (releaseGroups.length > 0 || workspaces.length > 0) { this.log(); this.logIndent(colors.bold("Depends on:"), 3); @@ -69,7 +66,7 @@ export class ListCommand extends Command { } } - private async logCompactReport(repo: IFluidRepo): Promise { + private async logCompactReport(repo: IBuildProject): Promise { this.logIndent(colors.underline("Repository layout")); for (const workspace of repo.workspaces.values()) { this.log(); diff --git a/build-tools/packages/build-infrastructure/src/config.ts b/build-tools/packages/build-infrastructure/src/config.ts index f8bba299a64b..4bc4ba2e36a2 100644 --- a/build-tools/packages/build-infrastructure/src/config.ts +++ b/build-tools/packages/build-infrastructure/src/config.ts @@ -13,32 +13,32 @@ import { } from "./types.js"; /** - * The version of the fluidRepo configuration currently used. + * The version of the BuildProject configuration currently used. */ -export const FLUIDREPO_CONFIG_VERSION = 1; +export const BUILDPROJECT_CONFIG_VERSION = 1; /** - * Top-most configuration for repo layout settings. + * Top-most configuration for BuildProject settings. */ -export interface IFluidRepoLayout { +export interface BuildProjectConfig { /** * The version of the config. */ - version: typeof FLUIDREPO_CONFIG_VERSION; + version: typeof BUILDPROJECT_CONFIG_VERSION; /** * **BACK-COMPAT ONLY** * * A mapping of package or release group names to metadata about the package or release group. * - * @deprecated Use the repoLayout property instead. + * @deprecated Use the buildProject property instead. */ repoPackages?: IFluidBuildDirs; /** - * The layout of repo into workspaces and release groups. + * The layout of the build project into workspaces and release groups. */ - repoLayout?: { + buildProject?: { workspaces: { /** * A mapping of workspace name to folder containing a workspace config file (e.g. pnpm-workspace.yaml). @@ -105,21 +105,21 @@ export interface ReleaseGroupDefinition { } /** - * @deprecated Use repoLayout and associated types instead. + * @deprecated Use buildProject and associated types instead. */ export interface IFluidBuildDirs { [name: string]: IFluidBuildDirEntry; } /** - * @deprecated Use repoLayout and associated types instead. + * @deprecated Use buildProject and associated types instead. */ export type IFluidBuildDirEntry = string | IFluidBuildDir | (string | IFluidBuildDir)[]; /** * Configures a package or release group * - * @deprecated Use repoLayout and associated types instead. + * @deprecated Use buildProject and associated types instead. */ export interface IFluidBuildDir { /** @@ -185,12 +185,12 @@ export function findReleaseGroupForPackage( } } -const configName = "repoLayout"; +const configName = "buildProject"; /** - * A cosmiconfig explorer to find the repoLayout config. First looks for JavaScript config files and falls back to the - * `repoLayout` property in package.json. We create a single explorer here because cosmiconfig internally caches configs - * for performance. The cache is per-explorer, so re-using the same explorer is a minor perf improvement. + * A cosmiconfig explorer to find the buildProject config. First looks for JavaScript config files and falls back to the + * `buildProject` property in package.json. We create a single explorer here because cosmiconfig internally caches + * configs for performance. The cache is per-explorer, so re-using the same explorer is a minor perf improvement. */ const configExplorer = cosmiconfigSync(configName, { searchPlaces: [ @@ -201,39 +201,39 @@ const configExplorer = cosmiconfigSync(configName, { "fluidBuild.config.cjs", "fluidBuild.config.js", - // Or the repoLayout property in package.json + // Or the buildProject property in package.json "package.json", ], packageProp: [configName], }); /** - * Search a path for a repo layout config file, and return the parsed config and the path to the config file. + * Search a path for a build project config file, and return the parsed config and the path to the config file. * * @param searchPath - The path to start searching for config files in. * @param noCache - If true, the config cache will be cleared and the config will be reloaded. - * @returns The loaded repoLayout config and the path to the config file. + * @returns The loaded build project config and the path to the config file. * * @throws If a config is not found or if the config version is not supported. */ -export function getFluidRepoLayout( +export function getBuildProjectConfig( searchPath: string, noCache = false, -): { config: IFluidRepoLayout; configFilePath: string } { +): { config: BuildProjectConfig; configFilePath: string } { if (noCache === true) { configExplorer.clearCaches(); } const configResult = configExplorer.search(searchPath); if (configResult === null || configResult === undefined) { - throw new Error("No fluidRepo configuration found."); + throw new Error("No BuildProject configuration found."); } - const config = configResult.config as IFluidRepoLayout; + const config = configResult.config as BuildProjectConfig; // Only version 1 of the config is supported. If any other value is provided, throw an error. - if (config.version !== FLUIDREPO_CONFIG_VERSION) { + if (config.version !== BUILDPROJECT_CONFIG_VERSION) { throw new Error( - `Configuration version is not supported: ${config?.version}. Config version must be ${FLUIDREPO_CONFIG_VERSION}.`, + `Configuration version is not supported: ${config?.version}. Config version must be ${BUILDPROJECT_CONFIG_VERSION}.`, ); } diff --git a/build-tools/packages/build-infrastructure/src/docs/cli.md b/build-tools/packages/build-infrastructure/src/docs/cli.md index 3a5fa35afe25..adbf77007dc7 100644 --- a/build-tools/packages/build-infrastructure/src/docs/cli.md +++ b/build-tools/packages/build-infrastructure/src/docs/cli.md @@ -15,22 +15,22 @@ title: repo-layout -- the build-infrastructure CLI # Commands -* [`repo-layout list`](#repo-layout-list) +* [`buildProject list`](#buildproject-list) -## `repo-layout list` +## `buildProject list` -List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY. +List objects in the build project, like release groups, workspaces, and packages. USED FOR TESTING ONLY. ``` USAGE - $ repo-layout list [--path ] [--full] + $ buildProject list [--path ] [--full] FLAGS --full Output the full report. - --path= [default: .] Path to start searching for the Fluid repo. + --path= [default: .] Path to start searching for the Build project configuration. DESCRIPTION - List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY. + List objects in the build project, like release groups, workspaces, and packages. USED FOR TESTING ONLY. ``` _See code: [src/commands/list.ts](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/build-infrastructure/src/commands/list.ts)_ diff --git a/build-tools/packages/build-infrastructure/src/index.ts b/build-tools/packages/build-infrastructure/src/index.ts index a870d8379496..3a69dc484400 100644 --- a/build-tools/packages/build-infrastructure/src/index.ts +++ b/build-tools/packages/build-infrastructure/src/index.ts @@ -8,8 +8,8 @@ * * The primary purpose of this package is to provide a common way to organize npm packages into groups called release * groups, and leverages workspaces functionality provided by package managers like npm, yarn, and pnpm to manage - * interdependencies between packages across a Fluid repo. It then provides APIs to select, filter, and work with those - * package groups. + * interdependencies between packages across a BuildProject. It then provides APIs to select, filter, and work with + * those package groups. * * @module default entrypoint */ @@ -20,22 +20,22 @@ export { type IFluidBuildDir, type IFluidBuildDirs, type IFluidBuildDirEntry, - type IFluidRepoLayout, - FLUIDREPO_CONFIG_VERSION, - getFluidRepoLayout, + type BuildProjectConfig as BuildProjectLayout, + BUILDPROJECT_CONFIG_VERSION, + getBuildProjectConfig, } from "./config.js"; export { NotInGitRepository } from "./errors.js"; export { - FluidRepo as FluidRepoBase, - getAllDependenciesInRepo, - loadFluidRepo, -} from "./fluidRepo.js"; + BuildProject, + getAllDependencies, + loadBuildProject, +} from "./buildProject.js"; export { PackageBase } from "./package.js"; export { createPackageManager } from "./packageManagers.js"; export type { AdditionalPackageProps, Installable, - IFluidRepo, + IBuildProject, IPackage, IReleaseGroup, IWorkspace, @@ -50,30 +50,4 @@ export type { IPackageManager, } from "./types.js"; export { isIPackage, isIReleaseGroup } from "./types.js"; - -// export { -// filterPackages, -// type FilterablePackage, -// selectAndFilterPackages, -// type GlobString, -// AllPackagesSelectionCriteria, -// EmptySelectionCriteria, -// type PackageSelectionCriteria, -// type PackageFilterOptions, -// } from "./filter.js"; -// export { -// FluidRepo as FluidRepoBase, -// getAllDependenciesInRepo, -// loadFluidRepo, -// } from "./fluidRepo.js"; -// export { -// getFiles, -// findGitRootSync, -// getMergeBaseRemote, -// getRemote, -// getChangedSinceRef, -// } from "./git.js"; -// export { PackageBase } from "./package.js"; -// export { updatePackageJsonFile, updatePackageJsonFileAsync } from "./packageJsonUtils.js"; -// export { createPackageManager } from "./packageManagers.js"; -// export { setVersion } from "./versions.js"; +export { setVersion } from "./versions.js"; diff --git a/build-tools/packages/build-infrastructure/src/releaseGroup.ts b/build-tools/packages/build-infrastructure/src/releaseGroup.ts index c20aabc41c08..4d71ea830f62 100644 --- a/build-tools/packages/build-infrastructure/src/releaseGroup.ts +++ b/build-tools/packages/build-infrastructure/src/releaseGroup.ts @@ -83,19 +83,19 @@ export class ReleaseGroup implements IReleaseGroup { public get releaseGroupDependencies(): IReleaseGroup[] { const dependentReleaseGroups = new Set(); const ignoredDependencies = new Set(); - const fluidRepo = this.workspace.fluidRepo; + const buildProject = this.workspace.buildProject; for (const pkg of this.packages) { for (const { name } of pkg.combinedDependencies) { if (ignoredDependencies.has(name)) { continue; } - const depPackage = fluidRepo.packages.get(name); + const depPackage = buildProject.packages.get(name); if (depPackage === undefined || depPackage.releaseGroup === this.name) { ignoredDependencies.add(name); continue; } - const releaseGroup = fluidRepo.releaseGroups.get(depPackage.releaseGroup); + const releaseGroup = buildProject.releaseGroups.get(depPackage.releaseGroup); if (releaseGroup === undefined) { throw new Error( `Cannot find release group "${depPackage.releaseGroup}" in workspace "${this.workspace}"`, diff --git a/build-tools/packages/build-infrastructure/src/test/fluidRepo.test.ts b/build-tools/packages/build-infrastructure/src/test/buildProject.test.ts similarity index 52% rename from build-tools/packages/build-infrastructure/src/test/fluidRepo.test.ts rename to build-tools/packages/build-infrastructure/src/test/buildProject.test.ts index eb04d16e4d59..d77479c23f57 100644 --- a/build-tools/packages/build-infrastructure/src/test/fluidRepo.test.ts +++ b/build-tools/packages/build-infrastructure/src/test/buildProject.test.ts @@ -8,8 +8,10 @@ import { strict as assert } from "node:assert"; import chai, { expect } from "chai"; import assertArrays from "chai-arrays"; import { describe, it } from "mocha"; +import * as semver from "semver"; +import { simpleGit } from "simple-git"; -import { loadFluidRepo } from "../fluidRepo.js"; +import { loadBuildProject, setDependencyRange } from "../buildProject.js"; import { findGitRootSync } from "../git.js"; import type { ReleaseGroupName, WorkspaceName } from "../types.js"; @@ -17,10 +19,12 @@ import { testRepoRoot } from "./init.js"; chai.use(assertArrays); -describe("loadFluidRepo", () => { +const git = simpleGit(testRepoRoot); + +describe("loadBuildProject", () => { describe("testRepo", () => { it("loads correctly", () => { - const repo = loadFluidRepo(testRepoRoot); + const repo = loadBuildProject(testRepoRoot); assert.strictEqual( repo.workspaces.size, 2, @@ -58,7 +62,7 @@ describe("loadFluidRepo", () => { }); it("releaseGroupDependencies", async () => { - const repo = loadFluidRepo(testRepoRoot); + const repo = loadBuildProject(testRepoRoot); const mainReleaseGroup = repo.releaseGroups.get("main" as ReleaseGroupName); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- test data (validated by another test) guarantees this has a value const actualDependencies = mainReleaseGroup!.releaseGroupDependencies; @@ -72,7 +76,7 @@ describe("loadFluidRepo", () => { describe("FluidFramework repo - tests backCompat config loading", () => { it("loads correctly", () => { // Load the root config - const repo = loadFluidRepo(findGitRootSync()); + const repo = loadBuildProject(findGitRootSync()); expect(repo.workspaces.size).to.be.greaterThan(1); const client = repo.workspaces.get("client" as WorkspaceName); @@ -93,7 +97,7 @@ describe("loadFluidRepo", () => { }); it("releaseGroupDependencies", async () => { - const repo = loadFluidRepo(findGitRootSync()); + const repo = loadBuildProject(findGitRootSync()); const clientReleaseGroup = repo.releaseGroups.get("client" as ReleaseGroupName); assert(clientReleaseGroup !== undefined); @@ -104,3 +108,76 @@ describe("loadFluidRepo", () => { }); }); }); + +describe("setDependencyRange", () => { + const repo = loadBuildProject(testRepoRoot); + const main = repo.releaseGroups.get("main" as ReleaseGroupName); + assert(main !== undefined); + const mainPackages = new Set(main.packages); + + const group2 = repo.releaseGroups.get("group2" as ReleaseGroupName); + assert(group2 !== undefined); + const group2Packages = new Set(group2.packages); + + const mainWorkspace = repo.workspaces.get("main" as WorkspaceName); + assert(mainWorkspace !== undefined); + const mainWorkspacePackages = new Set(mainWorkspace.packages); + + afterEach(async () => { + await git.checkout(["HEAD", "--", testRepoRoot]); + repo.reload(); + }); + + it("updates the dependency range to explicit version given group2 packages", async () => { + const version = semver.parse("2.0.0"); + assert(version !== null); + await setDependencyRange(mainPackages, group2Packages, version); + + const allCorrect = main.packages.every((pkg) => { + const dependencies = pkg.packageJson.dependencies ?? {}; + + const group2PkgDUpdated = (dependencies["@group2/pkg-d"] ?? "2.0.0") === "2.0.0"; + + return group2PkgDUpdated; + }); + expect(allCorrect).to.be.true; + }); + + it("updates the dependency range to explicit version given superset workspace", async () => { + const version = semver.parse("2.0.0"); + assert(version !== null); + await setDependencyRange(mainPackages, mainWorkspacePackages, version); + + const allCorrect = main.packages.every((pkg) => { + const dependencies = pkg.packageJson.dependencies ?? {}; + + const pkgbUpdated = (dependencies["pkg-b"] ?? "2.0.0") === "2.0.0"; + + const pkgcUpdated = (dependencies["@private/pkg-c"] ?? "2.0.0") === "2.0.0"; + + const sharedUpdated = (dependencies["@shared/shared"] ?? "2.0.0") === "2.0.0"; + + const pkgdUpdated = (dependencies["@group2/pkg-d"] ?? "2.0.0") === "2.0.0"; + + return pkgbUpdated && pkgcUpdated && sharedUpdated && pkgdUpdated; + }); + expect(allCorrect).to.be.true; + }); + + it("updates the dependency range to explicit version given main packages", async () => { + const version = semver.parse("2.0.0"); + assert(version !== null); + await setDependencyRange(mainPackages, mainPackages, version); + + const allCorrect = main.packages.every((pkg) => { + const dependencies = pkg.packageJson.dependencies ?? {}; + + const pkgbUpdated = (dependencies["pkg-b"] ?? "2.0.0") === "2.0.0"; + + const sharedUpdated = (dependencies["@shared/shared"] ?? "2.0.0") === "2.0.0"; + + return pkgbUpdated && sharedUpdated; + }); + expect(allCorrect).to.be.true; + }); +}); diff --git a/build-tools/packages/build-infrastructure/src/test/data/testRepo/fluidBuild.config.cjs b/build-tools/packages/build-infrastructure/src/test/data/testRepo/fluidBuild.config.cjs index 3ae27cf80d09..924efddc73f4 100644 --- a/build-tools/packages/build-infrastructure/src/test/data/testRepo/fluidBuild.config.cjs +++ b/build-tools/packages/build-infrastructure/src/test/data/testRepo/fluidBuild.config.cjs @@ -8,11 +8,11 @@ // @ts-check /** - * @type {import("@fluid-tools/build-infrastructure").IFluidRepoLayout & import("@fluid-tools/build-cli").FlubConfig} + * @type {import("@fluid-tools/build-infrastructure").IBuildProjectLayout & import("@fluid-tools/build-cli").FlubConfig} */ const config = { version: 1, - repoLayout: { + buildProject: { workspaces: { main: { directory: ".", diff --git a/build-tools/packages/build-infrastructure/src/test/versions.test.ts b/build-tools/packages/build-infrastructure/src/test/versions.test.ts new file mode 100644 index 000000000000..44487d3160be --- /dev/null +++ b/build-tools/packages/build-infrastructure/src/test/versions.test.ts @@ -0,0 +1,69 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; +import path from "node:path"; + +import { expect } from "chai"; +import { afterEach, describe, it } from "mocha"; +import * as semver from "semver"; +import { simpleGit } from "simple-git"; + +import { loadBuildProject } from "../buildProject.js"; +import type { ReleaseGroupName, WorkspaceName } from "../types.js"; +import { setVersion } from "../versions.js"; + +import { testDataPath, testRepoRoot } from "./init.js"; + +const repo = loadBuildProject(path.join(testDataPath, "./testRepo")); +const main = repo.releaseGroups.get("main" as ReleaseGroupName); +assert(main !== undefined); + +const group2 = repo.releaseGroups.get("group2" as ReleaseGroupName); +assert(group2 !== undefined); + +const group3 = repo.releaseGroups.get("group3" as ReleaseGroupName); +assert(group3 !== undefined); + +const secondWorkspace = repo.workspaces.get("second" as WorkspaceName); +assert(secondWorkspace !== undefined); + +/** + * A git client rooted in the test repo. Used for resetting tests. + */ +const git = simpleGit(testRepoRoot); + +describe("setVersion", () => { + afterEach(async () => { + await git.checkout(["HEAD", "--", testRepoRoot]); + repo.reload(); + }); + + it("release group", async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await setVersion(main.packages, semver.parse("1.2.1")!); + + const allCorrect = main.packages.every((pkg) => pkg.version === "1.2.1"); + expect(main.version).to.equal("1.2.1"); + expect(allCorrect).to.be.true; + }); + + it("workspace", async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await setVersion(secondWorkspace.packages, semver.parse("2.2.1")!); + + const allCorrect = secondWorkspace.packages.every((pkg) => pkg.version === "2.2.1"); + expect(allCorrect).to.be.true; + }); + + it("repo", async () => { + const packages = [...repo.packages.values()]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await setVersion(packages, semver.parse("1.2.1")!); + + const allCorrect = packages.every((pkg) => pkg.version === "1.2.1"); + expect(allCorrect).to.be.true; + }); +}); diff --git a/build-tools/packages/build-infrastructure/src/test/workspace.test.ts b/build-tools/packages/build-infrastructure/src/test/workspace.test.ts index 06056315192d..bcab05229ff5 100644 --- a/build-tools/packages/build-infrastructure/src/test/workspace.test.ts +++ b/build-tools/packages/build-infrastructure/src/test/workspace.test.ts @@ -10,13 +10,13 @@ import path from "node:path"; import { expect } from "chai"; import { describe, it } from "mocha"; -import { loadFluidRepo } from "../fluidRepo.js"; +import { loadBuildProject } from "../buildProject.js"; import type { PackageName, WorkspaceName } from "../types.js"; import { testRepoRoot } from "./init.js"; describe("workspaces", () => { - const repo = loadFluidRepo(testRepoRoot); + const repo = loadBuildProject(testRepoRoot); const workspace = repo.workspaces.get("main" as WorkspaceName); describe("lockfile outdated", () => { diff --git a/build-tools/packages/build-infrastructure/src/types.ts b/build-tools/packages/build-infrastructure/src/types.ts index f5ae424fd28e..f8e34efdb579 100644 --- a/build-tools/packages/build-infrastructure/src/types.ts +++ b/build-tools/packages/build-infrastructure/src/types.ts @@ -6,7 +6,7 @@ import { SimpleGit } from "simple-git"; import type { Opaque, SetRequired, PackageJson as StandardPackageJson } from "type-fest"; -import type { IFluidRepoLayout } from "./config.js"; +import type { BuildProjectConfig } from "./config.js"; /** * Extra package.json fields used by pnpm. @@ -41,7 +41,7 @@ export type PackageJson = SetRequired< export type AdditionalPackageProps = Record | undefined; /** - * A Fluid repo organizes a collection of npm packages into workspaces and release groups. A Fluid repo can contain + * A BuildProject organizes a collection of npm packages into workspaces and release groups. A BuildProject can contain * multiple workspaces, and a workspace can in turn contain multiple release groups. Both workspaces and release groups * represent ways to organize packages in the repo, but their purpose and function are different. * @@ -49,24 +49,24 @@ export type AdditionalPackageProps = Record | undefined; * * @typeParam P - The type of {@link IPackage} the repo uses. This can be any type that implements {@link IPackage}. */ -export interface IFluidRepo

extends Reloadable { +export interface IBuildProject

extends Reloadable { /** - * The absolute path to the root of the IFluidRepo. This is the path where the config file is located. + * The absolute path to the root of the IBuildProject. This is the path where the config file is located. */ root: string; /** - * A map of all workspaces in the Fluid repo. + * A map of all workspaces in the BuildProject. */ workspaces: Map; /** - * A map of all release groups in the Fluid repo. + * A map of all release groups in the BuildProject. */ releaseGroups: Map; /** - * A map of all packages in the Fluid repo. + * A map of all packages in the BuildProject. */ packages: Map; @@ -77,21 +77,21 @@ export interface IFluidRepo

extends Reloadable { upstreamRemotePartialUrl?: string; /** - * The layout configuration for the repo. + * The configuration for the build project. */ - configuration: IFluidRepoLayout; + configuration: BuildProjectConfig; /** - * Transforms an absolute path to a path relative to the IFluidRepo root. + * Transforms an absolute path to a path relative to the IBuildProject root. * - * @param p - The path to make relative to the IFluidRepo root. - * @returns The path relative to the IFluidRepo root. + * @param p - The path to make relative to the IBuildProject root. + * @returns The path relative to the IBuildProject root. */ relativeToRepo(p: string): string; /** - * If the FluidRepo is within a Git repository, this function will return a SimpleGit instance rooted at the root of - * the Git repository. If the FluidRepo is _not_ within a Git repository, this function will throw a + * If the BuildProject is within a Git repository, this function will return a SimpleGit instance rooted at the root + * of the Git repository. If the BuildProject is _not_ within a Git repository, this function will throw a * {@link NotInGitRepository} error. * * @throws A {@link NotInGitRepository} error if the path is not within a Git repository. @@ -141,8 +141,8 @@ export type WorkspaceName = Opaque; /** * A workspace is a collection of packages, including a root package, that is managed using a package manager's - * "workspaces" functionality. A Fluid repo can contain multiple workspaces. Workspaces are defined and managed using - * the package manager directly. A Fluid repo builds on top of workspaces and relies on the package manager to install + * "workspaces" functionality. A BuildProject can contain multiple workspaces. Workspaces are defined and managed using + * the package manager directly. A BuildProject builds on top of workspaces and relies on the package manager to install * and manage dependencies and interdependencies within the workspace. * * A workspace defines the _physical layout_ of the packages within it. Workspaces are a generally a feature provided by @@ -154,8 +154,8 @@ export type WorkspaceName = Opaque; * it is trivial to link multiple packages so they can depend on one another. The `IWorkspace` type is a thin wrapper on * top of these package manager features. * - * A Fluid repo will only load packages identified by the package manager's workspace feature. That is, any package in - * the repo that is not configured as part of a workspace is invisible to tools using the Fluid repo. + * A BuildProject will only load packages identified by the package manager's workspace feature. That is, any package in + * the repo that is not configured as part of a workspace is invisible to tools using the BuildProject. * * Workspaces are not involved in versioning or releasing packages. They are used for dependency management only. * Release groups, on the other hand, are used to group packages into releasable groups. See {@link IReleaseGroup} for @@ -183,9 +183,9 @@ export interface IWorkspace extends Installable, Reloadable { releaseGroups: Map; /** - * The Fluid repo that the workspace belongs to. + * The build project that the workspace belongs to. */ - fluidRepo: IFluidRepo; + buildProject: IBuildProject; /** * An array of all the packages in the workspace. This includes the workspace root and any release group roots and diff --git a/build-tools/packages/build-infrastructure/src/versions.ts b/build-tools/packages/build-infrastructure/src/versions.ts new file mode 100644 index 000000000000..fa139ed73b77 --- /dev/null +++ b/build-tools/packages/build-infrastructure/src/versions.ts @@ -0,0 +1,37 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { SemVer } from "semver"; + +import { updatePackageJsonFileAsync } from "./packageJsonUtils.js"; +import type { IPackage, PackageJson } from "./types.js"; + +/** + * Sets the version of a group of packages, writing the new version in package.json. After the update, the packages are + * reloaded so the in-memory data reflects the version changes. + * + * @param packages - An array of objects whose version should be updated. + * @param version - The version to set. + */ +export async function setVersion( + packages: IPackage[], + version: SemVer, +): Promise { + const translatedVersion = version; + const setPackagePromises: Promise[] = []; + for (const pkg of packages) { + setPackagePromises.push( + updatePackageJsonFileAsync(pkg.directory, async (json) => { + json.version = translatedVersion.version; + }), + ); + } + await Promise.all(setPackagePromises); + + // Reload all the packages to refresh the in-memory data + for (const pkg of packages) { + pkg.reload(); + } +} diff --git a/build-tools/packages/build-infrastructure/src/workspace.ts b/build-tools/packages/build-infrastructure/src/workspace.ts index 4acc72c044e7..a1353ace0b04 100644 --- a/build-tools/packages/build-infrastructure/src/workspace.ts +++ b/build-tools/packages/build-infrastructure/src/workspace.ts @@ -13,7 +13,7 @@ import { loadPackageFromWorkspaceDefinition } from "./package.js"; import { createPackageManager } from "./packageManagers.js"; import { ReleaseGroup } from "./releaseGroup.js"; import type { - IFluidRepo, + IBuildProject, IPackage, IPackageManager, IReleaseGroup, @@ -66,9 +66,9 @@ export class Workspace implements IWorkspace { root: string, /** - * {@inheritDoc IWorkspace.fluidRepo} + * {@inheritDoc IWorkspace.buildProject} */ - public readonly fluidRepo: IFluidRepo, + public readonly buildProject: IBuildProject, ) { this.name = name as WorkspaceName; this.directory = path.resolve(root, definition.directory); @@ -216,16 +216,16 @@ export class Workspace implements IWorkspace { * @param name - The name of the workspace. * @param definition - The definition for the workspace. * @param root - The path to the root of the workspace. - * @param fluidRepo - The Fluid repo that the workspace belongs to. + * @param buildProject - The build project that the workspace belongs to. * @returns A loaded {@link IWorkspace}. */ public static load( name: string, definition: WorkspaceDefinition, root: string, - fluidRepo: IFluidRepo, + buildProject: IBuildProject, ): IWorkspace { - const workspace = new Workspace(name, definition, root, fluidRepo); + const workspace = new Workspace(name, definition, root, buildProject); return workspace; } } diff --git a/build-tools/packages/build-infrastructure/src/workspaceCompat.ts b/build-tools/packages/build-infrastructure/src/workspaceCompat.ts index 93a76b389980..b8271732eb44 100644 --- a/build-tools/packages/build-infrastructure/src/workspaceCompat.ts +++ b/build-tools/packages/build-infrastructure/src/workspaceCompat.ts @@ -16,7 +16,7 @@ import type { ReleaseGroupDefinition, WorkspaceDefinition, } from "./config.js"; -import type { IFluidRepo, IWorkspace, WorkspaceName } from "./types.js"; +import type { IBuildProject, IWorkspace, WorkspaceName } from "./types.js"; import { Workspace } from "./workspace.js"; /** @@ -25,12 +25,12 @@ import { Workspace } from "./workspace.js"; * **ONLY INTENDED FOR BACK-COMPAT.** * * @param entry - The config entry. - * @param fluidRepo - The Fluid repo the workspace belongs to. + * @param buildProject - The BuildProject the workspace belongs to. */ export function loadWorkspacesFromLegacyConfig( // eslint-disable-next-line import/no-deprecated -- back-compat code config: IFluidBuildDirs, - fluidRepo: IFluidRepo, + buildProject: IBuildProject, ): Map { const workspaces: Map = new Map(); @@ -39,12 +39,12 @@ export function loadWorkspacesFromLegacyConfig( const loadedWorkspaces: IWorkspace[] = []; if (Array.isArray(entry)) { for (const item of entry) { - loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(item, fluidRepo)); + loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(item, buildProject)); } } else if (typeof entry === "object") { - loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(entry, fluidRepo, name)); + loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(entry, buildProject, name)); } else { - loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(entry, fluidRepo)); + loadedWorkspaces.push(...loadWorkspacesFromLegacyConfigEntry(entry, buildProject)); } for (const ws of loadedWorkspaces) { workspaces.set(ws.name, ws); @@ -64,14 +64,14 @@ export function loadWorkspacesFromLegacyConfig( * **ONLY INTENDED FOR BACK-COMPAT.** * * @param entry - The config entry. - * @param fluidRepoRoot - The path to the root of the FluidRepo. + * @param buildProject - The path to the root of the BuildProject. * @param name - If provided, this name will be used for the workspace. If it is not provided, the name will be derived * from the directory name. */ function loadWorkspacesFromLegacyConfigEntry( // eslint-disable-next-line import/no-deprecated -- back-compat code entry: string | IFluidBuildDir, - fluidRepo: IFluidRepo, + buildProject: IBuildProject, name?: string, ): IWorkspace[] { const directory = typeof entry === "string" ? entry : entry.directory; @@ -87,14 +87,16 @@ function loadWorkspacesFromLegacyConfigEntry( // BACK-COMPAT HACK - assume that a directory in the legacy config either has a package.json -- in which case the // directory will be treated as a workspace root -- or it does not, in which case all package.json files under the // path will be treated as workspace roots. - const packagePath = path.join(fluidRepo.root, directory, "package.json"); + const packagePath = path.join(buildProject.root, directory, "package.json"); if (existsSync(packagePath)) { const workspaceDefinition: WorkspaceDefinition = { directory, releaseGroups: releaseGroupDefinitions, }; - return [Workspace.load(workspaceName, workspaceDefinition, fluidRepo.root, fluidRepo)]; + return [ + Workspace.load(workspaceName, workspaceDefinition, buildProject.root, buildProject), + ]; } const packageJsonPaths = globby @@ -109,11 +111,11 @@ function loadWorkspacesFromLegacyConfigEntry( }) .map( // Make the paths relative to the repo root - (filePath) => path.relative(fluidRepo.root, filePath), + (filePath) => path.relative(buildProject.root, filePath), ); const workspaces = packageJsonPaths.flatMap((pkgPath) => { const dir = path.dirname(pkgPath); - return loadWorkspacesFromLegacyConfigEntry(dir, fluidRepo); + return loadWorkspacesFromLegacyConfigEntry(dir, buildProject); }); return workspaces; } diff --git a/build-tools/packages/build-infrastructure/typedoc.config.cjs b/build-tools/packages/build-infrastructure/typedoc.config.cjs index c3ef2dff3817..c9b2c53b602b 100644 --- a/build-tools/packages/build-infrastructure/typedoc.config.cjs +++ b/build-tools/packages/build-infrastructure/typedoc.config.cjs @@ -18,7 +18,7 @@ module.exports = { out: "docs", readme: "./README.md", mergeReadme: true, - projectDocuments: ["./src/docs/cli.md"], + // projectDocuments: ["./src/docs/cli.md"], defaultCategory: "API", categorizeByGroup: true, navigation: { diff --git a/build-tools/packages/build-tools/package.json b/build-tools/packages/build-tools/package.json index 06bc99ad8cab..9dfe5f4d1bd1 100644 --- a/build-tools/packages/build-tools/package.json +++ b/build-tools/packages/build-tools/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/build-tools", - "version": "0.50.0", + "version": "0.51.0", "description": "Fluid Build tools", "homepage": "https://fluidframework.com", "repository": { @@ -42,7 +42,6 @@ "@fluid-tools/version-tools": "workspace:~", "@manypkg/get-packages": "^2.2.2", "async": "^3.2.6", - "chalk": "^2.4.2", "cosmiconfig": "^8.3.6", "date-fns": "^2.30.0", "debug": "^4.3.7", @@ -56,6 +55,7 @@ "lodash": "^4.17.21", "lodash.isequal": "^4.5.0", "multimatch": "^5.0.0", + "picocolors": "^1.1.1", "picomatch": "^2.3.1", "rimraf": "^4.4.1", "semver": "^7.6.3", diff --git a/build-tools/packages/build-tools/src/common/logging.ts b/build-tools/packages/build-tools/src/common/logging.ts index 7056ecf2e1c0..576d241673b0 100644 --- a/build-tools/packages/build-tools/src/common/logging.ts +++ b/build-tools/packages/build-tools/src/common/logging.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import chalk from "chalk"; +import chalk from "picocolors"; import { commonOptions } from "../fluidBuild/commonOptions"; diff --git a/build-tools/packages/build-tools/src/common/npmPackage.ts b/build-tools/packages/build-tools/src/common/npmPackage.ts index b965e0d4404c..1155718cfeae 100644 --- a/build-tools/packages/build-tools/src/common/npmPackage.ts +++ b/build-tools/packages/build-tools/src/common/npmPackage.ts @@ -6,9 +6,9 @@ import { existsSync, readFileSync, readdirSync } from "node:fs"; import * as path from "node:path"; import { queue } from "async"; -import * as chalk from "chalk"; import detectIndent from "detect-indent"; import { readJsonSync, writeJson, writeJsonSync } from "fs-extra"; +import chalk from "picocolors"; import sortPackageJson from "sort-package-json"; import type { SetRequired, PackageJson as StandardPackageJson } from "type-fest"; @@ -75,21 +75,21 @@ interface PackageDependency { export class Package { private static packageCount: number = 0; private static readonly chalkColor = [ - chalk.default.red, - chalk.default.green, - chalk.default.yellow, - chalk.default.blue, - chalk.default.magenta, - chalk.default.cyan, - chalk.default.white, - chalk.default.grey, - chalk.default.redBright, - chalk.default.greenBright, - chalk.default.yellowBright, - chalk.default.blueBright, - chalk.default.magentaBright, - chalk.default.cyanBright, - chalk.default.whiteBright, + chalk.red, + chalk.green, + chalk.yellow, + chalk.blue, + chalk.magenta, + chalk.cyan, + chalk.white, + chalk.gray, + chalk.redBright, + chalk.greenBright, + chalk.yellowBright, + chalk.blueBright, + chalk.magentaBright, + chalk.cyanBright, + chalk.whiteBright, ]; private _packageJson: PackageJson; diff --git a/build-tools/packages/build-tools/src/common/typeCompatibility.ts b/build-tools/packages/build-tools/src/common/typeCompatibility.ts index 9a84b8d493c0..3838fa20188a 100644 --- a/build-tools/packages/build-tools/src/common/typeCompatibility.ts +++ b/build-tools/packages/build-tools/src/common/typeCompatibility.ts @@ -77,7 +77,7 @@ export type TypeOnly = T extends number }; /** - * Type preprocessing function selected with the `@type-test-minimal` tag. + * Type preprocessing function selected with the `@typeTestMinimal` tag. * * This throws away even more type information that the default {@link TypeOnly} option, resulting in only the most minimal of type compatibility testing. * Currently this minimal level of compatibility resting only includes existence of the type and does not preserve any details. @@ -90,7 +90,7 @@ export type TypeOnly = T extends number export type MinimalType = 0; /** - * Type preprocessing function selected with the `@type-test-full` tag. + * Type preprocessing function selected with the `@typeTestFull` tag. * * This allows opting into full type compatibility: two types will only be considered compatible if they are assignable unmodified. * @@ -304,3 +304,14 @@ namespace Test_TypeOnly_Symbols { type _check3 = requireAssignableTo, object>; type _check4 = requireAssignableTo>; } + +namespace TestNeverParameter { + type FooNever = T; + type AnyNever = T; + + // @ts-expect-error any not assignable to never + type _check1 = FooNever; + + // never is assignable to any + type _check2 = AnyNever; +} diff --git a/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts b/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts index b1fdbbb55d5f..c2e9fd280f28 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts @@ -4,7 +4,7 @@ */ import { AsyncPriorityQueue } from "async"; -import chalk from "chalk"; +import chalk from "picocolors"; import * as semver from "semver"; import * as assert from "assert"; diff --git a/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts b/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts index 0099c6184f74..d59d07af33ab 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import chalk from "chalk"; +import chalk from "picocolors"; import { GitRepo } from "../common/gitRepo"; import { defaultLogger } from "../common/logging"; diff --git a/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts b/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts index 2120c4f9e3c2..810e16584252 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts @@ -5,8 +5,8 @@ import { existsSync } from "node:fs"; import * as path from "node:path"; -import chalk from "chalk"; import registerDebug from "debug"; +import chalk from "picocolors"; import { defaultLogger } from "../common/logging"; import { MonoRepo } from "../common/monoRepo"; diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts index d4daa9e98b0a..380f16160b94 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts @@ -7,8 +7,8 @@ import * as assert from "assert"; import crypto from "crypto"; import * as path from "path"; import { AsyncPriorityQueue } from "async"; -import chalk from "chalk"; import registerDebug from "debug"; +import chalk from "picocolors"; import { existsSync } from "node:fs"; import { readFile, stat, unlink, writeFile } from "node:fs/promises"; diff --git a/build-tools/packages/bundle-size-tools/package.json b/build-tools/packages/bundle-size-tools/package.json index 225ba8544ca7..01e6664c16d3 100644 --- a/build-tools/packages/bundle-size-tools/package.json +++ b/build-tools/packages/bundle-size-tools/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/bundle-size-tools", - "version": "0.50.0", + "version": "0.51.0", "description": "Utility for analyzing bundle size regressions", "homepage": "https://fluidframework.com", "repository": { diff --git a/build-tools/packages/version-tools/package.json b/build-tools/packages/version-tools/package.json index 7c3cb0ea3d2f..f91905bc026c 100644 --- a/build-tools/packages/version-tools/package.json +++ b/build-tools/packages/version-tools/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/version-tools", - "version": "0.50.0", + "version": "0.51.0", "description": "Versioning tools for Fluid Framework", "homepage": "https://fluidframework.com", "repository": { @@ -82,7 +82,6 @@ "@oclif/plugin-commands": "^4.1.5", "@oclif/plugin-help": "^6.2.16", "@oclif/plugin-not-found": "^3.2.24", - "chalk": "^2.4.2", "semver": "^7.6.3", "table": "^6.8.2" }, diff --git a/build-tools/pnpm-lock.yaml b/build-tools/pnpm-lock.yaml index 5f14416cefb8..7d05d5e82a50 100644 --- a/build-tools/pnpm-lock.yaml +++ b/build-tools/pnpm-lock.yaml @@ -18,7 +18,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@commitlint/cli': specifier: ^17.8.1 version: 17.8.1 @@ -128,17 +128,14 @@ importers: specifier: ^21.0.2 version: 21.0.2 '@rushstack/node-core-library': - specifier: ^3.66.1 - version: 3.66.1(@types/node@18.19.60) + specifier: ^5.9.0 + version: 5.9.0(@types/node@18.19.60) async: specifier: ^3.2.6 version: 3.2.6 azure-devops-node-api: specifier: ^11.2.0 version: 11.2.0 - chalk: - specifier: ^5.3.0 - version: 5.3.0 change-case: specifier: ^3.1.0 version: 3.1.0 @@ -208,6 +205,9 @@ importers: oclif: specifier: ^4.15.16 version: 4.15.16(@types/node@18.19.60) + picocolors: + specifier: ^1.1.1 + version: 1.1.1 prettier: specifier: ~3.2.5 version: 3.2.5 @@ -274,7 +274,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 @@ -387,7 +387,7 @@ importers: packages/build-infrastructure: dependencies: '@fluid-tools/version-tools': - specifier: workspace:^ + specifier: workspace:~ version: link:../version-tools '@manypkg/get-packages': specifier: ^2.2.2 @@ -440,7 +440,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/api-markdown-documenter': specifier: ^0.17.1 version: 0.17.1(@types/node@18.19.60) @@ -531,9 +531,6 @@ importers: async: specifier: ^3.2.6 version: 3.2.6 - chalk: - specifier: ^2.4.2 - version: 2.4.2 cosmiconfig: specifier: ^8.3.6 version: 8.3.6(typescript@5.4.5) @@ -573,6 +570,9 @@ importers: multimatch: specifier: ^5.0.0 version: 5.0.0 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 picomatch: specifier: ^2.3.1 version: 2.3.1 @@ -603,7 +603,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 @@ -679,7 +679,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 @@ -731,9 +731,6 @@ importers: '@oclif/plugin-not-found': specifier: ^3.2.24 version: 3.2.24(@types/node@18.19.60) - chalk: - specifier: ^2.4.2 - version: 2.4.2 semver: specifier: ^7.6.3 version: 7.6.3 @@ -743,7 +740,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 @@ -868,24 +865,24 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@biomejs/biome@1.9.3: - resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} + /@biomejs/biome@1.9.4: + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} hasBin: true requiresBuild: true optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.3 - '@biomejs/cli-darwin-x64': 1.9.3 - '@biomejs/cli-linux-arm64': 1.9.3 - '@biomejs/cli-linux-arm64-musl': 1.9.3 - '@biomejs/cli-linux-x64': 1.9.3 - '@biomejs/cli-linux-x64-musl': 1.9.3 - '@biomejs/cli-win32-arm64': 1.9.3 - '@biomejs/cli-win32-x64': 1.9.3 - dev: true - - /@biomejs/cli-darwin-arm64@1.9.3: - resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + dev: true + + /@biomejs/cli-darwin-arm64@1.9.4: + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] @@ -893,8 +890,8 @@ packages: dev: true optional: true - /@biomejs/cli-darwin-x64@1.9.3: - resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} + /@biomejs/cli-darwin-x64@1.9.4: + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] @@ -902,8 +899,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64-musl@1.9.3: - resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} + /@biomejs/cli-linux-arm64-musl@1.9.4: + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -911,8 +908,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64@1.9.3: - resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} + /@biomejs/cli-linux-arm64@1.9.4: + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -920,8 +917,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64-musl@1.9.3: - resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} + /@biomejs/cli-linux-x64-musl@1.9.4: + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -929,8 +926,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64@1.9.3: - resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} + /@biomejs/cli-linux-x64@1.9.4: + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -938,8 +935,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-arm64@1.9.3: - resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} + /@biomejs/cli-win32-arm64@1.9.4: + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] @@ -947,8 +944,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-x64@1.9.3: - resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} + /@biomejs/cli-win32-x64@1.9.4: + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -2807,6 +2804,7 @@ packages: resolve: 1.22.8 semver: 7.5.4 z-schema: 5.0.5 + dev: true /@rushstack/node-core-library@5.3.0(@types/node@18.19.60): resolution: {integrity: sha512-t23gjdZV6aWkbwXSE3TkKr1UXJFbXICvAOJ0MRQEB/ZYGhfSJqqrQFaGd20I1a/nIIHJEkNO0xzycHixjcbCPw==} @@ -4657,6 +4655,7 @@ packages: /colors@1.2.5: resolution: {integrity: sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==} engines: {node: '>=0.1.90'} + dev: true /colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} @@ -8630,6 +8629,7 @@ packages: /lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + dev: true /lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -12455,6 +12455,7 @@ packages: /validator@13.9.0: resolution: {integrity: sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==} engines: {node: '>= 0.10'} + dev: true /vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -12832,6 +12833,7 @@ packages: validator: 13.9.0 optionalDependencies: commander: 9.5.0 + dev: true /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} diff --git a/common/build/eslint-config-fluid/CHANGELOG.md b/common/build/eslint-config-fluid/CHANGELOG.md index ecc9e4b25867..2d8d4bee4963 100644 --- a/common/build/eslint-config-fluid/CHANGELOG.md +++ b/common/build/eslint-config-fluid/CHANGELOG.md @@ -1,5 +1,43 @@ # @fluidframework/eslint-config-fluid Changelog +## [5.5.1](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v5.5.1) + +### Disabled rules + +The formatting-related rules below have been disabled in all configs because we use biome or prettier to enforce +formatting conventions. In addition, most of these rules are now deprecated because linters are decreasing their focus +on formatting-related rules in favor of dedicated formatting tools. + +#### typescript-eslint + +- @typescript-eslint/comma-spacing +- @typescript-eslint/func-call-spacing +- @typescript-eslint/keyword-spacing +- @typescript-eslint/member-delimiter-style +- @typescript-eslint/object-curly-spacing +- @typescript-eslint/semi +- @typescript-eslint/space-before-function-paren +- @typescript-eslint/space-infix-ops +- @typescript-eslint/type-annotation-spacing + +#### eslint + +All rules below are deprecated. See + +- array-bracket-spacing +- arrow-spacing +- block-spacing +- dot-location +- jsx-quotes +- key-spacing +- space-unary-ops +- switch-colon-spacing + +### Better test pattern support + +Update rule overrides for test code to better support patterns in the repo. +Namely, adds the allowance to "\*\*/tests" directories. + ## [5.4.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v5.4.0) ### New no-unchecked-record-access rule diff --git a/common/build/eslint-config-fluid/base.js b/common/build/eslint-config-fluid/base.js index 616a2a4b9a38..5904a6f5e072 100644 --- a/common/build/eslint-config-fluid/base.js +++ b/common/build/eslint-config-fluid/base.js @@ -135,15 +135,11 @@ module.exports = { }, ], - // eslint-plugin-import + // #region eslint-plugin-import + "import/no-default-export": "error", "import/no-deprecated": "off", - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: ["**/*.spec.ts", "src/test/**"], - }, - ], + "import/no-extraneous-dependencies": "error", "import/no-internal-modules": "error", "import/no-unassigned-import": "error", "import/no-unresolved": [ @@ -168,6 +164,8 @@ module.exports = { }, ], + // #region + // eslint-plugin-unicorn "unicorn/better-regex": "error", "unicorn/filename-case": [ diff --git a/common/build/eslint-config-fluid/minimal-deprecated.js b/common/build/eslint-config-fluid/minimal-deprecated.js index 8399710a8db7..1aca3bd77929 100644 --- a/common/build/eslint-config-fluid/minimal-deprecated.js +++ b/common/build/eslint-config-fluid/minimal-deprecated.js @@ -100,6 +100,15 @@ module.exports = { */ "@fluid-internal/fluid/no-member-release-tags": "error", + /** + * Rule to enforce safe property access on index signature types. + * + * Reports issues when non-array index properties are accessed without handling + * the possibility that they are absent. + * Enabling `noUncheckedIndexedAccess` will disable these checks. + */ + "@fluid-internal/fluid/no-unchecked-record-access": "error", + /** * The @rushstack rules are documented in the package README: * {@link https://www.npmjs.com/package/@rushstack/eslint-plugin} @@ -254,45 +263,30 @@ module.exports = { // #region FORMATTING RULES - // Disabled because it conflicts with formatter rules + // We use formatting tools like Biome or prettier to format code, so most formatting-related rules are superfluous + // and are disabled. Running fewer rules also improves lint performance. + + // The rules below are also deprecated in more recent versions of eslint/plugins "@typescript-eslint/brace-style": "off", - "@typescript-eslint/comma-spacing": "error", - "@typescript-eslint/func-call-spacing": "error", - "@typescript-eslint/keyword-spacing": "error", - "@typescript-eslint/member-delimiter-style": [ - "error", - { - multiline: { - delimiter: "semi", - requireLast: true, - }, - singleline: { - delimiter: "semi", - requireLast: true, - }, - multilineDetection: "brackets", - }, - ], - "@typescript-eslint/object-curly-spacing": ["error", "always"], - "@typescript-eslint/semi": ["error", "always"], - "@typescript-eslint/space-before-function-paren": [ - "error", - { - anonymous: "never", - asyncArrow: "always", - named: "never", - }, - ], - "@typescript-eslint/space-infix-ops": "error", - "@typescript-eslint/type-annotation-spacing": "error", - "array-bracket-spacing": "error", - "arrow-spacing": "error", - "block-spacing": "error", - "dot-location": ["error", "property"], - "jsx-quotes": "error", - "key-spacing": "error", - "space-unary-ops": "error", - "switch-colon-spacing": "error", + "@typescript-eslint/comma-spacing": "off", + "@typescript-eslint/func-call-spacing": "off", + "@typescript-eslint/keyword-spacing": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/semi": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/space-infix-ops": "off", + "@typescript-eslint/type-annotation-spacing": "off", + + // The rules below are deprecated in our current version of eslint/plugins + "@typescript-eslint/object-curly-spacing": "off", + "array-bracket-spacing": "off", + "arrow-spacing": "off", + "block-spacing": "off", + "dot-location": "off", + "jsx-quotes": "off", + "key-spacing": "off", + "space-unary-ops": "off", + "switch-colon-spacing": "off", // #endregion @@ -432,7 +426,13 @@ module.exports = { }, { // Rules only for test files - files: ["*.spec.ts", "*.test.ts", "**/test/**"], + files: [ + "*.spec.ts", + "*.test.ts", + "**/test/**", + // TODO: consider unifying code across the repo to use "test" and not "tests", then we can remove this. + "**/tests/**", + ], rules: { "@typescript-eslint/no-invalid-this": "off", "@typescript-eslint/unbound-method": "off", // This rule has false positives in many of our test projects. @@ -452,6 +452,9 @@ module.exports = { ), }, ], + + // Test code may leverage dev dependencies + "import/no-extraneous-dependencies": ["error", { devDependencies: true }], }, }, ], diff --git a/common/build/eslint-config-fluid/package.json b/common/build/eslint-config-fluid/package.json index b7e338994f9e..6946c1775ed9 100644 --- a/common/build/eslint-config-fluid/package.json +++ b/common/build/eslint-config-fluid/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/eslint-config-fluid", - "version": "5.5.0", + "version": "5.5.1", "description": "Shareable ESLint config for the Fluid Framework", "homepage": "https://fluidframework.com", "repository": { @@ -31,18 +31,18 @@ "@fluid-internal/eslint-plugin-fluid": "^0.1.2", "@microsoft/tsdoc": "^0.14.2", "@rushstack/eslint-patch": "~1.4.0", - "@rushstack/eslint-plugin": "~0.13.0", - "@rushstack/eslint-plugin-security": "~0.7.0", - "@typescript-eslint/eslint-plugin": "~6.7.2", - "@typescript-eslint/parser": "~6.7.2", + "@rushstack/eslint-plugin": "~0.13.1", + "@rushstack/eslint-plugin-security": "~0.7.1", + "@typescript-eslint/eslint-plugin": "~6.7.5", + "@typescript-eslint/parser": "~6.7.5", "eslint-config-prettier": "~9.0.0", - "eslint-import-resolver-typescript": "~3.6.1", + "eslint-import-resolver-typescript": "~3.6.3", "eslint-plugin-eslint-comments": "~3.2.0", - "eslint-plugin-import": "npm:eslint-plugin-i@~2.29.0", - "eslint-plugin-jsdoc": "~46.8.1", + "eslint-plugin-import": "npm:eslint-plugin-i@~2.29.1", + "eslint-plugin-jsdoc": "~46.8.2", "eslint-plugin-promise": "~6.1.1", "eslint-plugin-react": "~7.33.2", - "eslint-plugin-react-hooks": "~4.6.0", + "eslint-plugin-react-hooks": "~4.6.2", "eslint-plugin-tsdoc": "~0.2.17", "eslint-plugin-unicorn": "~48.0.1", "eslint-plugin-unused-imports": "~3.0.0" @@ -50,7 +50,7 @@ "devDependencies": { "@fluid-tools/markdown-magic": "file:../../../tools/markdown-magic", "@fluidframework/build-common": "^2.0.3", - "concurrently": "^8.2.1", + "concurrently": "^8.2.2", "eslint": "~8.55.0", "mocha-multi-reporters": "^1.5.1", "prettier": "~3.0.3", diff --git a/common/build/eslint-config-fluid/pnpm-lock.yaml b/common/build/eslint-config-fluid/pnpm-lock.yaml index ff166c69d223..185ed812581a 100644 --- a/common/build/eslint-config-fluid/pnpm-lock.yaml +++ b/common/build/eslint-config-fluid/pnpm-lock.yaml @@ -18,32 +18,32 @@ importers: specifier: ~1.4.0 version: 1.4.0 '@rushstack/eslint-plugin': - specifier: ~0.13.0 - version: 0.13.0(eslint@8.55.0)(typescript@5.1.6) + specifier: ~0.13.1 + version: 0.13.1(eslint@8.55.0)(typescript@5.1.6) '@rushstack/eslint-plugin-security': - specifier: ~0.7.0 - version: 0.7.0(eslint@8.55.0)(typescript@5.1.6) + specifier: ~0.7.1 + version: 0.7.1(eslint@8.55.0)(typescript@5.1.6) '@typescript-eslint/eslint-plugin': - specifier: ~6.7.2 - version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.55.0)(typescript@5.1.6) + specifier: ~6.7.5 + version: 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.55.0)(typescript@5.1.6) '@typescript-eslint/parser': - specifier: ~6.7.2 - version: 6.7.2(eslint@8.55.0)(typescript@5.1.6) + specifier: ~6.7.5 + version: 6.7.5(eslint@8.55.0)(typescript@5.1.6) eslint-config-prettier: specifier: ~9.0.0 version: 9.0.0(eslint@8.55.0) eslint-import-resolver-typescript: - specifier: ~3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.7.2)(eslint-plugin-i@2.29.0)(eslint@8.55.0) + specifier: ~3.6.3 + version: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) eslint-plugin-eslint-comments: specifier: ~3.2.0 version: 3.2.0(eslint@8.55.0) eslint-plugin-import: - specifier: npm:eslint-plugin-i@~2.29.0 - version: /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + specifier: npm:eslint-plugin-i@~2.29.1 + version: /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) eslint-plugin-jsdoc: - specifier: ~46.8.1 - version: 46.8.1(eslint@8.55.0) + specifier: ~46.8.2 + version: 46.8.2(eslint@8.55.0) eslint-plugin-promise: specifier: ~6.1.1 version: 6.1.1(eslint@8.55.0) @@ -51,8 +51,8 @@ importers: specifier: ~7.33.2 version: 7.33.2(eslint@8.55.0) eslint-plugin-react-hooks: - specifier: ~4.6.0 - version: 4.6.0(eslint@8.55.0) + specifier: ~4.6.2 + version: 4.6.2(eslint@8.55.0) eslint-plugin-tsdoc: specifier: ~0.2.17 version: 0.2.17 @@ -61,7 +61,7 @@ importers: version: 48.0.1(eslint@8.55.0) eslint-plugin-unused-imports: specifier: ~3.0.0 - version: 3.0.0(@typescript-eslint/eslint-plugin@6.7.2)(eslint@8.55.0) + version: 3.0.0(@typescript-eslint/eslint-plugin@6.7.5)(eslint@8.55.0) devDependencies: '@fluid-tools/markdown-magic': specifier: file:../../../tools/markdown-magic @@ -70,8 +70,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 concurrently: - specifier: ^8.2.1 - version: 8.2.1 + specifier: ^8.2.2 + version: 8.2.2 eslint: specifier: ~8.55.0 version: 8.55.0 @@ -230,16 +230,21 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@nolyfill/is-core-module@1.0.39: + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + dev: false + /@rushstack/eslint-patch@1.4.0: resolution: {integrity: sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==} dev: false - /@rushstack/eslint-plugin-security@0.7.0(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-vT+LMd1bWbb5XPpRGwyIk90LquYwA+CRNc9TvrbaXyyPi9X9GjVnYZPg9MRWWBD2to4llAozSdKbTv/eCd3ToA==} + /@rushstack/eslint-plugin-security@0.7.1(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-84N42tlONhcbXdlk5Rkb+/pVxPnH+ojX8XwtFoecCRV88/4Ii7eGEyJPb73lOpHaE3NJxLzLVIeixKYQmdjImA==} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@rushstack/tree-pattern': 0.3.0 + '@rushstack/tree-pattern': 0.3.1 '@typescript-eslint/experimental-utils': 5.59.11(eslint@8.55.0)(typescript@5.1.6) eslint: 8.55.0 transitivePeerDependencies: @@ -247,12 +252,12 @@ packages: - typescript dev: false - /@rushstack/eslint-plugin@0.13.0(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-xXV3xdo/r5X+XlWFJ6XiDsV3CRbPV1Q3zZtZ3ajKSpVfDrQBQX9A0v/cOBxDZtsCR+XOlsBsJjxaUgBePvOsrw==} + /@rushstack/eslint-plugin@0.13.1(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-qQ6iPCm8SFuY+bpcSv5hlYtdwDHcFlE6wlpUHa0ywG9tGVBYM5But8S4qVRFq1iejAuFX+ubNUOyFJHvxpox+A==} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@rushstack/tree-pattern': 0.3.0 + '@rushstack/tree-pattern': 0.3.1 '@typescript-eslint/experimental-utils': 5.59.11(eslint@8.55.0)(typescript@5.1.6) eslint: 8.55.0 transitivePeerDependencies: @@ -277,8 +282,8 @@ packages: z-schema: 5.0.5 dev: true - /@rushstack/tree-pattern@0.3.0: - resolution: {integrity: sha512-ivS4cLAYu6cy0K7bHPtafcdS2d+12lTV5oa3NgjfgKcmUSd/jlxkksUdmonKtaGsuVroGn89d5LB56IwsEjaGg==} + /@rushstack/tree-pattern@0.3.1: + resolution: {integrity: sha512-2yn4qTkXZTByQffL3ymS6viYuyZk3YnJT49bopGBlm9Thtyfa7iuFUV6tt+09YIRO1sjmSWILf4dPj6+Dr5YVA==} dev: false /@technote-space/anchor-markdown-header@1.1.42: @@ -437,8 +442,8 @@ packages: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} dev: true - /@typescript-eslint/eslint-plugin@6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q==} + /@typescript-eslint/eslint-plugin@6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -449,11 +454,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.8.1 - '@typescript-eslint/parser': 6.7.2(eslint@8.55.0)(typescript@5.1.6) - '@typescript-eslint/scope-manager': 6.7.2 - '@typescript-eslint/type-utils': 6.7.2(eslint@8.55.0)(typescript@5.1.6) - '@typescript-eslint/utils': 6.7.2(eslint@8.55.0)(typescript@5.1.6) - '@typescript-eslint/visitor-keys': 6.7.2 + '@typescript-eslint/parser': 6.7.5(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/scope-manager': 6.7.5 + '@typescript-eslint/type-utils': 6.7.5(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/utils': 6.7.5(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/visitor-keys': 6.7.5 debug: 4.3.4(supports-color@8.1.1) eslint: 8.55.0 graphemer: 1.4.0 @@ -500,8 +505,8 @@ packages: - supports-color dev: false - /@typescript-eslint/parser@6.7.2(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==} + /@typescript-eslint/parser@6.7.5(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -510,10 +515,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.7.2 - '@typescript-eslint/types': 6.7.2 - '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.1.6) - '@typescript-eslint/visitor-keys': 6.7.2 + '@typescript-eslint/scope-manager': 6.7.5 + '@typescript-eslint/types': 6.7.5 + '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.1.6) + '@typescript-eslint/visitor-keys': 6.7.5 debug: 4.3.4(supports-color@8.1.1) eslint: 8.55.0 typescript: 5.1.6 @@ -537,16 +542,16 @@ packages: '@typescript-eslint/visitor-keys': 6.21.0 dev: false - /@typescript-eslint/scope-manager@6.7.2: - resolution: {integrity: sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw==} + /@typescript-eslint/scope-manager@6.7.5: + resolution: {integrity: sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.7.2 - '@typescript-eslint/visitor-keys': 6.7.2 + '@typescript-eslint/types': 6.7.5 + '@typescript-eslint/visitor-keys': 6.7.5 dev: false - /@typescript-eslint/type-utils@6.7.2(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ==} + /@typescript-eslint/type-utils@6.7.5(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -555,8 +560,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.1.6) - '@typescript-eslint/utils': 6.7.2(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.1.6) + '@typescript-eslint/utils': 6.7.5(eslint@8.55.0)(typescript@5.1.6) debug: 4.3.4(supports-color@8.1.1) eslint: 8.55.0 ts-api-utils: 1.0.3(typescript@5.1.6) @@ -575,8 +580,8 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: false - /@typescript-eslint/types@6.7.2: - resolution: {integrity: sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==} + /@typescript-eslint/types@6.7.5: + resolution: {integrity: sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==} engines: {node: ^16.0.0 || >=18.0.0} dev: false @@ -623,8 +628,8 @@ packages: - supports-color dev: false - /@typescript-eslint/typescript-estree@6.7.2(typescript@5.1.6): - resolution: {integrity: sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==} + /@typescript-eslint/typescript-estree@6.7.5(typescript@5.1.6): + resolution: {integrity: sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -632,8 +637,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.7.2 - '@typescript-eslint/visitor-keys': 6.7.2 + '@typescript-eslint/types': 6.7.5 + '@typescript-eslint/visitor-keys': 6.7.5 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 @@ -664,8 +669,8 @@ packages: - typescript dev: false - /@typescript-eslint/utils@6.7.2(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ==} + /@typescript-eslint/utils@6.7.5(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -673,9 +678,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.2 - '@typescript-eslint/scope-manager': 6.7.2 - '@typescript-eslint/types': 6.7.2 - '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.1.6) + '@typescript-eslint/scope-manager': 6.7.5 + '@typescript-eslint/types': 6.7.5 + '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.1.6) eslint: 8.55.0 semver: 7.5.4 transitivePeerDependencies: @@ -699,11 +704,11 @@ packages: eslint-visitor-keys: 3.4.3 dev: false - /@typescript-eslint/visitor-keys@6.7.2: - resolution: {integrity: sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==} + /@typescript-eslint/visitor-keys@6.7.5: + resolution: {integrity: sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.7.2 + '@typescript-eslint/types': 6.7.5 eslint-visitor-keys: 3.4.3 dev: false @@ -1064,8 +1069,8 @@ packages: typedarray: 0.0.6 dev: true - /concurrently@8.2.1: - resolution: {integrity: sha512-nVraf3aXOpIcNud5pB9M82p1tynmZkrSGQ1p6X/VY8cJ+2LMVqAgXsJxYYefACSHbTYlm92O1xuhdGTjwoEvbQ==} + /concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} engines: {node: ^14.13.0 || >=16.0.0} hasBin: true dependencies: @@ -1122,6 +1127,18 @@ packages: ms: 2.1.2 supports-color: 8.1.1 + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + /decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} @@ -1394,21 +1411,28 @@ packages: - supports-color dev: false - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.2)(eslint-plugin-i@2.29.0)(eslint@8.55.0): - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0): + resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + '@nolyfill/is-core-module': 1.0.39 + debug: 4.3.7 enhanced-resolve: 5.15.0 eslint: 8.55.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-import: /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - fast-glob: 3.3.1 - get-tsconfig: 4.7.2 - is-core-module: 2.13.0 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) + eslint-plugin-import: /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) + fast-glob: 3.3.2 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 is-glob: 4.0.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -1417,7 +1441,36 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): + /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.7.5(eslint@8.55.0)(typescript@5.1.6) + debug: 3.2.7 + eslint: 8.55.0 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -1438,11 +1491,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.7.2(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.7.5(eslint@8.55.0)(typescript@5.1.6) debug: 3.2.7 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.2)(eslint-plugin-i@2.29.0)(eslint@8.55.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) transitivePeerDependencies: - supports-color dev: false @@ -1458,21 +1511,20 @@ packages: ignore: 5.2.4 dev: false - /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): - resolution: {integrity: sha512-slGeTS3GQzx9267wLJnNYNO8X9EHGsc75AKIAFvnvMYEcTJKotPKL1Ru5PIGVHIVet+2DsugePWp8Oxpx8G22w==} + /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): + resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==} engines: {node: '>=12'} peerDependencies: eslint: ^7.2.0 || ^8 dependencies: - debug: 3.2.7 - doctrine: 2.1.0 + debug: 4.3.4(supports-color@8.1.1) + doctrine: 3.0.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) get-tsconfig: 4.7.2 is-glob: 4.0.3 minimatch: 3.1.2 - resolve: 1.22.6 semver: 7.5.4 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -1481,8 +1533,8 @@ packages: - supports-color dev: false - /eslint-plugin-jsdoc@46.8.1(eslint@8.55.0): - resolution: {integrity: sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A==} + /eslint-plugin-jsdoc@46.8.2(eslint@8.55.0): + resolution: {integrity: sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ==} engines: {node: '>=16'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -1510,8 +1562,8 @@ packages: eslint: 8.55.0 dev: false - /eslint-plugin-react-hooks@4.6.0(eslint@8.55.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + /eslint-plugin-react-hooks@4.6.2(eslint@8.55.0): + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 @@ -1575,7 +1627,7 @@ packages: strip-indent: 3.0.0 dev: false - /eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@6.7.2)(eslint@8.55.0): + /eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@6.7.5)(eslint@8.55.0): resolution: {integrity: sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1585,7 +1637,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.55.0)(typescript@5.1.6) + '@typescript-eslint/eslint-plugin': 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.55.0)(typescript@5.1.6) eslint: 8.55.0 eslint-rule-composer: 0.3.0 dev: false @@ -1709,6 +1761,7 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 + dev: true /fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} @@ -1879,6 +1932,12 @@ packages: resolve-pkg-maps: 1.0.0 dev: false + /get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1946,7 +2005,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.1 + fast-glob: 3.3.2 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -2153,6 +2212,12 @@ packages: builtin-modules: 3.3.0 dev: false + /is-bun-module@1.2.1: + resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} + dependencies: + semver: 7.6.3 + dev: false + /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -3209,6 +3274,12 @@ packages: dependencies: lru-cache: 6.0.0 + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + dev: false + /serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} dependencies: diff --git a/common/build/eslint-config-fluid/printed-configs/default.json b/common/build/eslint-config-fluid/printed-configs/default.json index c2b1595d0311..2d692a3d752f 100644 --- a/common/build/eslint-config-fluid/printed-configs/default.json +++ b/common/build/eslint-config-fluid/printed-configs/default.json @@ -44,6 +44,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "error" ], @@ -82,7 +85,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "off" @@ -126,7 +129,7 @@ "error" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -135,24 +138,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -316,8 +308,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -370,14 +361,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -385,7 +376,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -394,7 +385,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -412,7 +403,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -425,7 +416,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -437,7 +428,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -473,8 +464,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -589,13 +579,7 @@ 1 ], "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] - } + "error" ], "import/no-internal-modules": [ "error", @@ -701,10 +685,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1217,7 +1201,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1244,7 +1228,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/printed-configs/minimal.json b/common/build/eslint-config-fluid/printed-configs/minimal.json index fd0f45ff1704..de94dcd4522d 100644 --- a/common/build/eslint-config-fluid/printed-configs/minimal.json +++ b/common/build/eslint-config-fluid/printed-configs/minimal.json @@ -44,6 +44,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "warn" ], @@ -82,7 +85,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "off" @@ -119,7 +122,7 @@ "off" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -128,24 +131,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -306,8 +298,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -360,14 +351,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -375,7 +366,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -384,7 +375,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -402,7 +393,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -415,7 +406,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -427,7 +418,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -463,8 +454,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -579,13 +569,7 @@ 1 ], "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] - } + "error" ], "import/no-internal-modules": [ "error", @@ -677,10 +661,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1190,7 +1174,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1217,7 +1201,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/printed-configs/react.json b/common/build/eslint-config-fluid/printed-configs/react.json index 7af7156e8409..9a2cc673c206 100644 --- a/common/build/eslint-config-fluid/printed-configs/react.json +++ b/common/build/eslint-config-fluid/printed-configs/react.json @@ -46,6 +46,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "error" ], @@ -84,7 +87,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "off" @@ -128,7 +131,7 @@ "error" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -137,24 +140,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -318,8 +310,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -372,14 +363,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -387,7 +378,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -396,7 +387,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -414,7 +405,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -427,7 +418,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -439,7 +430,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -475,8 +466,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -591,13 +581,7 @@ 1 ], "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] - } + "error" ], "import/no-internal-modules": [ "error", @@ -703,10 +687,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1291,7 +1275,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1318,7 +1302,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/printed-configs/recommended.json b/common/build/eslint-config-fluid/printed-configs/recommended.json index c2b1595d0311..2d692a3d752f 100644 --- a/common/build/eslint-config-fluid/printed-configs/recommended.json +++ b/common/build/eslint-config-fluid/printed-configs/recommended.json @@ -44,6 +44,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "error" ], @@ -82,7 +85,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "off" @@ -126,7 +129,7 @@ "error" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -135,24 +138,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -316,8 +308,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -370,14 +361,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -385,7 +376,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -394,7 +385,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -412,7 +403,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -425,7 +416,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -437,7 +428,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -473,8 +464,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -589,13 +579,7 @@ 1 ], "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] - } + "error" ], "import/no-internal-modules": [ "error", @@ -701,10 +685,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1217,7 +1201,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1244,7 +1228,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/printed-configs/strict.json b/common/build/eslint-config-fluid/printed-configs/strict.json index 53a7b129021b..68f5bcfacbce 100644 --- a/common/build/eslint-config-fluid/printed-configs/strict.json +++ b/common/build/eslint-config-fluid/printed-configs/strict.json @@ -44,6 +44,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "error" ], @@ -82,7 +85,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "error" @@ -142,7 +145,7 @@ "error" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -151,24 +154,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -335,8 +327,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -389,14 +380,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -404,7 +395,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -413,7 +404,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -431,7 +422,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -444,7 +435,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -456,7 +447,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -492,8 +483,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -608,13 +598,7 @@ 1 ], "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] - } + "error" ], "import/no-internal-modules": [ "error", @@ -747,10 +731,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1263,7 +1247,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1290,7 +1274,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/printed-configs/test.json b/common/build/eslint-config-fluid/printed-configs/test.json index 201ea7950bb9..bf33b7dfe780 100644 --- a/common/build/eslint-config-fluid/printed-configs/test.json +++ b/common/build/eslint-config-fluid/printed-configs/test.json @@ -44,6 +44,9 @@ "@fluid-internal/fluid/no-member-release-tags": [ "error" ], + "@fluid-internal/fluid/no-unchecked-record-access": [ + "error" + ], "@rushstack/no-new-null": [ "error" ], @@ -82,7 +85,7 @@ "always-multiline" ], "@typescript-eslint/comma-spacing": [ - "error" + "off" ], "@typescript-eslint/consistent-generic-constructors": [ "off" @@ -126,7 +129,7 @@ "error" ], "@typescript-eslint/func-call-spacing": [ - "error" + "off" ], "@typescript-eslint/indent": [ "off" @@ -135,24 +138,13 @@ "off" ], "@typescript-eslint/keyword-spacing": [ - "error" + "off" ], "@typescript-eslint/lines-around-comment": [ 0 ], "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - }, - "multilineDetection": "brackets" - } + "off" ], "@typescript-eslint/member-ordering": [ "off" @@ -316,8 +308,7 @@ "off" ], "@typescript-eslint/object-curly-spacing": [ - "error", - "always" + "off" ], "@typescript-eslint/prefer-as-const": [ "error" @@ -370,14 +361,14 @@ "error" ], "@typescript-eslint/semi": [ - "error", + "off", "always" ], "@typescript-eslint/space-before-blocks": [ "off" ], "@typescript-eslint/space-before-function-paren": [ - "error", + "off", { "anonymous": "never", "asyncArrow": "always", @@ -385,7 +376,7 @@ } ], "@typescript-eslint/space-infix-ops": [ - "error" + "off" ], "@typescript-eslint/strict-boolean-expressions": [ "error" @@ -394,7 +385,7 @@ "error" ], "@typescript-eslint/type-annotation-spacing": [ - "error" + "off" ], "@typescript-eslint/typedef": [ "off" @@ -412,7 +403,7 @@ "off" ], "array-bracket-spacing": [ - "error" + "off" ], "array-element-newline": [ "off" @@ -425,7 +416,7 @@ "always" ], "arrow-spacing": [ - "error" + "off" ], "babel/object-curly-spacing": [ "off" @@ -437,7 +428,7 @@ "off" ], "block-spacing": [ - "error" + "off" ], "brace-style": [ "off" @@ -473,8 +464,7 @@ "error" ], "dot-location": [ - "error", - "property" + "off" ], "dot-notation": [ "off" @@ -591,10 +581,7 @@ "import/no-extraneous-dependencies": [ "error", { - "devDependencies": [ - "**/*.spec.ts", - "src/test/**" - ] + "devDependencies": true } ], "import/no-internal-modules": [ @@ -703,10 +690,10 @@ "error" ], "jsx-quotes": [ - "error" + "off" ], "key-spacing": [ - "error" + "off" ], "keyword-spacing": [ "off" @@ -1219,7 +1206,7 @@ "off" ], "space-unary-ops": [ - "error" + "off" ], "space-unary-word-ops": [ "off" @@ -1246,7 +1233,7 @@ "off" ], "switch-colon-spacing": [ - "error" + "off" ], "template-curly-spacing": [ "off" diff --git a/common/build/eslint-config-fluid/recommended.js b/common/build/eslint-config-fluid/recommended.js index d3c32c4ec9b6..835113221fac 100644 --- a/common/build/eslint-config-fluid/recommended.js +++ b/common/build/eslint-config-fluid/recommended.js @@ -196,10 +196,17 @@ module.exports = { }, { // Rules for test code - files: ["*.spec.ts", "*.test.ts", "**/test/**"], + files: [ + "*.spec.ts", + "*.test.ts", + "**/test/**", + // TODO: consider unifying code across the repo to use "test" and not "tests", then we can remove this. + "**/tests/**", + ], rules: { // Does not work well with describe/it block scoping "unicorn/consistent-function-scoping": "off", + // We run most of our tests in a Node.js environment, so this rule is not important and makes // file-system logic more cumbersome. "unicorn/prefer-module": "off", diff --git a/common/build/eslint-plugin-fluid/package.json b/common/build/eslint-plugin-fluid/package.json index 9827c611928f..c766fffef946 100644 --- a/common/build/eslint-plugin-fluid/package.json +++ b/common/build/eslint-plugin-fluid/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/eslint-plugin-fluid", - "version": "0.1.2", + "version": "0.1.3", "description": "Custom ESLint rules for the Fluid Framework", "homepage": "https://fluidframework.com", "repository": { diff --git a/common/build/eslint-plugin-fluid/src/rules/no-unchecked-record-access.js b/common/build/eslint-plugin-fluid/src/rules/no-unchecked-record-access.js index 83e9d320df6c..53be8b4b8072 100644 --- a/common/build/eslint-plugin-fluid/src/rules/no-unchecked-record-access.js +++ b/common/build/eslint-plugin-fluid/src/rules/no-unchecked-record-access.js @@ -160,14 +160,6 @@ module.exports = { }, }; -// Helper function to check if a type has an index signature -function isIndexSignatureType(parserServices, node) { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object); - const typeChecker = parserServices.program.getTypeChecker(); - const type = typeChecker.getTypeAtLocation(tsNode); - return type.getStringIndexType() !== undefined; -} - // Helper function to check if a type includes undefined function isTypeUndefinable(type) { if (type.isUnion()) { @@ -176,17 +168,123 @@ function isTypeUndefinable(type) { return false; } +// Helper function to check if a type has an index signature +function isIndexSignatureType(parserServices, node) { + if (!node || !node.object) { + return false; + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object); + if (!tsNode) { + return false; + } + + const typeChecker = parserServices.program.getTypeChecker(); + const type = typeChecker.getTypeAtLocation(tsNode); + if (!type) { + return false; + } + + try { + // Check if this is a type with an index signature + const stringIndexType = type.getStringIndexType(); + const numberIndexType = type.getNumberIndexType(); + + // If it's not a type with an index signature, no need to check further + if (!stringIndexType && !numberIndexType) { + return false; + } + + // For array types, we don't want to treat numeric indexing as unsafe + if (type.symbol && type.symbol.escapedName === "Array") { + return false; + } + + // For types with index signatures, we need to check if the property being accessed + // is statically declared (not from the index signature) + const propName = node.property && (node.computed ? node.property.value : node.property.name); + if (!propName) { + return true; // If we can't determine the property name, be conservative + } + + const propSymbol = type.getProperty(propName); + if (!propSymbol) { + return true; // Property doesn't exist statically, must be from index signature + } + + // Check if the property is actually from an explicit declaration + const declarations = propSymbol.declarations || []; + const isFromIndexSignature = declarations.some(decl => + decl && (decl.kind === SyntaxKind.IndexSignature || !decl.name) + ); + + // If the property has no declarations or comes from an index signature, treat it as unsafe + return isFromIndexSignature || declarations.length === 0; + + } catch (e) { + // If there's any error in type checking, be conservative + return true; + } +} + // Helper function to check if an index signature type includes undefined function isUndefinableIndexSignatureType(parserServices, node) { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object); - const typeChecker = parserServices.program.getTypeChecker(); - const type = typeChecker.getTypeAtLocation(tsNode); - const indexType = type.getStringIndexType(); - return ( - indexType && - (indexType.flags & TypeFlags.Undefined || - (indexType.isUnion() && indexType.types.some((t) => t.flags & TypeFlags.Undefined))) - ); + if (!node || !node.object) { + return false; + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object); + if (!tsNode) { + return false; + } + + const typeChecker = parserServices.program.getTypeChecker(); + const type = typeChecker.getTypeAtLocation(tsNode); + if (!type) { + return false; + } + + // Get the property being accessed + const propName = node.property && (node.computed ? node.property.value : node.property.name); + if (!propName) { + return false; + } + + try { + // Check if it's a property explicitly defined (not from index signature) + const propSymbol = type.getProperty(propName); + if (propSymbol) { + const declarations = propSymbol.declarations || []; + const isFromIndexSignature = declarations.some( + decl => decl && decl.kind === SyntaxKind.IndexSignature + ); + + if (!isFromIndexSignature && declarations.length > 0) { + return false; + } + } + + // Check both string and number index signatures + const stringIndexType = type.getStringIndexType(); + const numberIndexType = type.getNumberIndexType(); + + const isStringIndexUndefinable = stringIndexType && ( + stringIndexType.flags & TypeFlags.Undefined || + (stringIndexType.isUnion && stringIndexType.isUnion() && + stringIndexType.types.some(t => t.flags & TypeFlags.Undefined)) + ); + + const isNumberIndexUndefinable = numberIndexType && ( + numberIndexType.flags & TypeFlags.Undefined || + (numberIndexType.isUnion && numberIndexType.isUnion() && + numberIndexType.types.some(t => t.flags & TypeFlags.Undefined)) + ); + + return isStringIndexUndefinable || isNumberIndexUndefinable; + } catch (e) { + // If there's any error in type checking, assume it might be undefinable + return true; + } } // Helper function to traverse up the code until the scope ends and checks if the property access has been checked for undefined diff --git a/common/build/eslint-plugin-fluid/src/test/enforce-no-unchecked-record-access/enforce-no-unchecked-record-access.test.js b/common/build/eslint-plugin-fluid/src/test/enforce-no-unchecked-record-access/enforce-no-unchecked-record-access.test.js index 0571fb809403..59b452ea5df1 100644 --- a/common/build/eslint-plugin-fluid/src/test/enforce-no-unchecked-record-access/enforce-no-unchecked-record-access.test.js +++ b/common/build/eslint-plugin-fluid/src/test/enforce-no-unchecked-record-access/enforce-no-unchecked-record-access.test.js @@ -39,68 +39,70 @@ describe("ESLint Rule Tests", function () { it("Should report errors for unchecked record access for indexed record of strings", async function () { const result = await lintFile("indexedRecordOfStrings.ts"); + const expectedLines = [25, 27, 28, 29, 46, 61, 80, 82, 85, 92, 97]; - assert.strictEqual(result.errorCount, 10, "Should have 10 errors"); + assert.strictEqual(result.errorCount, 11, "Should have 11 errors"); assert.strictEqual( result.messages[0].message, "'indexedRecordOfStrings.a' is possibly 'undefined'", ); - assert.strictEqual(result.messages[0].line, 21); + assert.strictEqual(result.messages[0].line, expectedLines[0]); assert.strictEqual( result.messages[1].message, "'indexedRecordOfStrings[\"a\"]' is possibly 'undefined'", ); - assert.strictEqual(result.messages[1].line, 23); + assert.strictEqual(result.messages[1].line, expectedLines[1]); assert.strictEqual( result.messages[2].message, "'indexedRecordOfStrings[a]' is possibly 'undefined'", ); - assert.strictEqual(result.messages[2].line, 24); + assert.strictEqual(result.messages[2].line, expectedLines[2]); assert.strictEqual( result.messages[3].message, "'indexedRecordOfStrings[b]' is possibly 'undefined'", ); - assert.strictEqual(result.messages[3].line, 25); + assert.strictEqual(result.messages[3].line, expectedLines[3]); assert.strictEqual( result.messages[4].message, "Returning 'record.a' directly from an index signature type is not allowed. 'record.a' may be 'undefined'", ); - assert.strictEqual(result.messages[4].line, 42); + assert.strictEqual(result.messages[4].line, expectedLines[4]); assert.strictEqual( result.messages[5].message, "Passing 'indexedRecordOfStrings.a' from an index signature type to a strictly typed parameter is not allowed. 'indexedRecordOfStrings.a' may be 'undefined'", ); - assert.strictEqual(result.messages[5].line, 57); + assert.strictEqual(result.messages[5].line, expectedLines[5]); assert.strictEqual( result.messages[6].message, "'indexedRecordOfStrings.a' is possibly 'undefined'", ); - assert.strictEqual(result.messages[6].line, 76); + assert.strictEqual(result.messages[6].line, expectedLines[6]); assert.strictEqual( result.messages[7].message, "'indexedRecordOfStrings.a' is possibly 'undefined'", ); - assert.strictEqual(result.messages[7].line, 78); + assert.strictEqual(result.messages[7].line, expectedLines[7]); assert.strictEqual( result.messages[8].message, "Assigning 'indexedRecordOfStrings.a' from an index signature type to a strictly typed variable without 'undefined' is not allowed. 'indexedRecordOfStrings.a' may be 'undefined'", ); - assert.strictEqual(result.messages[8].line, 81); + assert.strictEqual(result.messages[8].line, expectedLines[8]); assert.strictEqual( result.messages[9].message, "Implicit typing derived from 'indexedRecordOfStrings.a' is not allowed. 'indexedRecordOfStrings' is an index signature type and 'a' may be undefined. Please provide an explicit type annotation including undefined or enable noUncheckedIndexedAccess", ); - assert.strictEqual(result.messages[9].line, 88); + assert.strictEqual(result.messages[9].line, expectedLines[9]); + assert.strictEqual(result.messages[10].line, expectedLines[10]); }); it("Should report an error for unchecked nested record access", async function () { @@ -115,43 +117,45 @@ describe("ESLint Rule Tests", function () { it("Should report errors for unchecked record access in nullableIndexedRecord", async function () { const result = await lintFile("nullableIndexedRecord.ts"); + const expectedLines = [21, 40, 47, 48, 53, 60, 80, 82, 85, 92, 97]; + assert.strictEqual(result.errorCount, 6, "Should have 6 errors"); assert.strictEqual( result.messages[0].message, "Returning 'record.a' directly from an index signature type is not allowed. 'record.a' may be 'undefined'", ); - assert.strictEqual(result.messages[0].line, 21); + assert.strictEqual(result.messages[0].line, expectedLines[0]); assert.strictEqual( result.messages[1].message, "Passing 'nullableIndexedRecord.a' from an index signature type to a strictly typed parameter is not allowed. 'nullableIndexedRecord.a' may be 'undefined'", ); - assert.strictEqual(result.messages[1].line, 40); + assert.strictEqual(result.messages[1].line, expectedLines[1]); assert.strictEqual( result.messages[2].message, "'nullableIndexedRecord.a' is possibly 'undefined'", ); - assert.strictEqual(result.messages[2].line, 47); + assert.strictEqual(result.messages[2].line, expectedLines[2]); assert.strictEqual( result.messages[3].message, "'nullableIndexedRecord.a' is possibly 'undefined'", ); - assert.strictEqual(result.messages[3].line, 48); + assert.strictEqual(result.messages[3].line, expectedLines[3]); assert.strictEqual( result.messages[4].message, "Assigning 'nullableIndexedRecord.a' from an index signature type to a strictly typed variable without 'undefined' is not allowed. 'nullableIndexedRecord.a' may be 'undefined'", ); - assert.strictEqual(result.messages[4].line, 53); + assert.strictEqual(result.messages[4].line, expectedLines[4]); assert.strictEqual( result.messages[5].message, "Implicit typing derived from 'nullableIndexedRecord.a' is not allowed. 'nullableIndexedRecord' is an index signature type and 'a' may be undefined. Please provide an explicit type annotation including undefined or enable noUncheckedIndexedAccess", ); - assert.strictEqual(result.messages[5].line, 60); + assert.strictEqual(result.messages[5].line, expectedLines[5]); }); it("Should not report errors for correct usage of undefinableIndexedRecord", async function () { diff --git a/common/build/eslint-plugin-fluid/src/test/example/no-unchecked-record-access/indexedRecordOfStrings.ts b/common/build/eslint-plugin-fluid/src/test/example/no-unchecked-record-access/indexedRecordOfStrings.ts index 1c7111a524e3..96815ee8254a 100644 --- a/common/build/eslint-plugin-fluid/src/test/example/no-unchecked-record-access/indexedRecordOfStrings.ts +++ b/common/build/eslint-plugin-fluid/src/test/example/no-unchecked-record-access/indexedRecordOfStrings.ts @@ -9,7 +9,11 @@ /* Constants and Variables */ type IndexSignatureType = { [key: string]: string }; +interface ExtendedIndexSignatureType extends IndexSignatureType { + a: string; +} const indexedRecordOfStrings: IndexSignatureType = { a: "hello", b: "goodbye" }; +const extendedIndexedRecordOfStrings: ExtendedIndexSignatureType = { a: "hello", b: "goodbye" }; const a = "a"; const b = "b"; @@ -87,3 +91,7 @@ aLetExpectingStringOrUndefinedAfterVariableDeclaration = indexedRecordOfStrings. */ const aImplicitType = indexedRecordOfStrings.a; // defect: Assigning index property with inferred type without an explicit undefined type is not allowed aImplicitType.length; // ok: aImplicitType is the continuation of the inferred type case and should be caught in the variable initialization + + +extendedIndexedRecordOfStrings.a.length; // ok: Accessing string property of extendedIndexedRecordOfStrings is allowed +extendedIndexedRecordOfStrings.b.length; // defect: Accessing length of index property 'b', but 'b' might not be present diff --git a/common/lib/common-utils/.eslintrc.cjs b/common/lib/common-utils/.eslintrc.cjs index e28f52796ec4..662736648041 100644 --- a/common/lib/common-utils/.eslintrc.cjs +++ b/common/lib/common-utils/.eslintrc.cjs @@ -14,9 +14,6 @@ module.exports = { ], }, rules: { - // TODO: Remove once this config extends `recommended` or `strict` above. - "@typescript-eslint/explicit-function-return-type": "error", - // This package is being deprecated, so it's okay to use deprecated APIs. "import/no-deprecated": "off", @@ -42,9 +39,6 @@ module.exports = { rules: { // It's fine for tests to use node.js modules. "import/no-nodejs-modules": "off", - - // It's fine for tests to use `__dirname`, etc. - "unicorn/prefer-module": "off", }, }, ], diff --git a/common/lib/common-utils/package.json b/common/lib/common-utils/package.json index 0349bffc266b..d33b894c2e60 100644 --- a/common/lib/common-utils/package.json +++ b/common/lib/common-utils/package.json @@ -88,7 +88,7 @@ "@fluidframework/build-common": "^2.0.3", "@fluidframework/build-tools": "^0.49.0", "@fluidframework/common-utils-previous": "npm:@fluidframework/common-utils@1.0.0", - "@fluidframework/eslint-config-fluid": "^5.4.0", + "@fluidframework/eslint-config-fluid": "^5.5.1", "@microsoft/api-extractor": "^7.45.1", "@types/base64-js": "^1.3.0", "@types/benchmark": "^2.1.0", diff --git a/common/lib/common-utils/pnpm-lock.yaml b/common/lib/common-utils/pnpm-lock.yaml index 8de6ef36845e..f97147158924 100644 --- a/common/lib/common-utils/pnpm-lock.yaml +++ b/common/lib/common-utils/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: npm:@fluidframework/common-utils@1.0.0 version: /@fluidframework/common-utils@1.0.0 '@fluidframework/eslint-config-fluid': - specifier: ^5.4.0 - version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) + specifier: ^5.5.1 + version: 5.5.1(eslint@8.55.0)(typescript@5.4.5) '@microsoft/api-extractor': specifier: ^7.45.1 version: 7.45.1(patch_hash=tos6v6doskwck2kr7bbxweswri)(@types/node@18.19.39) @@ -1415,8 +1415,8 @@ packages: sha.js: 2.4.11 dev: true - /@fluidframework/eslint-config-fluid@5.4.0(eslint@8.55.0)(typescript@5.4.5): - resolution: {integrity: sha512-V9lKsH1oFq3pX8UjSv8AyZ9BswPEcozGi3Ic/KuMdsYHj8Ibm3EgTtYSyNgVOAFivDW474qvXc5PDhKD8T/mfw==} + /@fluidframework/eslint-config-fluid@5.5.1(eslint@8.55.0)(typescript@5.4.5): + resolution: {integrity: sha512-rbHvimalOIyLd6nsK5uWJQylxwkztJc0yWKOrop8rrxgMR9dSuGnjJXcjhijZ0p1+zHbZ325+udoDFWw2HJZRQ==} dependencies: '@fluid-internal/eslint-plugin-fluid': 0.1.2(eslint@8.55.0)(typescript@5.4.5) '@microsoft/tsdoc': 0.14.2 diff --git a/common/lib/protocol-definitions/api-report/protocol-definitions.alpha.api.md b/common/lib/protocol-definitions/api-report/protocol-definitions.alpha.api.md index 6d10ed8d93dc..dc1b6145cc9d 100644 --- a/common/lib/protocol-definitions/api-report/protocol-definitions.alpha.api.md +++ b/common/lib/protocol-definitions/api-report/protocol-definitions.alpha.api.md @@ -345,7 +345,6 @@ export interface ISummaryProposal { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/common/lib/protocol-definitions/api-report/protocol-definitions.beta.api.md b/common/lib/protocol-definitions/api-report/protocol-definitions.beta.api.md index 7362623e5884..19e7e1710b40 100644 --- a/common/lib/protocol-definitions/api-report/protocol-definitions.beta.api.md +++ b/common/lib/protocol-definitions/api-report/protocol-definitions.beta.api.md @@ -126,7 +126,6 @@ export interface ISummaryHandle { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/common/lib/protocol-definitions/api-report/protocol-definitions.public.api.md b/common/lib/protocol-definitions/api-report/protocol-definitions.public.api.md index 5ddf146372e8..b92d9aa6761e 100644 --- a/common/lib/protocol-definitions/api-report/protocol-definitions.public.api.md +++ b/common/lib/protocol-definitions/api-report/protocol-definitions.public.api.md @@ -126,7 +126,6 @@ export interface ISummaryHandle { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/common/lib/protocol-definitions/package.json b/common/lib/protocol-definitions/package.json index bedd08880749..1cb74e9154e2 100644 --- a/common/lib/protocol-definitions/package.json +++ b/common/lib/protocol-definitions/package.json @@ -77,7 +77,7 @@ "@fluid-tools/build-cli": "^0.49.0", "@fluidframework/build-common": "^2.0.3", "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/eslint-config-fluid": "^5.4.0", + "@fluidframework/eslint-config-fluid": "^5.5.1", "@fluidframework/protocol-definitions-previous": "npm:@fluidframework/protocol-definitions@3.2.0", "@microsoft/api-extractor": "^7.45.1", "concurrently": "^6.2.0", diff --git a/common/lib/protocol-definitions/pnpm-lock.yaml b/common/lib/protocol-definitions/pnpm-lock.yaml index 616477527ea9..024c01478f6e 100644 --- a/common/lib/protocol-definitions/pnpm-lock.yaml +++ b/common/lib/protocol-definitions/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: specifier: ^0.49.0 version: 0.49.0 '@fluidframework/eslint-config-fluid': - specifier: ^5.4.0 - version: 5.4.0(eslint@8.55.0)(typescript@5.1.6) + specifier: ^5.5.1 + version: 5.5.1(eslint@8.55.0)(typescript@5.1.6) '@fluidframework/protocol-definitions-previous': specifier: npm:@fluidframework/protocol-definitions@3.2.0 version: /@fluidframework/protocol-definitions@3.2.0 @@ -1010,8 +1010,8 @@ packages: - webpack-cli dev: true - /@fluidframework/eslint-config-fluid@5.4.0(eslint@8.55.0)(typescript@5.1.6): - resolution: {integrity: sha512-V9lKsH1oFq3pX8UjSv8AyZ9BswPEcozGi3Ic/KuMdsYHj8Ibm3EgTtYSyNgVOAFivDW474qvXc5PDhKD8T/mfw==} + /@fluidframework/eslint-config-fluid@5.5.1(eslint@8.55.0)(typescript@5.1.6): + resolution: {integrity: sha512-rbHvimalOIyLd6nsK5uWJQylxwkztJc0yWKOrop8rrxgMR9dSuGnjJXcjhijZ0p1+zHbZ325+udoDFWw2HJZRQ==} dependencies: '@fluid-internal/eslint-plugin-fluid': 0.1.2(eslint@8.55.0)(typescript@5.1.6) '@microsoft/tsdoc': 0.14.2 @@ -1021,13 +1021,13 @@ packages: '@typescript-eslint/eslint-plugin': 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.55.0)(typescript@5.1.6) '@typescript-eslint/parser': 6.7.5(eslint@8.55.0)(typescript@5.1.6) eslint-config-prettier: 9.0.0(eslint@8.55.0) - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.0)(eslint@8.55.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) eslint-plugin-eslint-comments: 3.2.0(eslint@8.55.0) - eslint-plugin-import: /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) eslint-plugin-jsdoc: 46.8.2(eslint@8.55.0) eslint-plugin-promise: 6.1.1(eslint@8.55.0) eslint-plugin-react: 7.33.2(eslint@8.55.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.55.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.55.0) eslint-plugin-tsdoc: 0.2.17 eslint-plugin-unicorn: 48.0.1(eslint@8.55.0) eslint-plugin-unused-imports: 3.0.0(@typescript-eslint/eslint-plugin@6.7.5)(eslint@8.55.0) @@ -1035,6 +1035,7 @@ packages: - eslint - eslint-import-resolver-node - eslint-import-resolver-webpack + - eslint-plugin-import-x - supports-color - typescript dev: true @@ -1312,6 +1313,11 @@ packages: fastq: 1.15.0 dev: true + /@nolyfill/is-core-module@1.0.39: + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + dev: true + /@npmcli/fs@2.1.2: resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4227,14 +4233,6 @@ packages: once: 1.4.0 dev: true - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} - engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - dev: true - /enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} @@ -4399,21 +4397,28 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.0)(eslint@8.55.0): - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0): + resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true dependencies: + '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6(supports-color@8.1.1) - enhanced-resolve: 5.15.0 + enhanced-resolve: 5.17.1 eslint: 8.55.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-import: /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) + eslint-plugin-import: /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) fast-glob: 3.3.2 - get-tsconfig: 4.7.2 - is-core-module: 2.13.1 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 is-glob: 4.0.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -4422,8 +4427,8 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -4447,7 +4452,7 @@ packages: debug: 3.2.7 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.0)(eslint@8.55.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) transitivePeerDependencies: - supports-color dev: true @@ -4463,21 +4468,20 @@ packages: ignore: 5.2.4 dev: true - /eslint-plugin-i@2.29.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): - resolution: {integrity: sha512-slGeTS3GQzx9267wLJnNYNO8X9EHGsc75AKIAFvnvMYEcTJKotPKL1Ru5PIGVHIVet+2DsugePWp8Oxpx8G22w==} + /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): + resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==} engines: {node: '>=12'} peerDependencies: eslint: ^7.2.0 || ^8 dependencies: - debug: 3.2.7 - doctrine: 2.1.0 + debug: 4.3.6(supports-color@8.1.1) + doctrine: 3.0.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - get-tsconfig: 4.7.2 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0) + get-tsconfig: 4.8.1 is-glob: 4.0.3 minimatch: 3.1.2 - resolve: 1.22.8 semver: 7.6.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -4515,8 +4519,8 @@ packages: eslint: 8.55.0 dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.55.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + /eslint-plugin-react-hooks@4.6.2(eslint@8.55.0): + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 @@ -5089,8 +5093,8 @@ packages: get-intrinsic: 1.2.2 dev: true - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + /get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} dependencies: resolve-pkg-maps: 1.0.0 dev: true @@ -5697,6 +5701,12 @@ packages: builtin-modules: 3.3.0 dev: true + /is-bun-module@1.2.1: + resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} + dependencies: + semver: 7.6.3 + dev: true + /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index 0a059e59d231..f2328a90d993 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -157,7 +157,11 @@ export interface ISummaryAttachment { export interface ISummaryTree { type: SummaryType.Tree; - // TODO type I can infer from SummaryObject. File mode I may want to directly specify so have symlink+exec access + /** + * The object containing all the tree's {@link SummaryObject} children. + * + * @param path - The key to store the SummaryObject at in the current summary tree being generated. Should not contain any "/" characters and should not change when encodeURIComponent is called on it. + */ tree: { [path: string]: SummaryObject }; /** diff --git a/docs/archetypes/default.md b/docs/archetypes/default.md deleted file mode 100644 index 26f317f303e7..000000000000 --- a/docs/archetypes/default.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -draft: true ---- diff --git a/docs/content/community/roadmap.md b/docs/content/community/roadmap.md deleted file mode 100644 index a9ccf1c1d295..000000000000 --- a/docs/content/community/roadmap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Roadmap -menuPosition: 1 -draft: true ---- diff --git a/docs/content/docs/concepts/images/architecture.png b/docs/content/docs/concepts/images/architecture.png deleted file mode 100644 index 8c8248f5dddc..000000000000 Binary files a/docs/content/docs/concepts/images/architecture.png and /dev/null differ diff --git a/docs/content/docs/concepts/images/load-flow.png b/docs/content/docs/concepts/images/load-flow.png deleted file mode 100644 index adb5d0d7741b..000000000000 Binary files a/docs/content/docs/concepts/images/load-flow.png and /dev/null differ diff --git a/docs/content/docs/data-structures/cell.md b/docs/content/docs/data-structures/cell.md deleted file mode 100644 index 37874dc513f8..000000000000 --- a/docs/content/docs/data-structures/cell.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: SharedCell -status: unwritten -draft: true ---- - -Example of wrapping an object in a SharedCell and listening to changes on that object. Synced settings could be a good -scenario to demonstrate. diff --git a/docs/content/docs/data-structures/directory.md b/docs/content/docs/data-structures/directory.md deleted file mode 100644 index aeb166245840..000000000000 --- a/docs/content/docs/data-structures/directory.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: SharedDirectory -status: unwritten -draft: true ---- - -Directory usage guide. - -How do I store hierarchical data correctly in Directory? - -Examples of using Directory to listen to only some changes in the underlying map. diff --git a/docs/content/docs/data-structures/task-manager.md b/docs/content/docs/data-structures/task-manager.md deleted file mode 100644 index ba4716f1dec3..000000000000 --- a/docs/content/docs/data-structures/task-manager.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: TaskManager -draft: true -menuPosition: 9 ---- - -## Introduction - -FluidFramework is designed to facilitate real-time collaboration in modern web applications by distributing data throughout its clients with the help of its many distributed data structures (DDSes). However, TaskManager uniquely distributes tasks rather than a dataset. Furthermore, TaskManager is designed to distribute tasks that should be exclusively executed by a single client to avoid errors and mitigate redundancy. - -{{% callout note "What exactly is a \"task\"?" %}} -A task is simply code that should only be executed by **one** client at a time. This could be as small as a single line of code, or an entire system . However, we reccomend large processes to frequently synchronize their progress in the case of an unexpected disconnection so another client can resume the process with minimal data loss. -{{% /callout %}} - -### Task Queue - -TaskManager's main role is to maintain a queue of clients for each unique task. The client at the top of the queue is assigned the task, and is given permission to exclusively execute the task. All other clients will remain in queue until they leave, disconnect (unexpectedly), or the task is completed by the assigned client. It's important to note that TaskManager maintains the consensus state of the task queue. This means that locally submitted operations will not affect the queue until the operation is accepted by all other clients. To learn more about conensus based data structures, click [here]({{< relref "./overview.md#consensus-data-structures" >}}). - -### Consensus Based DDS - -An important note about TaskManager is that it is a consensus based DDS. This essentially means that operations are not accepted until every client acknowledge and accepts the operation. This differs from an "optimistic" DDS (i.e. [SharedMap]({{< relref "./map.md" >}})) which immediately accept ops and then relays them to other clients. For more information regarding different types of DDSes, click [here]({{< relref "./overview.md" >}}). - -## Usage - -### APIs - -The `TaskManager` object provides a number of methods to manage the execution of tasks. Please note: each API requires an input of `taskId` which is type `string`. - - -- `volunteerForTask(taskId)` -- Adds the client to the task queue **once**. It returns a promise that resolves `true` if the client is assigned the task and `false` if the task was completed by another client. It will throw an error if the client disconnects while in queue. -- `subscribeToTask(taskId)` -- Will continuously add the client to the task queue. Does not return a value, and will therefore require listening to [events](#events) to determine if the task is assigned, lost, or completed. -- `subscribed(taskId)` -- Returns a boolean to indicate if the client is subscribed to the task. -- `complete(taskId)` -- Will release all clients from the task queue, including the currently assigned client. -- `abandon(taskId)` -- Exits the queue and releasing the task if currently assigned. Will also unsubscribe from the task (if subscribed). -- `queued(taskId)` -- Returns a boolean to indicate if the client is in the task queue (being assigned a task is still considered queued). -- `assigned(taskId)` -- Returns a boolean to indicate if the client is assigned the task. - - -### `volunteerForTask()` vs `subscribeToTask()` - -Although both APIs are ultimately used to join the task queue, they have two key differences which impacts which should be used in any given scenario. The first key difference is that `volunteerForTask()` returns a `Promise`, while `subscribeToTask()` is synchronous and will rely on events. Second, `volunteerForTask()` will only enter the client into the task queue **once**, while `subscribeToTask()` will re-enter the client into the task queue if the client disconnects and later reconnects. - -Due to these differences, `volunteerForTask()` is better suited for one-time tasks such as data imports or migrations. For an example, see [the schema upgrade demo](#external-examples). On the other hand, `subscribeToTask()` is prefered for ongoing tasks that have no definitive end. For an example, see [the task selection demo](#external-examples). - -### Events - -`TaskManager` is an `EventEmitter`, and will emit events when a task is assigned to the client or released. Each of the following events fires with an event listener that contains a callback argument `taskId`. This represents the task for which the event was fired. - -- `assigned` -- Fires when the client reaches the top of the task queue and is assigned the task. -- `lost` -- Fires when the client disconnects after having been assigned the task. -- `completed` -- Fires on all connected clients when the assigned client calls `complete()`. - -### Creation - -To create a `TaskManager`, call the static create method below. Note: - -- `this.runtime` is a `IFluidDataStoreRuntime` object that represents the data store that the new task queue belongs to. -- `"my-task-manager"` is the name for the new task queue (this is an optional argument). - -```typescript -const taskManager = TaskManager.create(this.runtime, "my-task-manager"); -``` - -## Examples - -### Basic Example -- `volunteerForTask()` - -The following is a basic example for `volunteerForTask()`. Note that we check the `boolean` return value from the promise to ensure that the task was not completed by another client. - -```typescript -const myTaskId = "myTaskId"; - -taskManager.volunteerForTask(myTaskId) - .then((isAssigned: boolean) => { - if (isAssigned) { - console.log("Assigned task."); - - // We setup a listener in case we lose the task assignment while executing the code. - const onLost = (taskId: string) => { - if (taskId === myTaskId) { - // The task assignment has been lost, therefore we should halt execution. - stopExecutingTask(); - } - }; - taskManager.on("lost", onLost); - - // Now that we are assigned the task we can begin executing the code. - executeTask() - .then(() => { - // We should remember to turn off the listener once we are done with it. - taskManager.off("lost", onLost); - - // We should call complete() if we didn't already do that at the end of executeTask(). - taskManager.complete(myTaskId); - }); - } else { - console.log("Task completed by another client."); - } - }) - .catch((error) => { - console.error("Removed from queue:", error); - }); -``` - -### Basic Example -- `subscribeToTask()` - -The following is an example using `subscribeToTask()`. Since `subscribeToTask()` does not have a return value, we must rely on event listeners. We can setup the following listeners below. Please note how we compare the `taskId` with `myTaskId` to ensure we are responding to the appropriate task event. - - -```typescript -const myTaskId = "myTaskId"; - -const onAssigned = (taskId: string) => { - console.log(`Client was assigned task: ${taskId}`); - if (taskId === myTaskId) { - // Now that we are assigned the task we can begin executing the code. - // We assume that complete() is called at the end of executeTask(). - executeTask(); - } -} - -const onLost = (taskId: string) => { - console.log(`Client released task: ${taskId}`); - if (taskId === myTaskId) { - // This client is no longer assigned the task, therefore we should halt execution. - stopExecutingTask(); - } -} - -const onCompleted = (taskId: string) => { - console.log(`Task ${taskId} completed by another client`); - if (taskId === myTaskId) { - // Make sure we turn off the event listeners now that we are done with them. - taskManager.off("assigned", onAssigned); - taskManager.off("lost", onLost); - taskManager.off("completed", onCompleted); - } - -} - -taskManager.on("assigned", onAssigned); -taskManager.on("lost", onLost); -taskManager.on("completed", onCompleted); - -// Once the listeners are setup we can finally subscribe to the task. -taskManager.subscribeToTask(myTaskId); -``` - -### External Examples - -- [Schema Upgrade](https://github.com/microsoft/FluidFramework/tree/main/examples/hosts/app-integration/schema-upgrade) -- Experimental application to outline an approach for migrating data from an existing Fluid container into a new Fluid container which may have a different schema or code running on it. TaskManager is used to ensure only a single client performs the migration. -- [Task Selection](https://github.com/microsoft/FluidFramework/tree/main/examples/data-objects/task-selection) -- Simple application to demonstrate TaskManager with a rolling die. TaskManager is used to have only a single client "rolling" the die while other clients observe. - diff --git a/docs/content/docs/deep/_index.md b/docs/content/docs/deep/_index.md deleted file mode 100644 index 675a91ce5be7..000000000000 --- a/docs/content/docs/deep/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "The depths" -draft: false -area: deep -cascade: - area: deep - draft: false ---- diff --git a/docs/content/docs/deep/blobs.md b/docs/content/docs/deep/blobs.md deleted file mode 100644 index 5b0b8e62662e..000000000000 --- a/docs/content/docs/deep/blobs.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "Blobs in the Fluid Framework" -menuPosition: 7 -status: unwritten -draft: true ---- - -Section on attachment blobs vs. snapshot blobs from issue #6374. diff --git a/docs/content/docs/deep/breaking-changes.md b/docs/content/docs/deep/breaking-changes.md deleted file mode 100644 index b0125e7cbd4c..000000000000 --- a/docs/content/docs/deep/breaking-changes.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Breaking changes -draft: true -aliases: - - "/docs/advanced/breaking-changes/" ---- - -See . diff --git a/docs/content/docs/deep/compatibility.md b/docs/content/docs/deep/compatibility.md deleted file mode 100644 index 559d49895bb7..000000000000 --- a/docs/content/docs/deep/compatibility.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Version compatibility -draft: true -status: outdated -aliases: - - "/docs/concepts/compatibility/" ---- - -Because the Fluid Framework is a platform, maintaining predictable backwards/forwards compatibility is an important part -of development and documentation. Any breaking changes should be placed in the [BREAKING.md](./breaking-changes.md) -file in the root of the repository. Understanding the different parts of the Fluid Framework can help with making sure -contributions are acceptably compatible and the code is reasonably clean. - -## Breakdown - -The following overview shows the various levels and their corresponding contracts: - -- Common: @fluidframework/common-definitions - - Common utils/definitions that might be shared at all levels of the stack -- Protocol: @fluidframework/protocol-definitions - - Definition of protocol between the server and the client (ops and summary structure, etc.) -- Driver: @fluidframework/driver-definitions - - API of driver for access to storage and web socket connections -- Loader: @fluidframework/container-definitions - - The core framework responsible for loading runtime code into a container -- Runtime: @fluidframework/runtime-definitions - - A base set of runtime code that supports the Fluid model, summarization, and other core Fluid features -- Framework: @fluidframework/framework-definitions - - A set of base implementations and helper utilities to support developers building on Fluid - -This document will focus on a few specific layer boundaries. - -### Protocol - -Changes to the protocol definitions should be vetted highly, and ideally should always be backwards compatible. These -changes require synchronization between servers and clients, and are meant to be minimal and well-designed. - -### Driver and Loader - -The driver and loader versions will come from the hosting applications. Driver implementations depend on the -corresponding server version. Changes to driver definitions must be applied to all driver implementations, and so they -should be infrequent. The loader implementations are meant to be very slim, only providing enough functionality to load -the runtime code and connect to the server. - -The driver contract is consumed by both the loader and the runtime layers. Since the driver and loader come from the -same source, it is not necessary to maintain compatibility between the driver-to-loader boundary for now. As number of -drivers increase and become external, this may change in the future. - -The loader contract (also called container definitions) is consumed by the runtime layer. Consumers of the Fluid -Framework may have different frequencies for releasing their host (with driver and loader) as their runtime code, so -this compatibility across this boundary is important. Currently Fluid maintains that the driver/loader will be -backwards *and* forwards compatible with the runtime by at least 1 version. For a given driver or loader version `2.x`, -it should be compatible with runtime versions `1.x`, `2.x`, and `3.x`. This is illustrated by the table below: - -| Driver/Loader | | 1.x | 2.x | 3.x | -|--------------:|-|:---:|:---:|:---:| -| Runtime | | | | | -| 1.x | | C | BC | X | -| 2.x | | FC | C | BC | -| 3.x | | X | FC | C | - -- C - Fully compatible -- BC - Driver/loader backwards compatible with runtime -- FC - Driver/loader forwards compatible with runtime (runtime backwards compatible with driver and loader) -- X - May not be compatible - -### Runtime - -Within the Fluid Framework, the runtime consists of a few parts: - -1. The container-level runtime code: this corresponds to a single data source/document, and *contains* the data stores. - The container-level runtime code is dictated by the "code" value in the quorum. Typically developers building on - Fluid will create an instance of the Fluid `ContainerRuntime` by passing it a registry- which instructs how to - instantiate data stores; this may be dynamic, or all data store code could bundled with the container runtime. -2. The data-store-level runtime code: this corresponds to each loaded data store within a container. The data-store-level - runtime code is dictated by the package information in its attach op. The data-store-level runtime code - and container-level runtime code depend on each other through the APIs defined in runtime-definitions. For reference, - this boundary occurs between the `IFluidDataStoreContext` - (container-level) and the `IFluidDataStoreRuntime` (data-store-level). Fluid tries to keep the container runtime backwards - compatible with the data store runtime by at least 1 version. -3. The distributed data structures code: typically developers can build data stores consisting of the Fluid Framework - provided set of distributed data structures. There is a registry of DDS factories within each data store that - instruct how to load the DDS code, but this code is meant to be statically loaded with the data store. Developers can - build their own distributed data structures, but it may be more complicated, being that they are lower-level to the - ops and summaries. - -When making changes to the Fluid Framework repository, it is important to note when breaking changes are made to -runtime-definitions which affect compatibilities between different version of data stores. We should ensure that -our own container-level runtime code can load our own data-store-level runtime code at least 1 version back. - -Specific interfaces to monitor: - -- `IContainerRuntime` - interfaces container runtime to loaded data store runtime -- `IFluidDataStoreContext` - interfaces data store context to loaded data store runtime -- `IFluidDataStoreRuntime` - interfaces loaded data store runtime to its context - -## Guidelines for compatible contributions - -There are many approaches to writing backwards/forwards compatible code. For large changes or changes that are -difficult to feature detect, it might be valuable to leverage versioned interfaces. For smaller changes, it might be as -simple as adding comments indicating what is deprecated and making the code backwards compatible. - -It is required to make the changes backwards/forwards compatible at least 1 version where indicated above. This means -splitting the logic in some way to handle the old API as well as comfortably handling the new API. 2+ versions later, -the code can be revisited and the specialized back-compat code can be removed. - -### Isolate back-compat code - -Typically, it is best to isolate the back-compat code as much as possible, rather than inline it. This will help make -it clear to readers of the code that they should not rely on that code, and it will simplify removing it in the future. - -One strategy is to write the code as it should be without being backwards compatible first, and then add extra code to -handle the old API. - -### Comment appropriately - -Add comments to indicate important changes in APIs; for example add a comment to indicate if an API is deprecated. Use -the tsdocs `@deprecated` comment keyword where appropriate. - -In addition to isolating back-compat code, adding comments can also help identify all places to change when revisiting -in the future for cleanup. Using a consistent comment format can make it easier to identify these places. - -```typescript -// back-compat: 0.11 clientType -``` - -The above format contains the breaking version and a brief tag, making it easy to find all references in the code later. -Liberally adding these near back-compat code can help with the later cleanup step significantly, as well as concisely -give readers of the code insight into why the forked code is there. - -### Track the follow-up work - -It is necessary to track the follow-up work to remove this back-compat code to keep the code pruned. The code -complexity will creep up as more back-compat code comes in. The strategy is to create a GitHub issue and include -information that provides context for the change and makes it easy for someone to cleanup in the future. - -### Update the docs - -During the initial change, it is important to make sure the API changes are indicated somewhere in the docs. After -making the follow-up change to remove the backwards compatible code, it should be documented in the -[BREAKING.md](./breaking-changes.md) file so that it is clear that it will break. diff --git a/docs/content/docs/deep/container-and-component-loading.md b/docs/content/docs/deep/container-and-component-loading.md deleted file mode 100644 index 3e3040814578..000000000000 --- a/docs/content/docs/deep/container-and-component-loading.md +++ /dev/null @@ -1,249 +0,0 @@ ---- -title: Container and component loading deep dive -draft: true -status: outdated ---- - -This doc provides an in-depth outline of how Container and Component loading works. It also provides an overview of how -Fluid packages are partitioned. While the system is not overly complex, looking at it as a whole can be overwhelming, -and difficult to rationalize. As we go through the doc we will build a clear picture of the entirety of the system. - -If you want to look at the entire system in one picture see [Appendix 1](#appendix-1) at the bottom of the doc. - -The complete loading flow in Fluid can follow multiple paths, and this can create complexities when attempting to explain -the flow in a single document. For simplicity, this document follows the *Create from Existing* flow with minor notes about -how the *Create New* flow differs. - -It should also be noted that this doc contains intentional simplifications. So, while this document attempts to provide -a detailed representation of the loading flow there may be areas where it does not 100% reflect the truth. Hopefully, -these simplifications are negligible and help provide clarity. But if you find any of the simplifications particularly -misleading please point them out. - -If you see a bolded number - Ex. **(2)** - it represents a line in the diagram. This number will be in the next diagram -as well as the finished diagram in [Appendix 1](#appendix-1). - -Finally, as you read through this doc you will find yourself having lots of questions. This is good, and intentional! -Keep reading as it's likely explained later. - -## Loading flow - -The Hosting Application is a webpage that loads a Fluid container. This has also been referred to as a "Fluid -Enlightened Canvas" and currently consists of: the Fluid preview app, Teams, Outlook, and a handful more. To load any -Fluid container, the Hosting Application needs the Fluid Loader Package. This is a small package whose only -responsibility is to load Fluid containers. The Fluid Loader has no knowledge of the `ContainerRuntime` or `Component` -specific code. - -The `Loader` object has a method `resolve(...)` **(1)** that can load a `Container` when provided the following: - -- `url` to Operation Stream (op stream) -- `Driver` **(1.1)** - used for talking to the Fluid Server -- `CodeLoader` **(1.2)** - used for resolving the `ContainerRuntime` code - -![Image 1](/images/container-and-component-loading-1.jpg) - -In the case of resolving a `Container` that has not been loaded locally, the `Loader` will create a new `Container` -object **(2)**. - -![Image 2](/images/container-and-component-loading-2.jpg) - -The `Container` will use the provided `url` and `Driver` to connect, and start processing, the op stream **(3)**. - -::: tip - -The Operation Stream (op stream) is how Fluid stores state. State, including connected clients, the code to load, as -well as distributed data structure modifications, are stored as a series of operations that when played in order produce -the current state. I don't go into further details about it here. - -::: - -Connecting and processing the op stream includes: - -- Getting the Summary -- Establishing the Websocket connection -- Retrieving any missing ops from the REST endpoint - -The `Driver` is responsible for taking the requests above **(3)** and transforming them to requests that the Fluid Server -understands **(3.1)**. - -The Fluid Core (`Loader` + `Runtime`) is agnostic to how the Fluid Server is implemented. It instead uses -a `Driver` model to allow for different servers to optimize for their own infrastructure. - -![Image 3](/images/container-and-component-loading-3.jpg) - -The `Container` object itself does not actually do much. Once it has established a connection via the `Driver` its other -responsibility is to listen specifically for one event emitted from the `Quorum`. This is the `"code"` proposal. - -::: tip - -The `Quorum` is a special key/value distributed data structure that requires all current members to agree on the value -before the it is accepted. I don't go into further details about it here. - -::: - -There are a few different ways that the `Container` will get this `"code"` value: - -1. In the *Create New* flow this `"code"` value needs to be proposed by the Hosting Application. Once the value is - accepted by everyone connected (only you, the current client, in this case) the `Container` will get the event and - have the value. -2. In the *Create from Existing* flow there are two scenarios. - 1. In the *load from Summary flow* the `"code"` value is written into the Summary itself. - 2. In the *load from op stream* flow (no Summary) the `"code"` value will be played as an op. - -In any case, once the `Container` has the `"code"` value it asks the `CodeLoader` to resolve the proposed code **(4)**. Since -the Loader Package does not know anything about the `ContainerRuntime`, or Components, it needs someone to tell it -where that code lives. This is the responsibility of the `CodeLoader`. The `CodeLoader` can dynamically pull this code -from some source (CDN) or in some cases the code already exists on the page. Either way the `CodeLoader` needs to -include the code on the page and return a pointer to that code to the `Container`. In the Browser, this pointer -is an entry point to a webpacked bundle that is usually on the `window` object. In Node.js, it's a pointer to a package. - -![Image 4](/images/container-and-component-loading-4.jpg) - -At this point the `Container` has a pointer to the `ContainerRuntime` code and it uses that code to create a new -`ContainerContext` **(5)** and executes the `instantiateRuntime` **(6.1)** on the webpack bundle with the `ContainerContext`. - -The important thing to note here is that up until this point the Hosting Application and Fluid know nothing of the Fluid -`ContainerRuntime` or the `Component` code. That code is provided after the `Container` is established and stored in the -op stream. **This is powerful because it allows the Hosting Applications to load Containers and Components without -knowing the underlying code.** This is how Teams and Outlook can easily load the Fluid preview app `Container` and -`Components`. - -![Image 5](/images/container-and-component-loading-5.jpg) - -The implementer of `instantiateRuntime` is what we refer to as a "Container Developer". As you can see, the term is slightly -overloaded since they are not actually writing the `Container` object, but simply implementing a function. This function -lives on the `IContainerFactory` interface and the `Container` specifically looks for an exported -`fluidExport` variable within the webpack bundle to find the Container Factory. - -The `instantiateRuntime` function can perform any number of functions but has become primarily responsible for **(6.2)**: - -1. Creating the `ContainerRuntime` object -2. Setting the `request` handler on the `ContainerRuntime` - - The `request` handlers are used to route requests through the `Container` (more on this later) - - The primary use is to get Components -3. Providing a `ComponentRegistry` of Component Factories to the `ContainerRuntime` - - The `ComponentRegistry` is a `Map Promise(IComponentFactory)>` - - Defines what Components can be created in the `Container` -4. Creating the default `Component` - -![Image 6](/images/container-and-component-loading-6.jpg) - -Containers can exist without Components but they are not very functional. The paradigm we've created is for the -Container Developer (`instantiateRuntime` implementer) to create a default `Component`. The default `Component` is simply -the first `Component` in the `Container`. Having a default `Component` allows the Hosting Application to make a `request` -against the `Container` asking for the default `Component` without knowing what the default `Component` is (more on -this later). - -The default `Component` is created the same as every other `Component`. The only difference is that it is the first -`Component` and created in the `instantiateRuntime` call as opposed to being created by another `Component` (also more on -this later). - -A `Component` is created by calling `createComponent("packageName")` on the `ContainerRuntime` **(6.2)**. The -`ContainerRuntime` uses its `ComponentRegistry` to look for the entry of `"packageName"`. When it's found it creates a -`ComponentContext` **(7)** and executes the corresponding `instantiateComponent` with the `ComponentContext` **(8.1)**. - -![Image 7](/images/container-and-component-loading-7.jpg) - -You might notice a lot of similarities between the `ContainerRuntime` creation flow and the `ComponentRuntime` -creation flow. - -In the `instantiateComponent` call **(8.1)** the following is performed: - -1. `ComponentRuntime` object is created **(8.2)** -2. Sets the `request` handler on the `ComponentRuntime` **(8.2)** - - Requests that are sent to the `ComponentRuntime` are proxied to the `Component` object (more on this later) -3. Provides a registry of Distributed Data Structures (DDS) / Sub-Component factories to the `ComponentRuntime` **(8.2)** - - This can be used to create new DDSs - - This can be used to create new Components that are not defined in the `ContainerRegistry` -4. Create the `Component` object **(8.3)** - -![Image 8](/images/container-and-component-loading-8.jpg) - -The `Component`, and the `instantiateComponent`, are what a "Component Developer" writes and contain all the business -specific logic. In most cases the `instantiateComponent` call will provide the `Component` with references to the -`ComponentContext` **(8.3.1)**, and the `ComponentRuntime` **(8.3.2)** it created. - -The `Component` should use the `ComponentContext` to talk upwards to the `ContainerRuntime` **(8.3.1)**, and should use -the `ComponentRuntime` to manage Fluid state of itself; mainly creating DDSs **(8.3.2)**. - -![Image 9](/images/container-and-component-loading-9.jpg) - -The `Component` will use the DDS objects directly **(9.1)** and will persist/`attach` them using the -`ComponentRuntime` **(9.2)**. When storing a DDS `handle` on another already attached DDS, the `ComponentRuntime` -will automatically `attach` the new DDS. - -::: tip - -`attach` sends an op on the op stream that persists the DDS and notifies all users it is live for editing. More on this in -[Anatomy of a Distributed Data Structure](./dds-anatomy) - -::: - -![Image 10](/images/container-and-component-loading-10.jpg) - -At this point you might have noticed that the `ComponentRuntime` does not actually know anything about the `Component` -object itself. In the `ContainerRuntime` all `ComponentRuntimes` are treated equally without hierarchy. But then how do Components -interact with each other? - -Components can create and hold references to other Components in the `ContainerRuntime`. The same way -`instantiateRuntime` created the default `Component`, the default `Component` can use `createComponent` on its -`ComponentContext` **(8.3.1)** to create a second `Component`. - -Calling `createComponent` causes the `ContainerRuntime` to look at the `ComponentRegistry`, create a second -`ComponentContext` **(7)**, which will call a new `instantiateComponent` **(8.1)**, which will create a second -`ComponentRuntime` **(8.2)**, and second `Component` object **(8.3)**. - -![Appendix 1](/images/container-and-component-loading-11.jpg) - -Great! Now we've loaded our entire `Container` plus our two Components. But we don't actually have anything rendered on -the page. All these objects just exist in memory. - -## Requesting and Routing - -### Loading the Default Component - -In the most basic case of rendering the default `Component` the Hosting Application will make a `request` against the -`Container` object. This `request` will look something like `container.request({ url: "/" });` where the `"/"` denotes the -default component. - -We've talked briefly about setting `request` handlers, and that it is done in the `instantiateRuntime` and -`instantiateComponent` section. Now we have a request on the `Container` object. But the `Container` doesn't know how to -handle this `request`, so it forwards the `request` to the `ContainerContext` **(5)**. The `ContainerContext` doesn't -know how to handle it either so it forwards the `request` to the `ContainerRuntime` **(6)**. - -In our `instantiateRuntime` we set a specific `request` handler on the `ContainerRuntime` that says if someone asks for -`"/"` we will return the default `Component` we've created. So the `ContainerRuntime` finds the `ComponentContext` -relating the default `Component` and forwards the `request` there **(7)**. The `ComponentContext` doesn't know how to -handle the request so it forwards the request to the `ComponentRuntime` **(8)**. - -Now in our `instantiateComponent` for the default `Component` we set a specific `request` handler that said if anyone asks -for this `ComponentRuntime` to forward the request to the `Component` object itself. So the `ComponentRuntime` forwards the -request to the `Component` **(8.3.2)**. Finally, in the `Component` we've set a `request` handler that if anyone should send -it a `request` it will return itself. - -So by requesting `"/"`, the Hosting Application has retrieved the default `Component` object. - -The Hosting Application can now use Fluid's [feature detection -mechanism](./components.md#feature-detection-and-delegation) to check if the `Component` it got supports a view by checking -`component.IComponentHTMLView` and calling `render(...)` if `IComponentHTMLView` returns an object. - -That was a lot to unpack in a lot of text, and don't worry if it feels overwhelming. The overall principal of the -request pattern is that requests are delegated through the system to the place where they are meant to go. - -### Loading a Component from a Component - -This flow works the same as the default `Component` above except that the loading `Component` has to be explicit about -the id of the loaded `Component`. - -In the scenario below we have Component1 attempting to get Component2. - -Instead of calling the `Container`, Component1 will call its `ComponentContext` -`context.request({ url: "/component-2-unique-id" });` **(8.3.1)**. You can see we are not just requesting `"/"` but the -direct id of Component2 `"/component-2-unique-id`. The `ContainerRuntime` handler that we set will use the id to look -up the `ComponentContext` of Component2 and forward the request there **(7)**. The -`ComponentContext` will forward to the `ComponentRuntime` **(8)** of Component2, which will forward to the `Component` -object of Component2 **(8.3.2)**. The `Component` object will return itself and now Component1 has a reference to Component2. - - -## Appendix 1 - -![Appendix 1](/images/container-and-component-loading-11.jpg) diff --git a/docs/content/docs/deep/containers-runtime.md b/docs/content/docs/deep/containers-runtime.md deleted file mode 100644 index 0c7a6394f6ed..000000000000 --- a/docs/content/docs/deep/containers-runtime.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "Containers and the container runtime" -menuPosition: 5 -aliases: - - "/docs/concepts/containers-runtime" -draft: true ---- - -A **Fluid container** is a foundational concept for creating anything with the Fluid Framework. All of the sample Fluid -applications use a Fluid container to manage the user experience, app logic, and app state. - -However, a Fluid container is *not* a standalone application. A Fluid container is a *code-plus-data package*. A -container must be loaded by the Fluid loader and connected to a Fluid service. - -Because containers are such a core concept, we'll look at them from a few different angles. - -## Container vs Runtime - -A Fluid container is the instantiated container JavaScript object, but it's also the definition of the container. We -interchangeably use "container" to refer to the class, which can create new objects, and the instantiated object itself. - -The `ContainerRuntime` refers to the inner mechanics of the Fluid container. As a developer you will interact with the -runtime through the runtime methods that expose useful properties of the instantiated container object. - -## What is a Fluid container? - -A Fluid container is a code-plus-data package. A container includes at least one shared object for app logic, but -often multiple shared objects are composed together to create the overall experience. - -From the Fluid service perspective, the container is the atomic unit of Fluid. The service does not know about anything -inside of a Fluid container. - -That being said, app logic is handled by Data Objects and state is handled by the distributed data structures within -the Data Objects. - -## What does the Fluid container do? - -The Fluid container interacts with the [processes and distributes operations](./hosts), manages the [lifecycle of Fluid -objects](./dataobject-aqueduct), and provides a request API for accessing shared objects. - -### Process and distribute operations - -When the Fluid loader resolves the Fluid container, it passes the container a group of service drivers. These drivers -are the **DeltaConnection**, **DeltaStorageService**, and **DocumentStorageService**. - -The Fluid container includes code to process the operations from the DeltaConnection, catch up on missed operations -using the DeltaStorageService, and create or fetch summaries from the DocumentStorageService. Each of these are -important, but the most critical is the op processing. - -The Fluid container is responsible for passing operations to the relevant distributed data structures and Data Objects. - -### Manage shared object lifecycle - -The container provides a `createDataStore` method to create new data stores. The container is responsible for -instantiating the shared objects and creating the operations that let other connected clients know about the new Fluid -object. - -### Using a Fluid container: the Request API - -The Fluid container is interacted with through the request paradigm. While aqueduct creates a default request handler -that returns the default Data Objects, the request paradigm is a powerful pattern that lets developers create custom -logic. - -To retrieve the default data store, you can perform a request on the container. Similar to the [loaders API](./hosts.md) -this will return a status code and the default data store. - -```ts -container.request({url: "/"}) -``` diff --git a/docs/content/docs/deep/custom-dds.md b/docs/content/docs/deep/custom-dds.md deleted file mode 100644 index 25b341a2a977..000000000000 --- a/docs/content/docs/deep/custom-dds.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Build a custom distributed data structure -menuPosition: 9 -status: unwritten -draft: true ---- diff --git a/docs/content/docs/deep/dataobject-aqueduct.md b/docs/content/docs/deep/dataobject-aqueduct.md deleted file mode 100644 index e5a59cd30fbc..000000000000 --- a/docs/content/docs/deep/dataobject-aqueduct.md +++ /dev/null @@ -1,258 +0,0 @@ ---- -title: Encapsulating data with DataObject -menuPosition: 7 -aliases: - - "/docs/concepts/dataobject-aqueduct/" -draft: true ---- - - - -In the previous section we introduced distributed data structures and demonstrated how to use them. We'll now discuss -how to combine those distributed data structures with custom code (business logic) to create modular, reusable pieces. - - - - - - - - -![Aqueduct](https://publicdomainvectors.org/photos/johnny-automatic-Roman-aqueducts.png) - -The Aqueduct is a library for building Fluid objects and Fluid containers within the Fluid Framework. Its goal is to -provide a thin base layer over the existing Fluid Framework interfaces that allows developers to get started quickly. - -## Fluid object development - -Fluid object development consists of developing the data object and the corresponding data object factory. The data -object defines the logic of your Fluid object, whereas the data object factory defines how to initialize your object. - -## Data object development - -`DataObject` and `PureDataObject` are the two base classes provided by the library. - -### DataObject - -The [DataObject][] class extends [PureDataObject](#puredataobject) and provides the following additional functionality: - -- A `root` SharedDirectory that makes creating and storing distributed data structures and objects easy. -- Blob storage implementation that makes it easier to store and retrieve blobs. - -**Note:** Most developers will want to use the `DataObject` as their base class to extend. - -### PureDataObject - -[PureDataObject][] provides the following functionality: - -- Basic set of interface implementations to be loadable in a Fluid container. -- Functions for managing the Fluid object lifecycle. - - `initializingFirstTime(props: S)` - called only the first time a Fluid object is initialized and only on the first - client on which it loads. - - `initializingFromExisting()` - called every time except the first time a Fluid object is initialized; that is, every - time an instance is loaded from a previously created instance. - - `hasInitialized()` - called every time after `initializingFirstTime` or `initializingFromExisting` executes -- Helper functions for creating and getting other data objects in the same container. - -**Note:** You probably don't want to inherit from this data object directly unless you are creating another base data -object class. If you have a data object that doesn't use distributed data structures you should use Container Services -to manage your object. - -### DataObject example - -In the below example we have a simple data object, _Clicker_, that will render a value alongside a button the the page. -Every time the button is pressed the value will increment. Because this data object renders to the DOM it also extends -`IFluidHTMLView`. - -```jsx -export class Clicker extends DataObject implements IFluidHTMLView { - public static get Name() { return "clicker"; } - - public get IFluidHTMLView() { return this; } - - private _counter: SharedCounter | undefined; - - protected async initializingFirstTime() { - const counter = SharedCounter.create(this.runtime); - this.root.set("clicks", counter.handle); - } - - protected async hasInitialized() { - const counterHandle = this.root.get>("clicks"); - this._counter = await counterHandle.get(); - } - - public render(div: HTMLElement) { - ReactDOM.render( - , - div, - ); - return div; - } - - private get counter() { - if (this._counter === undefined) { - throw new Error("SharedCounter not initialized"); - } - return this._counter; - } -} -``` - -## DataObjectFactory development - -The `DataObjectFactory` is used to create a Fluid object and to initialize a data object within the context of a -Container. The factory can live alongside a data object or within a different package. The `DataObjectFactory` defines -the distributed data structures used within the data object as well as any Fluid objects it depends on. - -The Aqueduct offers a factory for each of the data objects provided. - -### More details - -- [DataObjectFactory][] -- [PureDataObjectFactory][] - -### DataObjectFactory example - -In the below example we build a `DataObjectFactory` for the [Clicker](#dataobject-example) example above. To build a -`DataObjectFactory`, we need to provide factories for the distributed data structures we are using inside of our -`DataObject`. In the above example we store a handle to a `SharedCounter` in `this.root` to track our `"clicks"`. The -`DataObject` comes with the `SharedDirectory` (`this.root`) already initialized, so we just need to add the factory for -`SharedCounter`. - -```typescript -export const ClickerInstantiationFactory = new DataObjectFactory( - Clicker.Name, - Clicker, - [SharedCounter.getFactory()], // distributed data structures - {}, // Provider Symbols see below -); -``` - -This factory can then create Clickers when provided a creating instance context. - -```typescript -const myClicker = ClickerInstantiationFactory.createInstance(this.context) as Clicker; -``` - -### Providers in data objects - -The `this.providers` object on `PureDataObject` is initialized in the constructor and is generated based on Providers -provided by the Container. To access a specific provider you need to: - -1. Define the type in the generic on `PureDataObject`/`DataObject` -2. Add the symbol to your factory (see [DataObjectFactory Example](#dataobjectfactory-example) below) - -In the below example we have an `IFluidUserInfo` interface that looks like this: - -```typescript -interface IFluidUserInfo { - readonly userCount: number; -} -``` - -On our example we want to declare that we want the `IFluidUserInfo` Provider and get the `userCount` if the Container -provides the `IFluidUserInfo` provider. - -```typescript -export class MyExample extends DataObject { - protected async initializingFirstTime() { - const userInfo = await this.providers.IFluidUserInfo; - if(userInfo) { - console.log(userInfo.userCount); - } - } -} - -// Note: we have to define the symbol to the IFluidUserInfo that we declared above. This is compile time checked. -export const ClickerInstantiationFactory = new DataObjectFactory( - Clicker.Name - Clicker, - [], // distributed data structures - {IFluidUserInfo}, // Provider Symbols see below -); -``` - -## Container development - -A Container is a collection of data objects and functionality that produce an experience. Containers hold the instances -of data objects as well as defining the data objects that can be created within the Container. Because of this data -objects cannot be consumed except for when they are within a Container. - -The Aqueduct library provides the [ContainerRuntimeFactoryWithDefaultDataStore][] that enables you as a container -developer to: - -- Define the registry of data objects that can be created -- Declare the default data object -- Use provider entries -- Declare Container level [Request Handlers](#container-level-request-handlers) - -## Container object example - -In the below example we will write a Container that exposes the above [Clicker](#dataobject-example) using the -[Clicker Factory](#dataobjectfactory-example). You will notice below that the Container developer defines the -registry name (data object type) of the Fluid object. We also pass in the type of data object we want to be the default. -The default data object is created the first time the Container is created. - -```typescript -export fluidExport = new ContainerRuntimeFactoryWithDefaultDataStore( - ClickerInstantiationFactory.type, // Default data object type - ClickerInstantiationFactory.registryEntry, // Fluid object registry - [], // Provider Entries - [], // Request Handler Routes -); -``` - -## Container-level request handlers - -You can provide custom request handlers to the container. These request handlers are injected after system handlers but -before the `DataObject` get function. Request handlers allow you to intercept requests made to the container and return -custom responses. - -Consider a scenario where you want to create a random color generator. I could create a RequestHandler that when someone -makes a request to the Container for `{url:"color"}` will intercept and return a custom `IResponse` of `{ status:200, type:"text/plain", value:"blue"}`. - -We use custom handlers to build the Container Services pattern. - - - - - - - - - - - - - - -[Fluid container]: {{< relref "containers.md" >}} -[Signals]: {{< relref "/docs/concepts/signals.md" >}} - - - -[SharedCounter]: {{< relref "/docs/data-structures/counter.md" >}} -[SharedMap]: {{< relref "/docs/data-structures/map.md" >}} -[SharedString]: {{< relref "/docs/data-structures/string.md" >}} -[Sequences]: {{< relref "/docs/data-structures/sequences.md" >}} -[SharedTree]: {{< relref "/docs/data-structures/tree.md" >}} - - - -[fluid-framework]: {{< packageref "fluid-framework" "v2" >}} -[@fluidframework/azure-client]: {{< packageref "azure-client" "v2" >}} -[@fluidframework/tinylicious-client]: {{< packageref "tinylicious-client" "v1" >}} -[@fluidframework/odsp-client]: {{< packageref "odsp-client" "v2" >}} - -[AzureClient]: {{< apiref "azure-client" "AzureClient" "class" "v2" >}} -[TinyliciousClient]: {{< apiref "tinylicious-client" "TinyliciousClient" "class" "v1" >}} - -[FluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} -[IFluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} - - - - diff --git a/docs/content/docs/deep/dds-anatomy.md b/docs/content/docs/deep/dds-anatomy.md deleted file mode 100644 index 14f036b74ab1..000000000000 --- a/docs/content/docs/deep/dds-anatomy.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: Anatomy of a distributed data structure -menuPosition: 8 -draft: true ---- - -Although each distributed data structure (DDS) has its own unique functionality, they all share some broad traits. -Understanding these traits is the first step to understanding how DDSes work. They are: - -1. Local representation -1. Op vocabulary -1. Data serialization format (op) -1. Data serialization format (summary operations) -1. Reaction to remote changes -1. Conflict resolution strategies - -## Local representation - -Just like any non-distributed data structure such as JavaScript's Map object, all DDSes must also be accessible on the -client with an in-memory representation via a public API surface. A developer using the DDS operates on and reads from -this in-memory structure similarly to any other non-distributed data structure. The particular format of the data and -functionality of the API will vary between data structures. For example, a SharedMap holds key:value data and provides -interfaces like get and set for reading and updating values in the map. This is very similar to the native -(non-distributed) Map object in JS. - -## Op vocabulary - -As the in-memory representation is modified on one client, we need to notify other clients of the updates. Most DDSes -will have multiple operations that can be performed, so we'll need to differentiate the types of notifications (ops) -we're sending. For example, a SharedMap might be modified through "set", "delete", or "clear". - -These ops will probably correspond loosely with specific APIs on the DDS that cause data modification with the -expectation that there is a 1:1:1 correspondence between that API call on client A, the op that is sent, and the -corresponding update being applied on client B. However, this correspondence is not mandatory. - -## Data serialization format (op) - -Frequently, ops will need to carry a data payload. For example, when performing a "set" on a SharedMap, the new -key:value pair needs to be communicated to other clients. As a result, DDSes will have some serialization format for op -data payloads that can be reconstituted on the receiving end. This is why SharedMap requires its keys to be strings and -values to be serializable - non-serializable keys or values can't be transmitted to other clients. - -## Data serialization format (summary operations) - -Although the state of a DDS can be reconstructed by playing back every op that has ever been applied to it, this becomes -inefficient as the number of ops grows. Instead, DDSes should be able to serialize their entire contents into -a format that clients can use to reconstruct the DDS without processing the entire op history. There may be some overlap -with the serialization format used in ops, but it isn't strictly necessary. For instance, the SharedMap uses the same -serialization format for key/value pairs in its summary as it does in its set ops, but the Ink DDS serializes individual -coordinate updates in its ops while serializing entire ink strokes in its summary. - -## Reaction to remote changes - -As compared to their non-distributed counterparts, DDSes can change state without the developer's awareness as remote -ops are received. A standard JS Map will never change values without the local client calling a method on it, but a -SharedMap will, as remote clients modify data. To make the local client aware of the update, DDSes must expose a means -for the local client to observe and respond to these changes. This is typically done through eventing, like the -"valueChanged" event on SharedMap. - -## Conflict resolution strategies - -Data structures must be aware that multiple clients can act on the structure remotely, and the propagation of those -changes take time. It's possible then for a client to make a change to a data structure while unaware of its most-recent -state. The data structure must incorporate strategies for handling these scenarios such that any two clients which have -received the same set of ops will agree on the state. This property is referred to as "eventual consistency" or -"[convergence](https://en.wikipedia.org/wiki/Operational_transformation#The_CC_model)". These strategies may be varied -depending on the specific operation even within a single DDS. Some (non-exhaustive) examples of valid strategies: - -### Conflict avoidance - -Some data structures may not need to worry about conflict because their nature makes it impossible. For instance, the -Counter DDS increment operations can be applied in any order, since end result of the addition will be the same. -Characteristics of data structures that can take this approach: - -1. The data structure somehow ensures no data can be acted upon simultaneously by multiple users (purely additive, - designated owner, etc.) -1. The order in which actions are taken is either guaranteed (single actor, locking, etc.) or is irrelevant to the - scenario (incrementing a counter, etc.) - -### Last wins - -If it's possible to cause conflicts in the data, then a last-wins strategy may be appropriate. This strategy is used by -SharedMap, for example, in the case that multiple clients attempt to set the same key. In this case, clients need to be -aware that their locally applied operations may actually be chronologically before or after unprocessed remote -operations. As remote updates come in, each client needs to update the value to reflect the last (chronologically) set -operation. - -### Operational Transform and Intention Preservation - -More-advanced DDSes require a more-sophisticated conflict resolution strategy to meet user expectations. The general -principle is referred to as [Intention -Preservation](https://en.wikipedia.org/wiki/Operational_transformation#The_CCI_model). For example, the text I insert at -position 23 of a SharedString while a friend deletes at position 12 needs to be transformed to insert at the location -that matches my intention (that is, remains in the same location relative to the surrounding text, not the numerical -index). - -### Consensus and quorum - -Some resolution strategies may not be satisfied with eventual consistency, and instead require stronger guarantees -about the global state of the data. The consensus data structures achieve this by accepting a delay of a roundtrip -to the server before applying any changes locally (thus allowing them to confirm their operation was applied on a -known data state). The quorum offers an even stronger guarantee (with a correspondingly greater delay), that the -changes will not be applied until all connected clients have accepted the modification. These delays generally aren't -acceptable for real-time interactivity, but can be useful for scenarios with more lenient performance demands. - -## Additional thoughts - -1. Strictly speaking, summarization isn't a mandatory requirement of a DDS. If the ops are retained, the DDS can - be reconstructed from those. However, in practice it is not practical to load from ops alone, as this will - degrade load time over the lifetime of the DDS. -1. The requirement of "eventual consistency" has some flexibility to it. Discrepancies between clients are allowed as - long as they don't result in disagreements between clients on the observable state of the data. For example: - - SharedString can be represented differently across clients in internal in-memory representation depending on op - order, but this discrepancy is invisible to the user of the SharedString DDS. - - SharedMap will raise a different number of valueChanged events across clients when simultaneous sets occur. the - client that set last will get a single valueChanged event, while earlier setters will get an additional event for - each set after their own. diff --git a/docs/content/docs/deep/feature-detection-iprovide.md b/docs/content/docs/deep/feature-detection-iprovide.md deleted file mode 100644 index f224ab4db0b5..000000000000 --- a/docs/content/docs/deep/feature-detection-iprovide.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: Feature detection via FluidObject -draft: true -status: outdated -aliases: - - "/docs/advanced/feature-detection-iprovide/" ---- - -In an earlier section we introduced the Data Object, a convenient way to combine distributed data structures and our own -code (business logic) into a modular, reusable piece. This in turn enables us to modularize pieces of our application -- -data included. - -Fluid can be a very dynamic system. There are scenarios in which your code will call certain members of an object, *if -and only if*, the object has certain capabilities; that is, it implements certain interfaces. So, your code needs a way -of detecting whether the object implements specific interfaces. To make this easier, Fluid has a feature detection -mechanism, which centers around a special type called `FluidObject`. Feature detection is a technique by which one -Data Object can dynamically determine the capabilities of another Data Object. - -In order to detect features supported by an unknown object, you cast it to an `FluidObject` and then query the object -for a specific interface that it may support. The interfaces available via `FluidObject` include many core Fluid -interfaces, such as `IFluidHandle` or `IFluidLoadable`. This -discovery system (see example below) enables any Data Object to record what interfaces it implements and make it -possible for other Data Objects to discover them. The specifics of how these interfaces are declared is not relevant -until you want to define your own interfaces, which we'll cover in a later section. - -The following is an example of feature detection using `FluidObject`: - -```typescript -const anUnknownObject = anyObject as FluidObject; - -// Query the object to see if it supports IFluidLoadable -const loadable = anUnknownObject.IFluidLoadable; // loadable: IFluidLoadable | undefined - -if (loadable) { // or if (loadable !== undefined) - // It does! Now we know definitively that loadable's type is IFluidLoadable and we can safely call a method - await loadable.method(); -} -``` - -Note the `anUnknownObject.IFluidLoadable` expression and the types of the objects. If the object supports IFluidLoadable, -then an IFluidLoadable will be returned; otherwise, `undefined` will be returned. - - -## Delegation and the *IProvide* pattern - -In the example above, `fluidObject.IFluidLoadable` is a *property* that is of type IFluidLoadable. `fluidObject` itself -need not implement IFluidLoadable. Rather, it must *provide* an implementation of IFluidLoadable. We call this -*delegation* -- `fluidObject.IFluidLoadable` may return `fluidObject` itself in its implementation, or it may delegate by -returning another object that implements IFluidLoadable. - -If you search through the Fluid Framework code, you'll notice that many interfaces come in pairs, such as -`IFluidLoadable` and `IProvideFluidLoadable`. `IProvideFluidLoadable` is defined as follows: - -```typescript -export interface IProvideFluidLoadable { - readonly IFluidLoadable: IFluidLoadable; -} -``` - -We call this the *IProvide pattern*. This interface definition means that if we have an `IProvideFluidLoadable`, we may -call `.IFluidLoadable` on it and get an `IFluidLoadable` back -- which is what we did in the code sample above. - -As mentioned earlier, an object that implements IFluidLoadable may choose to return itself. This is quite common in -practice and is facilitated by the following convention: `IFluidFoo extends IProvideFluidFoo`. - -Returning to our `IFluidLoadable` example: - -```typescript -export interface IFluidLoadable extends IProvideFluidLoadable { - ... -} -``` - -The following example shows how a class may implement the IProvide* interfaces two different ways: - -```typescript -export abstract class PureDataObject<...> - extends ... - implements IFluidLoadable, IFluidRouter, IProvideFluidHandle -{ - ... - private readonly innerHandle: IFluidHandle; - ... - public get IFluidLoadable() { return this; } - public get IFluidHandle() { return this.innerHandle; } -``` - -`PureDataObject` implements `IProvideFluidLoadable` via `IFluidLoadable`, and thus simply returns `this` in that case. -But for `IProvideFluidHandle`, it delegates to a private member. The caller does not need to know how the property is -implemented -- it simply asks for `fluidObject.IFluidLoadable` or `fluidObject.IFluidHandle` and either gets back an -object of the correct type or `undefined`. - - - - - - - - - - - -[Fluid container]: {{< relref "containers.md" >}} -[Signals]: {{< relref "/docs/concepts/signals.md" >}} - - - -[SharedCounter]: {{< relref "/docs/data-structures/counter.md" >}} -[SharedMap]: {{< relref "/docs/data-structures/map.md" >}} -[SharedString]: {{< relref "/docs/data-structures/string.md" >}} -[Sequences]: {{< relref "/docs/data-structures/sequences.md" >}} -[SharedTree]: {{< relref "/docs/data-structures/tree.md" >}} - - - -[fluid-framework]: {{< packageref "fluid-framework" "v2" >}} -[@fluidframework/azure-client]: {{< packageref "azure-client" "v2" >}} -[@fluidframework/tinylicious-client]: {{< packageref "tinylicious-client" "v1" >}} -[@fluidframework/odsp-client]: {{< packageref "odsp-client" "v2" >}} - -[AzureClient]: {{< apiref "azure-client" "AzureClient" "class" "v2" >}} -[TinyliciousClient]: {{< apiref "tinylicious-client" "TinyliciousClient" "class" "v1" >}} - -[FluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} -[IFluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} - - - - diff --git a/docs/content/docs/deep/grouped-ops.md b/docs/content/docs/deep/grouped-ops.md deleted file mode 100644 index 64b044d34960..000000000000 --- a/docs/content/docs/deep/grouped-ops.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Grouped Ops -menuPosition: 6 -status: unwritten -discussion: 5468 -aliases: - - "/docs/advanced/grouped-ops/" -draft: true ---- - -Grouped ops provide a guarantee that all ops within a group will be ordered as a whole group. - -This is not the same as atomicity, and we need to explain that. diff --git a/docs/content/docs/deep/hosts.md b/docs/content/docs/deep/hosts.md deleted file mode 100644 index 5053330d44a5..000000000000 --- a/docs/content/docs/deep/hosts.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Hosts and the loader -menuPosition: 4 -aliases: - - "/docs/concepts/hosts" -draft: true ---- - -The **Fluid loader** is one of the key parts of the Fluid Framework. Developers use the Fluid loader within their -applications to load Fluid containers and to initiate communication with the Fluid service. - -A **Fluid host** is any application that uses the Fluid loader to load a Fluid container. - -The Fluid loader uses a plugin model. - - -## Who needs a Fluid loader? - -If your app or website will load a Fluid container, then you are creating a Fluid host and you will need to use the -Fluid loader! - -If you are building a Fluid container and you will not build a standalone application with Fluid, you may still be -interested in learning about the Fluid loader. The Fluid loader includes capabilities, such as host scopes, that are used -by containers. - -You may also want to host your Fluid container on a standalone website. - - -## Summary - -The Fluid loader loads Fluid containers by connecting to the Fluid service and fetching Fluid container code. From a -system architecture perspective, the Fluid loader sits in between the Fluid service and a Fluid container. - -The Fluid architecture consists of a client and service. The
-client contains the Fluid loader and the Fluid container. The Fluid loader contains a document service factory, code
-loader, scopes, and a URL resolver. The Fluid runtime is encapsulated within a container, which is built using Fluid
-objects and distributed data structures. - -The Fluid loader is intended to be extremely generic. To maintain generic-ness, the loader uses a plugin model. With the -right plugins (drivers, handlers, resolvers), the Fluid loader will work for any wire protocol and any service -implementation. - -The loader mimics existing web protocols. Similar to how the browser requests state and app logic (a website) from a -web server, a Fluid host uses the loader to request a [Fluid container][] from the Fluid service. - -## Fluid host responsibilities - -A Fluid host creates a Fluid loader with a URL resolver, Fluid service driver, and code loader. The host then requests a -Fluid container from the loader. Finally, the host *does something* with the Fluid containers. A host can request -multiple containers from the loader. - -The Fluid loader connects to a URL using a container resolver, a
-service driver, and a container code loader. It then returns a Fluid container or shared object. - -We'll talk about each of these parts, starting with the request and loader dependencies, over the next sections. - -## Loading a container: class by class - -Let's address the role of each part of the Fluid loader and dive in to some details. - -### Request - -The request includes a Fluid container URL and optional header information. This URL contains a protocol and other -information that will be parsed by the URL Resolver to identify where the container is located. - -This is not part of instantiating the loader. The request kicks of the process of loading a container. - -### URL resolver - -The URL resolver parses a request and returns an `IFluidResolvedUrl`. This object includes all the endpoints and tokens -needed by the Fluid service driver to access the container. - -An example `IFluidResolvedUrl` includes the below information. - -```typescript -const resolvedUrl: IFluidResolvedUrl = { - endpoints: { - deltaStorageUrl: "www.ContosoFluidService.com/deltaStorage", - ordererUrl: "www.ContosoFluidService.com/orderer", - storageUrl: "www.ContosoFluidService.com/storage", - }, - tokens: { jwt: "token" }, - type: "fluid", - url: "https://www.ContosoFluidService.com/ContosoTenant/documentIdentifier", -} -``` - -You may notice we are mimicking the DNS and protocol lookup a browser performs when loading a webpage. That's because a -loader may access containers stored on multiple Fluid services. Furthermore, each Fluid service could be operating with -a different API and protocol. - -### Fluid service driver factory (DocumentServiceFactory) - -The loader uses a Fluid service driver to connect to a Fluid service. - -While many developers will only load one container at a time, it's interesting to consider how the loader handles -loading two containers that are stored on different Fluid services. To keep track of the services, the loader uses the -protocol from the resolved URL to identify the correct Fluid service driver for the Fluid service. - -### Code loader - -The loader uses the code loader to fetch container code. Because a Fluid container is a app logic and distributed state -we need all of the connected clients to agree on the same container code. - -### Scopes - -Scopes allow the container access to resources from the host. For example, the host may have access to an authorization -context that the container code is not trusted to access. The host can provide a scope to the container that federates -access to the secure resource. - -## Handling the response - -The Fluid loader will return a response object from the request. This is a continuation of our web protocol metaphor, -you'll receive an object with a mimeType (e.g. "fluid/object"), response status (e.g. 200), and a value (e.g. the Fluid -object). - -The host is responsible for checking that this response is valid. Did the loader return a 200? Is the mimeType correct? -As the Fluid Framework expands, we intend to make further use of these responses. - - - - - - - - - - -[Fluid container]: {{< relref "containers.md" >}} -[Signals]: {{< relref "/docs/concepts/signals.md" >}} - - - -[SharedCounter]: {{< relref "/docs/data-structures/counter.md" >}} -[SharedMap]: {{< relref "/docs/data-structures/map.md" >}} -[SharedString]: {{< relref "/docs/data-structures/string.md" >}} -[Sequences]: {{< relref "/docs/data-structures/sequences.md" >}} -[SharedTree]: {{< relref "/docs/data-structures/tree.md" >}} - - - -[fluid-framework]: {{< packageref "fluid-framework" "v2" >}} -[@fluidframework/azure-client]: {{< packageref "azure-client" "v2" >}} -[@fluidframework/tinylicious-client]: {{< packageref "tinylicious-client" "v1" >}} -[@fluidframework/odsp-client]: {{< packageref "odsp-client" "v2" >}} - -[AzureClient]: {{< apiref "azure-client" "AzureClient" "class" "v2" >}} -[TinyliciousClient]: {{< apiref "tinylicious-client" "TinyliciousClient" "class" "v1" >}} - -[FluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} -[IFluidContainer]: {{< apiref "fluid-static" "IFluidContainer" "interface" "v2" >}} - - - - diff --git a/docs/content/docs/deep/service.md b/docs/content/docs/deep/service.md deleted file mode 100644 index 64dbbdaa461e..000000000000 --- a/docs/content/docs/deep/service.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: The Fluid service -menuPosition: 3 -aliases: - - "/docs/concepts/service" -draft: true ---- - -The Fluid Framework contains a service component. A reference implementation of a Fluid service called *Routerlicious* is -included in the FluidFramework repo. Note that Routerlicious is one of many Fluid services that could be implemented. -The Fluid Framework uses a loose-coupling architecture for integrating with services, so Fluid is not limited to a single -implementation. - - -## Responsibilities - -Fluid services like Routerlicious have three responsibilities: - -1. **Ordering:** They assign monotonically increasing sequence numbers to incoming operations. -1. **Broadcast:** They then broadcast the operations to all connected clients, including their sequence numbers. -1. **Storage:** They're also responsible for storing Fluid data in the form of summary operations. - - -## Ordering and drivers - -The Fluid service ensures that all operations are ordered and also broadcasts the operations to other connected clients. -We sometimes refer to this as "op routing;" this is the source of the name *Routerlicious*. - - -## Summaries - -Summaries are a serialized form of a Fluid document, created by consolidating all operations and serializing the data -model. Summaries are used to improve load performance. When a Fluid document is loaded, the service may send a summary -to the client so that the client does not need to replay all ops locally to get to the current state. - -One of the connected clients is chosen to generate the summary. Once the summary is created it is sent to the service -like any other operation. To learn more about summaries and how they are created, see the [advanced Summarizer -topic]({{< relref "summarizer.md" >}}). - - -## Drivers - -The Fluid Framework uses a loose-coupling architecture for integrating with Fluid services. Drivers are used to abstract -the service-specific behavior. This enables an implementer to use any ordering and storage architecture or technology to -implement the Fluid service. - - -## More information - -You can learn more about Routerlicious, including how to run it using Docker, at -. diff --git a/docs/content/docs/deep/summaryTelemetry.md b/docs/content/docs/deep/summaryTelemetry.md deleted file mode 100644 index 91415f7bf4be..000000000000 --- a/docs/content/docs/deep/summaryTelemetry.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -title: Summary telemetry -menuPosition: 9 -draft: true ---- - - -## Summary Collection - -The core data structure that tracks summary attempts and corresponding results by monitoring the op log. - -### SummaryAckWithoutOp - -> Error - -It means that a summary ack was observed without a corresponding summary op. We only raise this event if the missing summary op's sequence number >= the initial sequence number which we loaded from. - -Potential causes are that a summary op was nacked then acked, double-acked, or the `summarySequenceNumber` is invalid. All cases should be recoverable, but still indicate bad behavior. - -- `sequenceNumber` - sequence number of the observed summary ack op. -- `summarySequenceNumber` - sequence number of the missing summary op, as indicated by the summary ack op. -- `initialSequenceNumber` - sequence number we initially loaded from. This is relevant since it is compared with the missing summary op sequence number to determine if we are in an error case or not. - -## Summary Manager - -> Event Prefix: `SummaryManager:` - -### CreatingSummarizer - -Logs right before attempting to spawn summarizer client. - -- `throttlerDelay` - throttle delay in ms (does not include initial delay) -- `initialDelay` - initial delay in ms -- `opsSinceLastAck` - count of ops since last summary ack, reported by SummaryCollection. This can be relevant for the initial delay bypass logic. -- `opsToBypassInitialDelay` - count of ops since last summary ack that allow us to bypass the initial delay - -### RunningSummarizer - -> Performance - -The parent client elected as responsible for summaries tracks the life cycle of its spawned summarizer client. - -This event starts when calling `run()` on the spawned summarizer client's `ISummarizer`. - -This event ends when that `run()` call's resulting promise is fulfilled. This happens when the client closes. - -- `attempt` - number of attempts within the last time window, used for calculating the throttle delay. - -### SummarizerException - -> Error - -Exception raised during summarization. - -- `category` - string that categorizes the exception ("generic" or "error") - -### EndingSummarizer - -Logs after summarizer has stopped running, i.e., after the client has disconnected or stop has been requested - -- `reason` - the reason for stopping, returned by Summarizer.run - -## Summarizer Client Election - -> Event Prefix: `OrderedClientElection:` - -### ElectedClientNotSummarizing - -> Error - -When a client is elected the summarizer, this indicates that too many ops have passed since they were elected or since their latest successful summary ack if they have one. - -- `electedClientId` - the client ID of the elected parent client responsible for summaries which is not summarizing. -- `lastSummaryAckSeqForClient` - the sequence number of the last summary ack received during this client's election. -- `electionSequenceNumber` - the sequence number at which this failing client was elected. -- `nextElectedClientId` - the client ID of the next oldest client in the Quorum which is eligible to be elected as responsible for summaries. It may be undefined if the currently elected client is the youngest (or only) client in the Quorum. -- `electionEnabled` - election of a new client on logging this error is enabled - -### UnexpectedElectionSequenceNumber - -> Unexpected Error - -Verifies the state transitioned as expected, based on assumptions about how `OrderedClientElection` works. - -- `lastSummaryAckSeqForClient` - expected to be undefined! -- `electionSequenceNumber` - expected to be same as op sequence number! - -## Ordered Client Election - -> Event Prefix: `OrderedClientElection:` - -### InitialElectedClientNotFound - -> Error - -Failed to find the initially elected client determined by the state in the summary. This is unexpected, and likely indicates a discrepancy between the `Quorum` members and the `SummarizerClientElection` state at the time the summary was generated. - -When this error happens, no client will be elected at the start. The code in `SummarizerClientElection` should still recover from this scenario. - -- `electionSequenceNumber` - sequence number which the initially elected client was supposedly elected as of. This is coming from the initial state loaded from the summary. -- `expectedClientId` - client ID of the initially elected client which was not found in the underlying `OrderedClientCollection`. This is coming from the base summary. -- `electedClientId` - the client which will now be elected; always undefined. -- `clientCount` - the number of clients in the underlying `OrderedClientCollection`, which should be the same as the number of clients in the `Quorum` at the time of load. - -### InitialElectedClientIneligible - -> Error - -The initially elected client determined by the summary fails the eligibility check. Presumably they must have passed it at the time the summary was generated and they were originally elected. So this indicates a discrepancy/change between the eligibility or a bug in the code. - -When this error happens, the first eligible client that is younger than this client will be elected. - -- `electionSequenceNumber` - sequence number which the initially elected client was elected as of. This is coming from the initial state loaded from the summary. -- `expectedClientId` - client ID of the initially elected client which is failing the eligibility check. This is coming from the base summary. -- `electedClientId` - client ID of the newly elected client or undefined if no younger clients are eligible. - -## Ordered Client Collection - -> Event Prefix: `OrderedClientCollection:` - -## ClientNotFound - -> Error - -A member of the `Quorum` was removed, but it was not found in the `OrderedClientCollection`. This should not be possible, since the tracked clients in the `OrderedClientCollection` should match 1-1 to the clients in the `Quorum`. - -- `clientId` - client ID of the member removed from the `Quorum`. -- `sequenceNumber` - sequence number at the time when the member was removed from the `Quorum`. This should be equivalent to the sequence number of their leave op, since that is what triggers them exiting the `Quorum`. - -## Summarizer - -> Event Prefix: `Summarizer:` - -### StoppingSummarizer - -This event fires when the Summarizer is stopped. - -- `reason` - reason code provided for stopping. -- `onBehalfOf` - the last known client ID of the parent client which spawned this summarizer client. - -### RunningSummarizer - -Summarizer has started running. This happens when the summarizer client becomes connected with write permissions, and `run()` has been called on it. At this point in time it will create a `RunningSummarizer` and start updating its state in response to summary ack ops. - -- `onBehalfOf` - the last known client ID of the parent client which spawned this summarizer client. -- `initSummarySeqNumber` - initial sequence number that the summarizer client loaded from - -### HandleSummaryAckError - -> Error - -An error was encountered while watching for or handling an inbound summary ack op. - -- `referenceSequenceNumber` - reference sequence number of the summary ack we are handling if the error occurs during `refreshLatestSummaryAck` (most likely). It could be the reference sequence number of the previously handled one + 1 (defaulting to initial sequence number if this is the first) if the error occurs while waiting for the summary ack (indicating a bug in `SummaryCollection`), but that should be significantly less likely. - -### HandleSummaryAckFatalError - -> Unexpected Error - -This should not even be possible, but it means that an unhandled error was raised while listening for summary ack ops in a loop. This is particularly unexpected, because if any handling of a summary ack fails, then we catch that error already and keep going, logging a different error. - -## Running Summarizer - -> Event Prefix: `Summarizer:Running:` - -- `summarizeCount` - the number of summarize attempts this client has made. This can be used to correlate events for individual summary attempts. -- `summarizerSuccessfulAttempts` - the number of successful summaries this summarizer instance has performed. This property subtracted from the `summarizeCount` property equals the number of attempts that failed to produce a summary. - -### SummaryAckWaitTimeout - -> Error - -When a summary op is sent, the summarizer waits `summaryAckWaitTimeout` for a summary ack/nack op in response from the server. If a corresponding response is not seen within that time, this event is raised, and the client retries. - -- `maxAckWaitTime` - cap on the maximum amount of time client will wait for a summarize op ack -- `referenceSequenceNumber` - last attempt summary op reference sequence number. -- `summarySequenceNumber` - last attempt summary op sequence number. -- `timePending` - time spent waiting for a summary ack/nack as computed by client. - -### MissingSummaryAckFoundByOps - -During first load, the wait for a summary ack/nack op in response to a summary op, can be bypassed by comparing the op timestamps. Normally a timer is used while running, but if the server-stamped op time difference exceeds the `maxAckWaitTimeout`, then raise this event, clear the timer and stop waiting to start. - -- `referenceSequenceNumber` - last attempt summary op reference sequence number. -- `summarySequenceNumber` - last attempt summary op sequence number. - -### SummarizeAttemptDelay - -Logs the presence of a delay before attempting summary. Note that the event is logged before waiting for the delay. - -- `duration` - duration delay in seconds. This is the `retryAfter` value found in the summary nack response op, if present. -Otherwise, it's the delay from regular summarize attempt retry. -- `reason` - "nack with retryAfter" if the `duration` value came from a summary nack response op. Undefined otherwise. - -### FailToSummarize - -> Error - -All consecutive retry attempts to summarize by heuristics have failed. The summarizer client should stop itself with "failToSummarize" reason code, closing the container. - -- `summarizeReason` - reason for attempting to summarize -- `message` - message returned with the last summarize result - -### UnexpectedSummarizeError - -> Unexpected Error - -This should not be possible, but it indicates an error was thrown in the code that runs immediately after a summarize attempt. This is just lock release and checking if it should summarize again. - -## Summary Generator - -> Event Prefix: `Summarizer:Running:` - -- `summarizeCount` - the number of summarize attempts this client has made. This can be used to correlate events for individual summary attempts. -- `summarizerSuccessfulAttempts` - the number of successful summaries this summarizer instance has performed - -### UnexpectedSummarizeError - -> Unexpected Error - -This definitely should not happen, since the code that can trigger this is trivial. - -### Summarize - -> Performance - -This event is used to track an individual summarize attempt from end to end. - -The event starts when the summarize attempt is first started. - -The event ends after a summary ack op is received in response to this attempt's summary op. - -The event cancels in response to a summary nack op for this attempt, an error along the way, or if the client disconnects while summarizing. - -- `reason` - reason code for attempting to summarize. -- `fullTree` - flag indicating whether the attempt should generate a full summary tree without any handles for unchanged subtrees. -- `timeSinceLastAttempt` - time in ms since the last summary attempt (whether it failed or succeeded) for this client. -- `timeSinceLastSummary` - time in ms since the last successful summary attempt for this client. - -- `message` - message indicating result of summarize attempt; possible values: - - - `disconnect` - the summary op was submitted but broadcast was cancelled. - - `submitSummaryFailure` - the attempt failed to submit the summary op. - - `summaryOpWaitTimeout` - timeout while waiting to receive the submitted summary op broadcasted. - - `summaryAckWaitTimeout` - timeout while waiting to receive a summary ack/nack op in response to this attempt's summary op. - - `summaryNack` - attempt was rejected by server via a summary nack op. - - `summaryAck` - attempt was successful, and the summary ack op was received. - -- `ackWaitDuration` (ack/nack received only) - time in ms spent waiting for the summary ack/nack op after submitting the summary op. -- `ackNackSequenceNumber` (ack/nack received only) - sequence number of the summary ack/nack op in response to this attempt's summary op. -- `summarySequenceNumber` (ack/nack received only) - sequence number of this attempt's summary op. -- `handle` (ack only) - summary handle found on this attempt's summary ack op. - -### Summarize_generate - -This event fires during a summary attempt, as soon as the ContainerRuntime has finished its summarize work, which consists of: generating the tree, uploading to storage, and submitting the op. It should fire this event even if something goes wrong during those steps. - -- `fullTree` - flag indicating whether the attempt should generate a full summary tree without any handles for unchanged subtrees. -- `timeSinceLastAttempt` - time in ms since the last summary attempt (whether it failed or succeeded) for this client. -- `timeSinceLastSummary` - time in ms since the last successful summary attempt for this client. -- `referenceSequenceNumber` - reference sequence number at the time of this summary attempt. -- `opsSinceLastAttempt` - number of ops that have elapsed since the the last summarize attempt for this client. -- `opsSinceLastSummary` - number of ops that have elapsed since the last successful summarize attempt for this client. -- several properties with summary stats (count of nodes in the tree, etc.) -- `generateDuration` (only if tree generated) - time in ms it took to generate the summary tree. -- `handle` (only if uploaded to storage) - proposed summary handle as returned by storage for this summary attempt. -- `uploadDuration` (only if uploaded to storage) - time in ms it took to upload the summary tree to storage and receive back a handle. -- `clientSequenceNumber` (only if summary op submitted) - client sequence number of summary op submitted for this attempt. This can be used to correlate the submit attempt with the received summary op after it is broadcasted. - -### IncrementalSummaryViolation - -> Error - -Fires if an incremental summary (i.e., not full tree) summarizes more data stores than the expected maximum number - -- `summarizedDataStoreCount` - number of data stores actually summarized -- `gcStateUpdatedDataStoreCount` - number of data stores with an updated GC state since the last summary -- `opsSinceLastSummary` - number of ops since the last summary - -### Summarize_Op - -This event fires during a summary attempt, as soon as the client observes its own summary op. This means that the summary op it submitted was sequenced and broadcasted by the server. - -- `duration` - time in ms spent waiting for the summary op to be broadcast after submitting it. This should be low; should represent the round-trip time for an op. -- `referenceSequenceNumber` - reference sequence number of the summary op. This should match the reference sequence number of the Summarize event for this attempt as well. -- `summarySequenceNumber` - server-stamped sequence number of the summary op for this attempt. -- `handle` - proposed summary tree handle on the summary op for this attempt, which was originally returned from storage. - -### SummaryNack - -> Error - -Fires if the summary receives a nack response - -- `fullTree` - flag indicating whether the attempt should generate a full summary tree without any handles for unchanged subtrees. -- `timeSinceLastAttempt` - time in ms since the last summary attempt (whether it failed or succeeded) for this client. -- `timeSinceLastSummary` - time in ms since the last successful summary attempt for this client. -- `referenceSequenceNumber` - reference sequence number at the time of this summary attempt. -- `opsSinceLastAttempt` - number of ops that have elapsed since the the last summarize attempt for this client. -- `opsSinceLastSummary` - number of ops that have elapsed since the last successful summarize attempt for this client. -- several properties with summary stats (count of nodes in the tree, etc.) -- `generateDuration` (only if tree generated) - time in ms it took to generate the summary tree. -- `handle` (only if uploaded to storage) - proposed summary handle as returned by storage for this summary attempt. -- `uploadDuration` (only if uploaded to storage) - time in ms it took to upload the summary tree to storage and receive back a handle. -- `clientSequenceNumber` (only if summary op submitted) - client sequence number of summary op submitted for this attempt. This can be used to correlate the submit attempt with the received summary op after it is broadcasted. -- `retryAfterSeconds` - time in seconds to wait before retrying, as read from the nack message - -### SummarizeTimeout - -> Performance - -This event can fire multiple times (up to a cap) per summarize attempt. It indicates that a lot of time has passed during the summarize attempt. - -For example, after 20 seconds of summarizing this event might fire. Then after another 40 seconds pass, it will fire again. Then after another 80 seconds pass, it will fire again. The third time that it logged, a total time of 140 seconds has passed. - -- `timeoutTime` - time in ms for this timeout to occur, this counts since the previous timeout event for this summarize attempt, so it is not cumulative. -- `timeoutCount` - number of times this event has fired for this attempt. - -## SummarizerNode - -Should use the in-progress summarize attempt correlated logger. - -### DecodeSummaryMaxDepth - -Differential summaries are disabled, so we aren't expecting to see this often, but it is possible since it happens while loading a snapshot. - -Indicates >100 consecutive failed summaries for a single datastore. It means there are 100+ nested `_baseSummary` trees encountered while loading. - -- `maxDecodeDepth` - 100 - -### DuplicateOutstandingOps - -Differential summaries are disabled, so we aren't expecting to see this often, but it is possible since it happens while loading a snapshot. - -When organizing the outstanding ops from the `_outstandingOps` blobs of nested differential summaries, it found an overlap in sequence number ranges. This indicates something went wrong. - -- `message` - "newEarliestSeq <= latestSeq in decodeSummary: {newEarliestSeq} <= {latestSeq}" - -## Container Runtime - -Should use the in-progress summarize attempt correlated logger. - -### SequenceNumberMismatch - -> Error - -Fires during ContainerRuntime load from snapshot if the sequence number read from the snapshot does not match DeltaManager.initialSequenceNumber. - -### SummariesDisabled - -Fires during ContainerRuntime load if automatic summaries are disabled for the given Container - -### SummaryStatus:Behind - -> Error - -Fires if too many ops (7000 by default) have been processed since the last summary. - -### SummaryStatus:CaughtUp - -Fires if, after a previous `SummaryStatus:Behind` event, a summary ack is received - -### LastSequenceMismatch - -> Error - -Fires on summary submit if the summary sequence number does not match the sequence number of the last message processed by the Delta Manager. - -- `error` - error message containing the mismatched sequence numbers - -### GarbageCollection - -> Performance - -This event tracks the performance around the garbage collection process. - -- `deletedNodes` -- `totalNodes` -- `deletedDataStores` -- `totalDataStores` - -### MissingGCNode - -> Disabled: too noisy - -While running garbage collection, a node was detected as missing that is referenced. - -- `missingNodeId` diff --git a/docs/content/docs/start/tree-start.md b/docs/content/docs/start/tree-start.md index 245a1220fef0..8f23fcaf99aa 100644 --- a/docs/content/docs/start/tree-start.md +++ b/docs/content/docs/start/tree-start.md @@ -181,7 +181,7 @@ class TodoList extends schemaFactory.object("TodoList", { These methods are designed to merge well in collaborative settings without you having to think much about it. See [schema definition](../data-structures/tree) for more details on the built-in editing methods. -You can also read more about how these editing operations work in collaborative settings [here](https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/main/merge-semantics.md). +You can also read more about how these editing operations work in collaborative settings [here](https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/user-facing/merge-semantics.md). ### Grouping Edits into Transactions diff --git a/docs/content/docs/testing/debugging.md b/docs/content/docs/testing/debugging.md deleted file mode 100644 index 45472b50c8df..000000000000 --- a/docs/content/docs/testing/debugging.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Debugging -menuPosition: 5 -status: unwritten -draft: true ---- - -## How to test your application - -### Enable Fluid logs in the browser - -### Understanding Fluid error logs - -Errors raised by the Fluid Framework, or handled and "normalized" by the framework, will have a few keys fields to consider: - -* `errorType` -- e.g. `throttlingError` -- A code-searchable term that directs you to the "class" of error. This may indicate some other domain-specific data that would be logged, such as `retryAfterSeconds`. This is the only field in the error contract used programatically by partners. -* `error` or `message` (optional) -- The free-form error message. May contain additional details, but if not, remember to check for other properties -in the log line. In cases where an external error is wrapped, you may find there's a prefix that gives Fluid's summary of the error, -with the original error message following after a colon. - -Note that for a time, `fluidErrorCode` was used in addition to `message` to describe the specific error case, but has since been deprecated. - -## Debugging with Fluid diff --git a/docs/content/posts/_index.md b/docs/content/posts/_index.md deleted file mode 100644 index 0c416154b97b..000000000000 --- a/docs/content/posts/_index.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Blog & Updates" -draft: true ---- diff --git a/docs/static/images/container-and-component-loading-1.jpg b/docs/static/images/container-and-component-loading-1.jpg deleted file mode 100644 index 600987a90d6c..000000000000 Binary files a/docs/static/images/container-and-component-loading-1.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-10.jpg b/docs/static/images/container-and-component-loading-10.jpg deleted file mode 100644 index 64fceed43071..000000000000 Binary files a/docs/static/images/container-and-component-loading-10.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-11.jpg b/docs/static/images/container-and-component-loading-11.jpg deleted file mode 100644 index 85dcaf916c82..000000000000 Binary files a/docs/static/images/container-and-component-loading-11.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-2.jpg b/docs/static/images/container-and-component-loading-2.jpg deleted file mode 100644 index 99ee7eee7ad7..000000000000 Binary files a/docs/static/images/container-and-component-loading-2.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-3.jpg b/docs/static/images/container-and-component-loading-3.jpg deleted file mode 100644 index 4c7cf4628778..000000000000 Binary files a/docs/static/images/container-and-component-loading-3.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-4.jpg b/docs/static/images/container-and-component-loading-4.jpg deleted file mode 100644 index e6a6667518ca..000000000000 Binary files a/docs/static/images/container-and-component-loading-4.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-5.jpg b/docs/static/images/container-and-component-loading-5.jpg deleted file mode 100644 index c106797ff998..000000000000 Binary files a/docs/static/images/container-and-component-loading-5.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-6.jpg b/docs/static/images/container-and-component-loading-6.jpg deleted file mode 100644 index b9cd2cd79eb6..000000000000 Binary files a/docs/static/images/container-and-component-loading-6.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-7.jpg b/docs/static/images/container-and-component-loading-7.jpg deleted file mode 100644 index 361eb630fcd8..000000000000 Binary files a/docs/static/images/container-and-component-loading-7.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-8.jpg b/docs/static/images/container-and-component-loading-8.jpg deleted file mode 100644 index 42cab0de00a1..000000000000 Binary files a/docs/static/images/container-and-component-loading-8.jpg and /dev/null differ diff --git a/docs/static/images/container-and-component-loading-9.jpg b/docs/static/images/container-and-component-loading-9.jpg deleted file mode 100644 index 2dcd3a438318..000000000000 Binary files a/docs/static/images/container-and-component-loading-9.jpg and /dev/null differ diff --git a/examples/apps/ai-collab/.eslintrc.cjs b/examples/apps/ai-collab/.eslintrc.cjs index 6f52ebade008..b4c382a0e7b6 100644 --- a/examples/apps/ai-collab/.eslintrc.cjs +++ b/examples/apps/ai-collab/.eslintrc.cjs @@ -25,7 +25,7 @@ module.exports = { "@/components/**", // Experimental package APIs and exports are unknown, so allow any imports from them. - "@fluid-experimental/**", + "@fluidframework/ai-collab/alpha", ], }, ], diff --git a/examples/apps/ai-collab/CHANGELOG.md b/examples/apps/ai-collab/CHANGELOG.md index 542c567d4866..93e3f1b3f97b 100644 --- a/examples/apps/ai-collab/CHANGELOG.md +++ b/examples/apps/ai-collab/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/ai-collab +## 2.5.0 + +Dependency updates only. + ## 2.4.0 New package. diff --git a/examples/apps/ai-collab/README.md b/examples/apps/ai-collab/README.md index 934878c9d997..b983c6f709e0 100644 --- a/examples/apps/ai-collab/README.md +++ b/examples/apps/ai-collab/README.md @@ -1,6 +1,6 @@ # @fluid-example/ai-collab -This is an example app that showcases the `@fluid-experimental/ai-collab` package to interact with an LLM (Large Language +This is an example app that showcases the `@fluidframework/ai-collab` package to interact with an LLM (Large Language Model). By default it uses Tinylicious as the server, but you can also use SharePoint embedded. diff --git a/examples/apps/ai-collab/package.json b/examples/apps/ai-collab/package.json index 125ee6188ec8..77593128d47e 100644 --- a/examples/apps/ai-collab/package.json +++ b/examples/apps/ai-collab/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/ai-collab", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example app that showcases the experimental package for AI collaboration in Fluid-based applications.", "homepage": "https://fluidframework.com", @@ -36,11 +36,11 @@ "@biomejs/biome": "~1.9.3", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@fluid-experimental/ai-collab": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluidframework/ai-collab": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/devtools": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/odsp-client": "workspace:~", diff --git a/examples/apps/ai-collab/src/components/TaskCard.tsx b/examples/apps/ai-collab/src/components/TaskCard.tsx index a08e24a299c7..7955c08475e5 100644 --- a/examples/apps/ai-collab/src/components/TaskCard.tsx +++ b/examples/apps/ai-collab/src/components/TaskCard.tsx @@ -10,7 +10,7 @@ import { type DifferenceChange, type DifferenceMove, SharedTreeBranchManager, -} from "@fluid-experimental/ai-collab"; +} from "@fluidframework/ai-collab/alpha"; import { Icon } from "@iconify/react"; import { LoadingButton } from "@mui/lab"; import { diff --git a/examples/apps/ai-collab/src/components/TaskGroup.tsx b/examples/apps/ai-collab/src/components/TaskGroup.tsx index a3270e4fb694..f30d5e3dbb39 100644 --- a/examples/apps/ai-collab/src/components/TaskGroup.tsx +++ b/examples/apps/ai-collab/src/components/TaskGroup.tsx @@ -3,8 +3,12 @@ * Licensed under the MIT License. */ -import { type Difference, SharedTreeBranchManager } from "@fluid-experimental/ai-collab"; -import { type TreeBranch, type TreeBranchFork } from "@fluidframework/tree/alpha"; +import { type Difference, SharedTreeBranchManager } from "@fluidframework/ai-collab/alpha"; +import { + type BranchableTree, + type TreeBranchFork, + type TreeViewAlpha, +} from "@fluidframework/tree/alpha"; import { Icon } from "@iconify/react"; import { LoadingButton } from "@mui/lab"; import { @@ -48,7 +52,7 @@ export function TaskGroup(props: { const [isAiTaskRunning, setIsAiTaskRunning] = useState(false); const [llmBranchData, setLlmBranchData] = useState<{ differences: Difference[]; - originalBranch: TreeBranch; + originalBranch: BranchableTree; forkBranch: TreeBranchFork; forkView: TreeView; newBranchTargetNode: SharedTreeTaskGroup; @@ -194,7 +198,8 @@ export function TaskGroup(props: { ); const { originalBranch, forkBranch, forkView, newBranchTargetNode } = branchManager.checkoutNewMergedBranchV2( - props.treeView, + // TODO: Remove cast when TreeViewAlpha becomes public + props.treeView as TreeViewAlpha, TREE_CONFIGURATION, ["taskGroups", indexOfTaskGroup], ); diff --git a/examples/apps/attributable-map/.eslintrc.cjs b/examples/apps/attributable-map/.eslintrc.cjs index 484c63b7e874..bd863a6a893a 100644 --- a/examples/apps/attributable-map/.eslintrc.cjs +++ b/examples/apps/attributable-map/.eslintrc.cjs @@ -5,5 +5,7 @@ module.exports = { extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"], - rules: {}, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/apps/attributable-map/CHANGELOG.md b/examples/apps/attributable-map/CHANGELOG.md index 5e3917e89183..1a97c50b3ef3 100644 --- a/examples/apps/attributable-map/CHANGELOG.md +++ b/examples/apps/attributable-map/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/attributable-map +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/attributable-map/package.json b/examples/apps/attributable-map/package.json index df3c19b8032e..c4e9134500bb 100644 --- a/examples/apps/attributable-map/package.json +++ b/examples/apps/attributable-map/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/attributable-map", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a hit counter as a standalone app.", "homepage": "https://fluidframework.com", @@ -46,9 +46,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/node": "^18.19.0", "eslint": "~8.55.0", diff --git a/examples/apps/collaborative-textarea/CHANGELOG.md b/examples/apps/collaborative-textarea/CHANGELOG.md index 78824bc3e98a..a1ac97f02a4b 100644 --- a/examples/apps/collaborative-textarea/CHANGELOG.md +++ b/examples/apps/collaborative-textarea/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/collaborative-textarea +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/collaborative-textarea/package.json b/examples/apps/collaborative-textarea/package.json index ceca7e1b5710..0172e92933bb 100644 --- a/examples/apps/collaborative-textarea/package.json +++ b/examples/apps/collaborative-textarea/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/collaborative-textarea", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "A minimal example using the react collaborative-textarea", "homepage": "https://fluidframework.com", @@ -59,9 +59,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@fluidframework/test-utils": "workspace:~", diff --git a/examples/apps/contact-collection/CHANGELOG.md b/examples/apps/contact-collection/CHANGELOG.md index 27cf5a549bb4..52dfc2c48d9d 100644 --- a/examples/apps/contact-collection/CHANGELOG.md +++ b/examples/apps/contact-collection/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/contact-collection +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/contact-collection/package.json b/examples/apps/contact-collection/package.json index c90e8a826a96..ee8d67b5e026 100644 --- a/examples/apps/contact-collection/package.json +++ b/examples/apps/contact-collection/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/contact-collection", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example of using a Fluid Object as a collection of items", "homepage": "https://fluidframework.com", @@ -51,9 +51,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/apps/data-object-grid/CHANGELOG.md b/examples/apps/data-object-grid/CHANGELOG.md index b35575967c8c..94dee54bbc02 100644 --- a/examples/apps/data-object-grid/CHANGELOG.md +++ b/examples/apps/data-object-grid/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/data-object-grid +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/data-object-grid/package.json b/examples/apps/data-object-grid/package.json index a7253d7e071d..628443ff6c32 100644 --- a/examples/apps/data-object-grid/package.json +++ b/examples/apps/data-object-grid/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/data-object-grid", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Data object grid creates child data objects from a registry and lays them out in a grid.", "homepage": "https://fluidframework.com", @@ -65,9 +65,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/apps/presence-tracker/.eslintrc.cjs b/examples/apps/presence-tracker/.eslintrc.cjs index 8826fa4dec21..4cbdde7864d5 100644 --- a/examples/apps/presence-tracker/.eslintrc.cjs +++ b/examples/apps/presence-tracker/.eslintrc.cjs @@ -8,5 +8,7 @@ module.exports = { require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), "prettier", ], - rules: {}, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/apps/presence-tracker/CHANGELOG.md b/examples/apps/presence-tracker/CHANGELOG.md index 0f83e7db0c9f..a11ad3e60394 100644 --- a/examples/apps/presence-tracker/CHANGELOG.md +++ b/examples/apps/presence-tracker/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/presence-tracker +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/presence-tracker/package.json b/examples/apps/presence-tracker/package.json index 17bdd1315c44..0e8edbee07da 100644 --- a/examples/apps/presence-tracker/package.json +++ b/examples/apps/presence-tracker/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/presence-tracker", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example Data Object that tracks page focus for Audience members using signals.", "homepage": "https://fluidframework.com", @@ -53,9 +53,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/apps/task-selection/CHANGELOG.md b/examples/apps/task-selection/CHANGELOG.md index a31b598764c6..c20a071c9747 100644 --- a/examples/apps/task-selection/CHANGELOG.md +++ b/examples/apps/task-selection/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/task-selection +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/task-selection/package.json b/examples/apps/task-selection/package.json index 650320a1c591..3ef4ecabd5af 100644 --- a/examples/apps/task-selection/package.json +++ b/examples/apps/task-selection/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/task-selection", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example demonstrating selecting a unique task amongst connected Fluid clients", "homepage": "https://fluidframework.com", @@ -54,9 +54,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/apps/tree-cli-app/.eslintrc.cjs b/examples/apps/tree-cli-app/.eslintrc.cjs new file mode 100644 index 000000000000..f1aaefc67a58 --- /dev/null +++ b/examples/apps/tree-cli-app/.eslintrc.cjs @@ -0,0 +1,11 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +module.exports = { + extends: [require.resolve("@fluidframework/eslint-config-fluid/strict"), "prettier"], + parserOptions: { + project: ["./tsconfig.json", "./src/test/tsconfig.json"], + }, +}; diff --git a/examples/apps/tree-cli-app/.mocharc.cjs b/examples/apps/tree-cli-app/.mocharc.cjs new file mode 100644 index 000000000000..a2bcb0e04316 --- /dev/null +++ b/examples/apps/tree-cli-app/.mocharc.cjs @@ -0,0 +1,14 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +"use strict"; + +const getFluidTestMochaConfig = require("@fluid-internal/mocha-test-setup/mocharc-common"); + +const packageDir = __dirname; +const config = getFluidTestMochaConfig(packageDir); +config.spec = "lib/test"; + +module.exports = config; diff --git a/examples/apps/tree-cli-app/CHANGELOG.md b/examples/apps/tree-cli-app/CHANGELOG.md new file mode 100644 index 000000000000..3e0f99d2bee2 --- /dev/null +++ b/examples/apps/tree-cli-app/CHANGELOG.md @@ -0,0 +1,3 @@ +# @fluid-example/tree-cli-app + +## 2.5.0 diff --git a/examples/apps/tree-cli-app/README.md b/examples/apps/tree-cli-app/README.md new file mode 100644 index 000000000000..ed6caee92a35 --- /dev/null +++ b/examples/apps/tree-cli-app/README.md @@ -0,0 +1,7 @@ +# @fluid-example/tree-cli-app + +Example application using Shared-Tree to create a non-collaborative file editing CLI application. + +Note that it's perfectly possible to write a collaborative online CLI app using tree as well: this simply is not an example of that. + +Run the app with `pnpm run app` after building. diff --git a/examples/apps/tree-cli-app/biome.jsonc b/examples/apps/tree-cli-app/biome.jsonc new file mode 100644 index 000000000000..4b65e1c0aea2 --- /dev/null +++ b/examples/apps/tree-cli-app/biome.jsonc @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "extends": ["../../../biome.jsonc"] +} diff --git a/examples/apps/tree-cli-app/data/default.compressed.json b/examples/apps/tree-cli-app/data/default.compressed.json new file mode 100644 index 000000000000..44cb937d5b02 --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.compressed.json @@ -0,0 +1 @@ +{"version":1,"identifiers":[],"shapes":[{"c":{"type":"com.fluidframework.leaf.number","value":true}},{"c":{"type":"com.fluidframework.example.cli.List","value":false,"fields":[["",5]]}},{"c":{"type":"com.fluidframework.example.cli.Item","value":false,"fields":[["location",3],["name",4]]}},{"c":{"type":"com.fluidframework.example.cli.Point","value":false,"fields":[["x",0],["y",0]]}},{"c":{"type":"com.fluidframework.leaf.string","value":true}},{"a":6},{"d":0}],"data":[[1,[2,0,0,"default"]]]} \ No newline at end of file diff --git a/examples/apps/tree-cli-app/data/default.concise-stored.json b/examples/apps/tree-cli-app/data/default.concise-stored.json new file mode 100644 index 000000000000..8e244f381d82 --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.concise-stored.json @@ -0,0 +1 @@ +[{"location":{"x":0,"y":0},"name":"default"}] \ No newline at end of file diff --git a/examples/apps/tree-cli-app/data/default.concise.json b/examples/apps/tree-cli-app/data/default.concise.json new file mode 100644 index 000000000000..99686a35907c --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.concise.json @@ -0,0 +1 @@ +[{"position":{"x":0,"y":0},"name":"default"}] \ No newline at end of file diff --git a/examples/apps/tree-cli-app/data/default.snapshot.json b/examples/apps/tree-cli-app/data/default.snapshot.json new file mode 100644 index 000000000000..df15f5e777ba --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.snapshot.json @@ -0,0 +1 @@ +{"tree":{"version":1,"identifiers":[],"shapes":[{"c":{"type":"com.fluidframework.leaf.number","value":true}},{"c":{"type":"com.fluidframework.example.cli.List","value":false,"fields":[["",5]]}},{"c":{"type":"com.fluidframework.example.cli.Item","value":false,"fields":[["location",3],["name",4]]}},{"c":{"type":"com.fluidframework.example.cli.Point","value":false,"fields":[["x",0],["y",0]]}},{"c":{"type":"com.fluidframework.leaf.string","value":true}},{"a":6},{"d":0}],"data":[[1,[2,0,0,"default"]]]},"schema":{"version":1,"nodes":{"com.fluidframework.example.cli.Item":{"object":{"location":{"kind":"Value","types":["com.fluidframework.example.cli.Point"]},"name":{"kind":"Value","types":["com.fluidframework.leaf.string"]}}},"com.fluidframework.example.cli.List":{"object":{"":{"kind":"Sequence","types":["com.fluidframework.example.cli.Item","com.fluidframework.leaf.string"]}}},"com.fluidframework.example.cli.Point":{"object":{"x":{"kind":"Value","types":["com.fluidframework.leaf.number"]},"y":{"kind":"Value","types":["com.fluidframework.leaf.number"]}}},"com.fluidframework.leaf.number":{"leaf":0},"com.fluidframework.leaf.string":{"leaf":1}},"root":{"kind":"Value","types":["com.fluidframework.example.cli.List"]}},"idCompressor":"AAAAAAAAAEAAAAAAAADwPwAAAAAAAPA/AAAAAAAAAAAnHwqpeTHgaoAxFokW+gsBAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAA"} \ No newline at end of file diff --git a/examples/apps/tree-cli-app/data/default.verbose-stored.json b/examples/apps/tree-cli-app/data/default.verbose-stored.json new file mode 100644 index 000000000000..e9c6e1cc0fc1 --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.verbose-stored.json @@ -0,0 +1 @@ +{"type":"com.fluidframework.example.cli.List","fields":[{"type":"com.fluidframework.example.cli.Item","fields":{"location":{"type":"com.fluidframework.example.cli.Point","fields":{"x":0,"y":0}},"name":"default"}}]} \ No newline at end of file diff --git a/examples/apps/tree-cli-app/data/default.verbose.json b/examples/apps/tree-cli-app/data/default.verbose.json new file mode 100644 index 000000000000..cdc453db50f4 --- /dev/null +++ b/examples/apps/tree-cli-app/data/default.verbose.json @@ -0,0 +1 @@ +{"type":"com.fluidframework.example.cli.List","fields":[{"type":"com.fluidframework.example.cli.Item","fields":{"position":{"type":"com.fluidframework.example.cli.Point","fields":{"x":0,"y":0}},"name":"default"}}]} \ No newline at end of file diff --git a/examples/apps/tree-cli-app/package.json b/examples/apps/tree-cli-app/package.json new file mode 100644 index 000000000000..83df78691095 --- /dev/null +++ b/examples/apps/tree-cli-app/package.json @@ -0,0 +1,58 @@ +{ + "name": "@fluid-example/tree-cli-app", + "version": "2.10.0", + "private": true, + "description": "SharedTree CLI app demo", + "homepage": "https://fluidframework.com", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/FluidFramework.git", + "directory": "examples/apps/tree-cli-app" + }, + "license": "MIT", + "author": "Microsoft and contributors", + "type": "module", + "scripts": { + "app": "node ./lib/index.js", + "build": "fluid-build . --task build", + "build:compile": "fluid-build . --task compile", + "build:esnext": "tsc --project ./tsconfig.json", + "build:test": "npm run build:test:esm", + "build:test:esm": "tsc --project ./src/test/tsconfig.json", + "check:biome": "biome check .", + "check:format": "npm run check:biome", + "clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\" nyc", + "eslint": "eslint --format stylish src", + "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout", + "format": "npm run format:biome", + "format:biome": "biome check . --write", + "lint": "fluid-build . --task lint", + "lint:fix": "fluid-build . --task eslint:fix --task format", + "test": "npm run test:mocha", + "test:mocha": "npm run test:mocha:esm", + "test:mocha:esm": "mocha", + "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha" + }, + "dependencies": { + "@fluidframework/core-interfaces": "workspace:~", + "@fluidframework/id-compressor": "workspace:~", + "@fluidframework/runtime-utils": "workspace:~", + "@fluidframework/tree": "workspace:~", + "@sinclair/typebox": "^0.32.29" + }, + "devDependencies": { + "@biomejs/biome": "~1.9.3", + "@fluid-internal/mocha-test-setup": "workspace:~", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/eslint-config-fluid": "^5.4.0", + "@types/mocha": "^9.1.1", + "@types/node": "^18.19.0", + "cross-env": "^7.0.3", + "eslint": "~8.55.0", + "mocha": "^10.2.0", + "mocha-json-output-reporter": "^2.0.1", + "mocha-multi-reporters": "^1.5.1", + "rimraf": "^4.4.0", + "typescript": "~5.4.5" + } +} diff --git a/examples/apps/tree-cli-app/src/index.ts b/examples/apps/tree-cli-app/src/index.ts new file mode 100644 index 000000000000..a1a44a14ac42 --- /dev/null +++ b/examples/apps/tree-cli-app/src/index.ts @@ -0,0 +1,44 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +// This is a node powered CLI application, so using node makes sense: +/* eslint-disable unicorn/no-process-exit */ + +import { applyEdit, loadDocument, saveDocument } from "./utils.js"; + +const args = process.argv.slice(2); + +console.log(`Requires arguments: [] [] []`); +console.log(); +console.log( + `Example to load the default tree, insert 10 strings and 100 items, and save the result in the concise format:`, +); +console.log(`default data/large.concise.json string:10,item:100`); +console.log(); +console.log(`Example to load data/large.concise.json, and log it to the console:`); +console.log(`data/large.concise.json`); +console.log(); +console.log( + `File formats are specified by extension, for example ".verbose.json" uses the "verbose" format.`, +); +console.log( + `See implementation for supported formats and edit syntax: this is just a demo, not a nice app!`, +); +console.log(); +console.log(`Running with augments: ${args}`); + +if (args.length > 3) { + process.exit(1); +} + +const [sourceArg, destinationArg, editArg] = args; + +const node = loadDocument(sourceArg); + +if (editArg !== undefined) { + applyEdit(editArg, node); +} + +saveDocument(destinationArg, node); diff --git a/examples/apps/tree-cli-app/src/schema.ts b/examples/apps/tree-cli-app/src/schema.ts new file mode 100644 index 000000000000..94da744852c1 --- /dev/null +++ b/examples/apps/tree-cli-app/src/schema.ts @@ -0,0 +1,38 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { SchemaFactory, TreeViewConfiguration } from "@fluidframework/tree"; + +/** + * The SchemaFactory. + */ +export const schemaBuilder = new SchemaFactory("com.fluidframework.example.cli"); + +class Point extends schemaBuilder.object("Point", { + x: schemaBuilder.number, + y: schemaBuilder.number, +}) {} + +/** + * Complex list item. + */ +export class Item extends schemaBuilder.object("Item", { + position: schemaBuilder.required(Point, { key: "location" }), + name: schemaBuilder.string, +}) {} + +/** + * List node. + */ +export class List extends schemaBuilder.array("List", [schemaBuilder.string, Item]) {} + +/** + * Tree configuration. + */ +export const config = new TreeViewConfiguration({ + schema: List, + enableSchemaValidation: true, + preventAmbiguity: true, +}); diff --git a/examples/apps/tree-cli-app/src/test/schema.spec.ts b/examples/apps/tree-cli-app/src/test/schema.spec.ts new file mode 100644 index 000000000000..c31245d6b653 --- /dev/null +++ b/examples/apps/tree-cli-app/src/test/schema.spec.ts @@ -0,0 +1,144 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { + comparePersistedSchema, + extractPersistedSchema, + typeboxValidator, + type ForestOptions, + type ICodecOptions, + type JsonCompatible, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/alpha"; + +import { List } from "../schema.js"; + +// This file demonstrates how applications can write tests which ensure they maintain compatibility with the schema from previously released versions. + +describe("schema", () => { + it("current schema matches latest historical schema", () => { + const current = extractPersistedSchema(List); + + // For compatibility with deep equality and simple objects, round trip via JSON to erase prototypes. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const currentRoundTripped: JsonCompatible = JSON.parse(JSON.stringify(current)); + + const previous = historicalSchema.at(-1); + assert(previous !== undefined); + // This ensures that historicalSchema's last entry is up to date with the current application code. + // This can catch: + // 1. Forgetting to update historicalSchema when intentionally making schema changes. + // 2. Accidentally changing schema in a way that impacts document compatibility. + assert.deepEqual(currentRoundTripped, previous.schema); + }); + + it("historical schema can be upgraded to current schema", () => { + const options: ForestOptions & ICodecOptions = { jsonValidator: typeboxValidator }; + + for (let documentIndex = 0; documentIndex < historicalSchema.length; documentIndex++) { + for (let viewIndex = 0; viewIndex < historicalSchema.length; viewIndex++) { + const compat = comparePersistedSchema( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + historicalSchema[documentIndex]!.schema, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + historicalSchema[viewIndex]!.schema, + options, + false, + ); + + // We do not expect duplicates in historicalSchema. + assert.equal(compat.isEquivalent, documentIndex === viewIndex); + // Currently collaboration is only allowed between identical versions + assert.equal(compat.canView, documentIndex === viewIndex); + // Older versions should be upgradable to newer versions, but not the reverse. + assert.equal(compat.canUpgrade, documentIndex <= viewIndex); + } + } + }); +}); + +/** + * List of schema from previous versions of this application. + * Storing these as .json files in a folder may make more sense for more complex applications. + * + * The `schema` field is generated by passing the schema to `extractPersistedSchema`. + */ +const historicalSchema: { version: string; schema: JsonCompatible }[] = [ + { + version: "1.0", + schema: { + version: 1, + nodes: { + "com.fluidframework.example.cli.List": { + object: { + "": { + kind: "Sequence", + types: ["com.fluidframework.leaf.string"], + }, + }, + }, + "com.fluidframework.leaf.string": { + leaf: 1, + }, + }, + root: { + kind: "Value", + types: ["com.fluidframework.example.cli.List"], + }, + }, + }, + { + version: "2.0", + schema: { + version: 1, + nodes: { + "com.fluidframework.example.cli.Item": { + object: { + location: { + kind: "Value", + types: ["com.fluidframework.example.cli.Point"], + }, + name: { + kind: "Value", + types: ["com.fluidframework.leaf.string"], + }, + }, + }, + "com.fluidframework.example.cli.List": { + object: { + "": { + kind: "Sequence", + types: ["com.fluidframework.example.cli.Item", "com.fluidframework.leaf.string"], + }, + }, + }, + "com.fluidframework.example.cli.Point": { + object: { + x: { + kind: "Value", + types: ["com.fluidframework.leaf.number"], + }, + y: { + kind: "Value", + types: ["com.fluidframework.leaf.number"], + }, + }, + }, + "com.fluidframework.leaf.number": { + leaf: 0, + }, + "com.fluidframework.leaf.string": { + leaf: 1, + }, + }, + root: { + kind: "Value", + types: ["com.fluidframework.example.cli.List"], + }, + }, + }, +]; diff --git a/examples/apps/tree-cli-app/src/test/tsconfig.json b/examples/apps/tree-cli-app/src/test/tsconfig.json new file mode 100644 index 000000000000..acbd99a2893e --- /dev/null +++ b/examples/apps/tree-cli-app/src/test/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../../common/build/build-common/tsconfig.test.node16.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "../../lib/test", + "types": ["mocha", "node"], + // Allows writing type checking expression without having to use the results. + "noUnusedLocals": false, + // Allow testing that declarations work properly + "declaration": true, + // Needed to ensure testExport's produce a valid d.ts + "skipLibCheck": false, + // Due to several of our own packages' exports failing to build with "exactOptionalPropertyTypes", + // disable it to prevent that from erroring when combined with "skipLibCheck". + "exactOptionalPropertyTypes": false, + }, + "include": ["./**/*"], + "references": [ + { + "path": "../..", + }, + ], +} diff --git a/examples/apps/tree-cli-app/src/utils.ts b/examples/apps/tree-cli-app/src/utils.ts new file mode 100644 index 000000000000..64e32b58f27c --- /dev/null +++ b/examples/apps/tree-cli-app/src/utils.ts @@ -0,0 +1,249 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +// This is a node powered CLI application, so using node makes sense: +/* eslint-disable unicorn/no-process-exit */ +/* eslint-disable import/no-nodejs-modules */ + +import { readFileSync, writeFileSync } from "node:fs"; + +import type { IFluidHandle } from "@fluidframework/core-interfaces"; +import type { SerializedIdCompressorWithOngoingSession } from "@fluidframework/id-compressor/internal"; +import { + createIdCompressor, + deserializeIdCompressor, +} from "@fluidframework/id-compressor/internal"; +import { isFluidHandle } from "@fluidframework/runtime-utils"; +import { TreeArrayNode, type InsertableTypedNode } from "@fluidframework/tree"; +import { + extractPersistedSchema, + FluidClientVersion, + independentInitializedView, + typeboxValidator, + type ForestOptions, + type ICodecOptions, + type JsonCompatible, + type VerboseTree, + type ViewContent, + type ConciseTree, + TreeAlpha, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/alpha"; +import { type Static, Type } from "@sinclair/typebox"; + +import type { Item } from "./schema.js"; +import { config, List } from "./schema.js"; + +/** + * Examples showing how to import data in a variety of formats. + * + * @param source - What data to load. + * If "default" or `undefined` data will come from a hard coded small default tree. + * Otherwise assumed to be a file path ending in a file matching `*.FORMAT.json` where format defines how to parse the file. + * See implementation for supported formats and how they are encoded. + */ +export function loadDocument(source: string | undefined): List { + if (source === undefined || source === "default") { + return new List([{ name: "default", position: { x: 0, y: 0 } }]); + } + const parts = source.split("."); + if (parts.length < 3 || parts.at(-1) !== "json") { + console.log(`Invalid source: ${source}`); + process.exit(1); + } + + // Data parsed from JSON is safe to consider JsonCompatible. + // If file is invalid JSON, that will throw and is fine for this app. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const fileData: JsonCompatible = JSON.parse(readFileSync(source).toString()); + + switch (parts.at(-2)) { + case "concise": { + return TreeAlpha.importConcise(List, fileData as ConciseTree); + } + case "verbose": { + return TreeAlpha.importVerbose(List, fileData as VerboseTree); + } + case "verbose-stored": { + return TreeAlpha.importVerbose(List, fileData as VerboseTree, { + useStoredKeys: true, + }); + } + case "compressed": { + return TreeAlpha.importCompressed(List, fileData, { jsonValidator: typeboxValidator }); + } + case "snapshot": { + // TODO: This should probably do a validating parse of the data (probably using type box) rather than just casting it. + const combo: File = fileData as File; + + const content: ViewContent = { + schema: combo.schema, + tree: combo.tree, + idCompressor: deserializeIdCompressor(combo.idCompressor), + }; + const view = independentInitializedView(config, options, content); + return view.root; + } + default: { + console.log(`Invalid source format: ${parts.at(-2)}`); + process.exit(1); + } + } +} + +/** + * Examples showing how to export data in a variety of formats. + * + * @param destination - Where to save the data, and in what format. + * If `undefined` data will logged to the console. + * Otherwise see {@link exportContent}. + */ +export function saveDocument(destination: string | undefined, tree: List): void { + if (destination === undefined || destination === "default") { + console.log("Tree Content:"); + console.log(tree); + return; + } + const parts = destination.split("."); + if (parts.length < 3 || parts.at(-1) !== "json") { + console.log(`Invalid destination: ${destination}`); + process.exit(1); + } + + const fileData: JsonCompatible = exportContent(destination, tree); + console.log(`Writing: ${destination}`); + writeFileSync(destination, JSON.stringify(fileData, rejectHandles)); +} + +/** + * Examples showing how to export data in a variety of formats. + * + * @param destination - File path used to select the format. + * Assumed to be a file path ending in a file matching `*.FORMAT.json` where format defines how to parse the file. + * See implementation for supported formats and how they are encoded. + */ +export function exportContent(destination: string, tree: List): JsonCompatible { + const parts = destination.split("."); + if (parts.length < 3 || parts.at(-1) !== "json") { + console.log(`Invalid destination: ${destination}`); + process.exit(1); + } + + switch (parts.at(-2)) { + case "concise": { + return TreeAlpha.exportConcise(tree) as JsonCompatible; + } + case "verbose": { + return TreeAlpha.exportVerbose(tree) as JsonCompatible; + } + case "concise-stored": { + return TreeAlpha.exportConcise(tree, { useStoredKeys: true }) as JsonCompatible; + } + case "verbose-stored": { + return TreeAlpha.exportVerbose(tree, { useStoredKeys: true }) as JsonCompatible; + } + case "compressed": { + return TreeAlpha.exportCompressed(tree, { + ...options, + oldestCompatibleClient: FluidClientVersion.v2_3, + }) as JsonCompatible; + } + case "snapshot": { + // TODO: This should be made better. See privateRemarks on TreeAlpha.exportCompressed. + const idCompressor = createIdCompressor(); + const file: File = { + tree: TreeAlpha.exportCompressed(tree, { + oldestCompatibleClient: FluidClientVersion.v2_3, + idCompressor, + }), + schema: extractPersistedSchema(List), + idCompressor: idCompressor.serialize(true), + }; + return file as JsonCompatible; + } + default: { + console.log(`Invalid source format: ${parts.at(-2)}`); + process.exit(1); + } + } +} + +/** + * Example of editing a tree. + * This allows for some basic editing of `list` sufficient to add and remove items to create trees of different sizes. + * + * This interprets `edits` as a comma separated list of edits to apply to `tree`. + * + * Each edit is in the format `kind:count`. + * Count is a number and indicates how many nodes to add when positive, and how many to remove when negative. + * + * Positive numbers have valid kinds of "string" and "item" to insert that many strings or items to `list`. + * Negative numbers have valid kinds of "start" or "end" to indicate if the items should be removed from the start or end of `list`. + */ +export function applyEdit(edits: string, list: List): void { + for (const edit of edits.split(",")) { + console.log(`Applying edit ${edit}`); + const parts = edit.split(":"); + if (parts.length !== 2) { + throw new Error(`Invalid edit ${edit}`); + } + const [kind, countString] = parts; + const count = Number(countString); + if (count === 0 || !Number.isInteger(count)) { + throw new TypeError(`Invalid count in edit ${edit}`); + } + if (count > 0) { + let data: InsertableTypedNode | string; + switch (kind) { + case "string": { + data = "x"; + break; + } + case "item": { + data = { position: { x: 0, y: 0 }, name: "item" }; + break; + } + default: { + throw new TypeError(`Invalid kind in insert edit ${edit}`); + } + } + // eslint-disable-next-line unicorn/no-new-array + list.insertAtEnd(TreeArrayNode.spread(new Array(count).fill(data))); + } else { + switch (kind) { + case "start": { + list.removeRange(0, -count); + break; + } + case "end": { + list.removeRange(list.length + count, -count); + break; + } + default: { + throw new TypeError(`Invalid end in remove edit ${edit}`); + } + } + } + } +} + +/** + * Throw if handle. + */ +export function rejectHandles(key: string, value: unknown): unknown { + if (isFluidHandle(value)) { + throw new Error("Fluid handles are not supported"); + } + return value; +} + +const options: ForestOptions & ICodecOptions = { jsonValidator: typeboxValidator }; + +const File = Type.Object({ + tree: Type.Unsafe>(), + schema: Type.Unsafe(), + idCompressor: Type.Unsafe(), +}); +type File = Static; diff --git a/examples/apps/tree-cli-app/tsconfig.json b/examples/apps/tree-cli-app/tsconfig.json new file mode 100644 index 000000000000..55e7ad09857c --- /dev/null +++ b/examples/apps/tree-cli-app/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../common/build/build-common/tsconfig.node16.json", + "include": ["src/**/*"], + "exclude": ["src/test/**/*"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "exactOptionalPropertyTypes": false, + "noUnusedLocals": false, + "types": ["node"], + }, +} diff --git a/examples/apps/tree-comparison/.eslintrc.cjs b/examples/apps/tree-comparison/.eslintrc.cjs index 8826fa4dec21..4cbdde7864d5 100644 --- a/examples/apps/tree-comparison/.eslintrc.cjs +++ b/examples/apps/tree-comparison/.eslintrc.cjs @@ -8,5 +8,7 @@ module.exports = { require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), "prettier", ], - rules: {}, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/apps/tree-comparison/CHANGELOG.md b/examples/apps/tree-comparison/CHANGELOG.md index c56e799f165e..c095845c9726 100644 --- a/examples/apps/tree-comparison/CHANGELOG.md +++ b/examples/apps/tree-comparison/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/tree-comparison +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/apps/tree-comparison/package.json b/examples/apps/tree-comparison/package.json index 6b13e65418b7..049a25a77553 100644 --- a/examples/apps/tree-comparison/package.json +++ b/examples/apps/tree-comparison/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/tree-comparison", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Comparing API usage in legacy SharedTree and new SharedTree.", "homepage": "https://fluidframework.com", @@ -60,9 +60,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/benchmarks/bubblebench/baseline/CHANGELOG.md b/examples/benchmarks/bubblebench/baseline/CHANGELOG.md index a676341cd47c..8d04203f7bce 100644 --- a/examples/benchmarks/bubblebench/baseline/CHANGELOG.md +++ b/examples/benchmarks/bubblebench/baseline/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bubblebench-baseline +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/bubblebench/baseline/package.json b/examples/benchmarks/bubblebench/baseline/package.json index 2239b8039a3c..adca027f2529 100644 --- a/examples/benchmarks/bubblebench/baseline/package.json +++ b/examples/benchmarks/bubblebench/baseline/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-baseline", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", @@ -54,7 +54,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/benchmarks/bubblebench/common/CHANGELOG.md b/examples/benchmarks/bubblebench/common/CHANGELOG.md index f55dd4c062b0..db5430c38dd9 100644 --- a/examples/benchmarks/bubblebench/common/CHANGELOG.md +++ b/examples/benchmarks/bubblebench/common/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bubblebench-common +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/bubblebench/common/package.json b/examples/benchmarks/bubblebench/common/package.json index aa18df657844..732749ac9ce5 100644 --- a/examples/benchmarks/bubblebench/common/package.json +++ b/examples/benchmarks/bubblebench/common/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-common", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", @@ -52,9 +52,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", diff --git a/examples/benchmarks/bubblebench/experimental-tree/.eslintrc.cjs b/examples/benchmarks/bubblebench/experimental-tree/.eslintrc.cjs index 7d5c060de0a8..2b16f5490eff 100644 --- a/examples/benchmarks/bubblebench/experimental-tree/.eslintrc.cjs +++ b/examples/benchmarks/bubblebench/experimental-tree/.eslintrc.cjs @@ -12,5 +12,6 @@ module.exports = { // TODO: AB#18875 - Re-enable react/no-deprecated once we replace uses of the deprecated ReactDOM.render() // with the new React 18 createRoot(). "react/no-deprecated": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/examples/benchmarks/bubblebench/experimental-tree/CHANGELOG.md b/examples/benchmarks/bubblebench/experimental-tree/CHANGELOG.md index 28b4a3819d8b..68bde9666914 100644 --- a/examples/benchmarks/bubblebench/experimental-tree/CHANGELOG.md +++ b/examples/benchmarks/bubblebench/experimental-tree/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bubblebench-experimental-tree +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/bubblebench/experimental-tree/package.json b/examples/benchmarks/bubblebench/experimental-tree/package.json index 9efd6b55bb4c..a15a9d078f1f 100644 --- a/examples/benchmarks/bubblebench/experimental-tree/package.json +++ b/examples/benchmarks/bubblebench/experimental-tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-experimental-tree", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", @@ -55,7 +55,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/benchmarks/bubblebench/ot/.eslintrc.cjs b/examples/benchmarks/bubblebench/ot/.eslintrc.cjs index 69dd265570ae..6003c9eca380 100644 --- a/examples/benchmarks/bubblebench/ot/.eslintrc.cjs +++ b/examples/benchmarks/bubblebench/ot/.eslintrc.cjs @@ -9,5 +9,6 @@ module.exports = { // TODO: AB#18875 - Re-enable react/no-deprecated once we replace uses of the deprecated ReactDOM.render() // with the new React 18 createRoot(). "react/no-deprecated": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/examples/benchmarks/bubblebench/ot/CHANGELOG.md b/examples/benchmarks/bubblebench/ot/CHANGELOG.md index a9eeea6907e7..86ae14b936d9 100644 --- a/examples/benchmarks/bubblebench/ot/CHANGELOG.md +++ b/examples/benchmarks/bubblebench/ot/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bubblebench-ot +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/bubblebench/ot/package.json b/examples/benchmarks/bubblebench/ot/package.json index 32fc04d382dc..ec3ea08fd312 100644 --- a/examples/benchmarks/bubblebench/ot/package.json +++ b/examples/benchmarks/bubblebench/ot/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-ot", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", @@ -56,7 +56,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/benchmarks/bubblebench/shared-tree/CHANGELOG.md b/examples/benchmarks/bubblebench/shared-tree/CHANGELOG.md index af75dcaef4ee..6b937f01a55f 100644 --- a/examples/benchmarks/bubblebench/shared-tree/CHANGELOG.md +++ b/examples/benchmarks/bubblebench/shared-tree/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bubblebench-simple-tree +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/bubblebench/shared-tree/package.json b/examples/benchmarks/bubblebench/shared-tree/package.json index 8cac7a31d9c8..34c4d3f516fd 100644 --- a/examples/benchmarks/bubblebench/shared-tree/package.json +++ b/examples/benchmarks/bubblebench/shared-tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-shared-tree", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", @@ -63,7 +63,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/benchmarks/odspsnapshotfetch-perftestapp/CHANGELOG.md b/examples/benchmarks/odspsnapshotfetch-perftestapp/CHANGELOG.md index a18d3d392771..5793567df058 100644 --- a/examples/benchmarks/odspsnapshotfetch-perftestapp/CHANGELOG.md +++ b/examples/benchmarks/odspsnapshotfetch-perftestapp/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/odspsnapshotfetch-perftestapp +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json b/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json index 580484ce617b..1f9bebbbdd51 100644 --- a/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json +++ b/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/odspsnapshotfetch-perftestapp", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Benchmark binary vs. json download", "homepage": "https://fluidframework.com", @@ -48,9 +48,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/express": "^4.17.21", "@types/fs-extra": "^9.0.11", diff --git a/examples/benchmarks/tablebench/.eslintrc.cjs b/examples/benchmarks/tablebench/.eslintrc.cjs index 3514825ac914..c489a4dd5f2d 100644 --- a/examples/benchmarks/tablebench/.eslintrc.cjs +++ b/examples/benchmarks/tablebench/.eslintrc.cjs @@ -11,4 +11,7 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/benchmarks/tablebench/CHANGELOG.md b/examples/benchmarks/tablebench/CHANGELOG.md index 3c034729a0d7..e4c5186ce1be 100644 --- a/examples/benchmarks/tablebench/CHANGELOG.md +++ b/examples/benchmarks/tablebench/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/tablebench +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/benchmarks/tablebench/package.json b/examples/benchmarks/tablebench/package.json index 10dccd5abdab..005b196e13ec 100644 --- a/examples/benchmarks/tablebench/package.json +++ b/examples/benchmarks/tablebench/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/tablebench", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Table focused benchmarks", "homepage": "https://fluidframework.com", @@ -65,7 +65,7 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/id-compressor": "workspace:~", "@types/mocha": "^9.1.1", diff --git a/examples/client-logger/app-insights-logger/CHANGELOG.md b/examples/client-logger/app-insights-logger/CHANGELOG.md index 36ca75710753..a57d43172d44 100644 --- a/examples/client-logger/app-insights-logger/CHANGELOG.md +++ b/examples/client-logger/app-insights-logger/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-insights-logger +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/client-logger/app-insights-logger/package.json b/examples/client-logger/app-insights-logger/package.json index a9059fd2eac8..ef0a3b3e6cf8 100644 --- a/examples/client-logger/app-insights-logger/package.json +++ b/examples/client-logger/app-insights-logger/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-insights-logger", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Provides a simple Fluid application with a UI view written in React to test the Fluid App Insights telemetry logger that will route typical Fluid telemetry to configured Azure App Insights", "homepage": "https://fluidframework.com", @@ -57,7 +57,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^5.16.5", diff --git a/examples/data-objects/canvas/CHANGELOG.md b/examples/data-objects/canvas/CHANGELOG.md index a50520a09905..5c87a117102c 100644 --- a/examples/data-objects/canvas/CHANGELOG.md +++ b/examples/data-objects/canvas/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/canvas +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/canvas/package.json b/examples/data-objects/canvas/package.json index dfe5eb643a19..28cadb04b367 100644 --- a/examples/data-objects/canvas/package.json +++ b/examples/data-objects/canvas/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/canvas", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Fluid ink canvas", "homepage": "https://fluidframework.com", @@ -51,7 +51,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/data-objects/clicker/CHANGELOG.md b/examples/data-objects/clicker/CHANGELOG.md index 0cfb77cfa586..7a50925cfa94 100644 --- a/examples/data-objects/clicker/CHANGELOG.md +++ b/examples/data-objects/clicker/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/clicker +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/clicker/package.json b/examples/data-objects/clicker/package.json index a232417fd410..c39840444453 100644 --- a/examples/data-objects/clicker/package.json +++ b/examples/data-objects/clicker/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/clicker", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid component sample to implement a collaborative counter.", "homepage": "https://fluidframework.com", @@ -63,7 +63,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@fluidframework/test-utils": "workspace:~", diff --git a/examples/data-objects/codemirror/.eslintrc.cjs b/examples/data-objects/codemirror/.eslintrc.cjs index fcae210b6902..a8f260775d3c 100644 --- a/examples/data-objects/codemirror/.eslintrc.cjs +++ b/examples/data-objects/codemirror/.eslintrc.cjs @@ -10,5 +10,6 @@ module.exports = { ], rules: { "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/examples/data-objects/codemirror/CHANGELOG.md b/examples/data-objects/codemirror/CHANGELOG.md index f0640a0b14fa..cb482cb28ec0 100644 --- a/examples/data-objects/codemirror/CHANGELOG.md +++ b/examples/data-objects/codemirror/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/codemirror +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/codemirror/package.json b/examples/data-objects/codemirror/package.json index 3843f2c1376a..d21168cf8e19 100644 --- a/examples/data-objects/codemirror/package.json +++ b/examples/data-objects/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/codemirror", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", @@ -70,7 +70,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/codemirror": "5.60.7", "@types/node": "^18.19.0", diff --git a/examples/data-objects/diceroller/CHANGELOG.md b/examples/data-objects/diceroller/CHANGELOG.md index 628d2ee62348..91c8b2c14c58 100644 --- a/examples/data-objects/diceroller/CHANGELOG.md +++ b/examples/data-objects/diceroller/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/diceroller +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/diceroller/package.json b/examples/data-objects/diceroller/package.json index 4ae17a75df28..e2f116c5167f 100644 --- a/examples/data-objects/diceroller/package.json +++ b/examples/data-objects/diceroller/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/diceroller", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid Container & Object sample to implement a collaborative dice roller.", "homepage": "https://fluidframework.com", @@ -50,7 +50,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/data-objects/inventory-app/CHANGELOG.md b/examples/data-objects/inventory-app/CHANGELOG.md index 9369c3cdc261..f9e16a9c0a85 100644 --- a/examples/data-objects/inventory-app/CHANGELOG.md +++ b/examples/data-objects/inventory-app/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/inventory-app +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/inventory-app/package.json b/examples/data-objects/inventory-app/package.json index a74ef0274234..1e53bcf79464 100644 --- a/examples/data-objects/inventory-app/package.json +++ b/examples/data-objects/inventory-app/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/inventory-app", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal sample of SharedTree/React integration.", "homepage": "https://fluidframework.com", @@ -54,7 +54,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/data-objects/monaco/CHANGELOG.md b/examples/data-objects/monaco/CHANGELOG.md index 23bb54000417..9d710ce4204b 100644 --- a/examples/data-objects/monaco/CHANGELOG.md +++ b/examples/data-objects/monaco/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/monaco +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/monaco/package.json b/examples/data-objects/monaco/package.json index 260d9ea500cc..af9e89b49ac1 100644 --- a/examples/data-objects/monaco/package.json +++ b/examples/data-objects/monaco/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/monaco", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Monaco code editor", "homepage": "https://fluidframework.com", @@ -52,7 +52,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "css-loader": "^7.1.2", diff --git a/examples/data-objects/multiview/constellation-model/CHANGELOG.md b/examples/data-objects/multiview/constellation-model/CHANGELOG.md index 0f1b7a1981c3..e15ce0eaddcf 100644 --- a/examples/data-objects/multiview/constellation-model/CHANGELOG.md +++ b/examples/data-objects/multiview/constellation-model/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-constellation-model +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/constellation-model/package.json b/examples/data-objects/multiview/constellation-model/package.json index 0da0df035ade..17dc863f4218 100644 --- a/examples/data-objects/multiview/constellation-model/package.json +++ b/examples/data-objects/multiview/constellation-model/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-constellation-model", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Constellation model for multiview sample", "homepage": "https://fluidframework.com", @@ -48,7 +48,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "eslint": "~8.55.0", "prettier": "~3.0.3", diff --git a/examples/data-objects/multiview/constellation-view/CHANGELOG.md b/examples/data-objects/multiview/constellation-view/CHANGELOG.md index e4b5913f1a85..32d308022717 100644 --- a/examples/data-objects/multiview/constellation-view/CHANGELOG.md +++ b/examples/data-objects/multiview/constellation-view/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-constellation-view +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/constellation-view/package.json b/examples/data-objects/multiview/constellation-view/package.json index 9b55a7f30243..70d0e8109ade 100644 --- a/examples/data-objects/multiview/constellation-view/package.json +++ b/examples/data-objects/multiview/constellation-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-constellation-view", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", @@ -47,7 +47,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "copyfiles": "^2.4.1", diff --git a/examples/data-objects/multiview/container/CHANGELOG.md b/examples/data-objects/multiview/container/CHANGELOG.md index e09c994c6eef..a6fcbdbb4023 100644 --- a/examples/data-objects/multiview/container/CHANGELOG.md +++ b/examples/data-objects/multiview/container/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-container +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/container/package.json b/examples/data-objects/multiview/container/package.json index e4328190c545..0537ec2ba0ee 100644 --- a/examples/data-objects/multiview/container/package.json +++ b/examples/data-objects/multiview/container/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-container", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Container for multiview sample", "homepage": "https://fluidframework.com", @@ -62,7 +62,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/data-objects/multiview/coordinate-model/CHANGELOG.md b/examples/data-objects/multiview/coordinate-model/CHANGELOG.md index c580057fe553..2690d94bb2e5 100644 --- a/examples/data-objects/multiview/coordinate-model/CHANGELOG.md +++ b/examples/data-objects/multiview/coordinate-model/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-coordinate-model +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/coordinate-model/package.json b/examples/data-objects/multiview/coordinate-model/package.json index da187c504705..e54e500f3038 100644 --- a/examples/data-objects/multiview/coordinate-model/package.json +++ b/examples/data-objects/multiview/coordinate-model/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-coordinate-model", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Coordinate model for multiview sample", "homepage": "https://fluidframework.com", @@ -46,7 +46,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "eslint": "~8.55.0", "prettier": "~3.0.3", diff --git a/examples/data-objects/multiview/interface/CHANGELOG.md b/examples/data-objects/multiview/interface/CHANGELOG.md index a36e3bc7c966..9f879bbc5903 100644 --- a/examples/data-objects/multiview/interface/CHANGELOG.md +++ b/examples/data-objects/multiview/interface/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-coordinate-interface +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/interface/package.json b/examples/data-objects/multiview/interface/package.json index 516c9d198192..18fa578782af 100644 --- a/examples/data-objects/multiview/interface/package.json +++ b/examples/data-objects/multiview/interface/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-coordinate-interface", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Interface for multiview sample", "homepage": "https://fluidframework.com", @@ -43,9 +43,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "eslint": "~8.55.0", "prettier": "~3.0.3", diff --git a/examples/data-objects/multiview/plot-coordinate-view/CHANGELOG.md b/examples/data-objects/multiview/plot-coordinate-view/CHANGELOG.md index c9a5815a2bb6..96d5307e8628 100644 --- a/examples/data-objects/multiview/plot-coordinate-view/CHANGELOG.md +++ b/examples/data-objects/multiview/plot-coordinate-view/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-plot-coordinate-view +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/plot-coordinate-view/package.json b/examples/data-objects/multiview/plot-coordinate-view/package.json index e85dde417a28..e739b001ddd8 100644 --- a/examples/data-objects/multiview/plot-coordinate-view/package.json +++ b/examples/data-objects/multiview/plot-coordinate-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-plot-coordinate-view", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", @@ -46,7 +46,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "copyfiles": "^2.4.1", diff --git a/examples/data-objects/multiview/slider-coordinate-view/CHANGELOG.md b/examples/data-objects/multiview/slider-coordinate-view/CHANGELOG.md index 6d349d625676..b58777b42d4a 100644 --- a/examples/data-objects/multiview/slider-coordinate-view/CHANGELOG.md +++ b/examples/data-objects/multiview/slider-coordinate-view/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-slider-coordinate-view +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/slider-coordinate-view/package.json b/examples/data-objects/multiview/slider-coordinate-view/package.json index 1cdda2c1c264..5e5e25158c25 100644 --- a/examples/data-objects/multiview/slider-coordinate-view/package.json +++ b/examples/data-objects/multiview/slider-coordinate-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-slider-coordinate-view", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", @@ -46,7 +46,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "copyfiles": "^2.4.1", diff --git a/examples/data-objects/multiview/triangle-view/CHANGELOG.md b/examples/data-objects/multiview/triangle-view/CHANGELOG.md index 84044e3f1f50..1b850bb93a4a 100644 --- a/examples/data-objects/multiview/triangle-view/CHANGELOG.md +++ b/examples/data-objects/multiview/triangle-view/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/multiview-triangle-view +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/multiview/triangle-view/package.json b/examples/data-objects/multiview/triangle-view/package.json index 92588cb1ebd6..33598c1b262d 100644 --- a/examples/data-objects/multiview/triangle-view/package.json +++ b/examples/data-objects/multiview/triangle-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/multiview-triangle-view", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", @@ -46,7 +46,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "copyfiles": "^2.4.1", diff --git a/examples/data-objects/prosemirror/.eslintrc.cjs b/examples/data-objects/prosemirror/.eslintrc.cjs index 65e3b12d145c..750db0d5d8af 100644 --- a/examples/data-objects/prosemirror/.eslintrc.cjs +++ b/examples/data-objects/prosemirror/.eslintrc.cjs @@ -13,5 +13,6 @@ module.exports = { "@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/strict-boolean-expressions": "off", "no-case-declarations": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/examples/data-objects/prosemirror/CHANGELOG.md b/examples/data-objects/prosemirror/CHANGELOG.md index eb26ee989196..da2060a965a7 100644 --- a/examples/data-objects/prosemirror/CHANGELOG.md +++ b/examples/data-objects/prosemirror/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/prosemirror +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/prosemirror/package.json b/examples/data-objects/prosemirror/package.json index c0dad9ed6c52..ffe84167eb6f 100644 --- a/examples/data-objects/prosemirror/package.json +++ b/examples/data-objects/prosemirror/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/prosemirror", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "ProseMirror", "homepage": "https://fluidframework.com", @@ -81,7 +81,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/node": "^18.19.0", "@types/orderedmap": "^1.0.0", diff --git a/examples/data-objects/smde/CHANGELOG.md b/examples/data-objects/smde/CHANGELOG.md index 0c128797ea31..457f653c36cc 100644 --- a/examples/data-objects/smde/CHANGELOG.md +++ b/examples/data-objects/smde/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/smde +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/smde/package.json b/examples/data-objects/smde/package.json index 159be731d703..f0a53faf3740 100644 --- a/examples/data-objects/smde/package.json +++ b/examples/data-objects/smde/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/smde", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", @@ -60,7 +60,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/react": "^18.3.11", "@types/simplemde": "^1.11.7", diff --git a/examples/data-objects/table-document/CHANGELOG.md b/examples/data-objects/table-document/CHANGELOG.md index 7529abaef118..0184e1c3cf78 100644 --- a/examples/data-objects/table-document/CHANGELOG.md +++ b/examples/data-objects/table-document/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/table-document +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/table-document/package.json b/examples/data-objects/table-document/package.json index 890b7013c5fe..6545f92624de 100644 --- a/examples/data-objects/table-document/package.json +++ b/examples/data-objects/table-document/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/table-document", - "version": "2.5.0", + "version": "2.10.0", "description": "Chaincode component containing a table's data", "homepage": "https://fluidframework.com", "repository": { @@ -93,9 +93,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-version-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/runtime-utils": "workspace:~", "@fluidframework/test-utils": "workspace:~", diff --git a/examples/data-objects/todo/.eslintrc.cjs b/examples/data-objects/todo/.eslintrc.cjs index 42f0972900f5..4cbdde7864d5 100644 --- a/examples/data-objects/todo/.eslintrc.cjs +++ b/examples/data-objects/todo/.eslintrc.cjs @@ -8,4 +8,7 @@ module.exports = { require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), "prettier", ], + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/data-objects/todo/CHANGELOG.md b/examples/data-objects/todo/CHANGELOG.md index d4fce2b627a3..20c6b5e5015e 100644 --- a/examples/data-objects/todo/CHANGELOG.md +++ b/examples/data-objects/todo/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/todo +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/todo/package.json b/examples/data-objects/todo/package.json index cf55e95e9f5e..b787c858c8a6 100644 --- a/examples/data-objects/todo/package.json +++ b/examples/data-objects/todo/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/todo", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Simple todo canvas.", "homepage": "https://fluidframework.com", @@ -56,7 +56,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-example/webpack-fluid-loader": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@fluidframework/test-utils": "workspace:~", diff --git a/examples/data-objects/webflow/CHANGELOG.md b/examples/data-objects/webflow/CHANGELOG.md index fd802eb83c06..acffa8a720c1 100644 --- a/examples/data-objects/webflow/CHANGELOG.md +++ b/examples/data-objects/webflow/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/webflow +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/data-objects/webflow/package.json b/examples/data-objects/webflow/package.json index f6319e86b14d..450da5aac572 100644 --- a/examples/data-objects/webflow/package.json +++ b/examples/data-objects/webflow/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/webflow", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Collaborative markdown editor.", "homepage": "https://fluidframework.com", @@ -93,7 +93,7 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-version-utils": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/runtime-utils": "workspace:~", "@fluidframework/test-utils": "workspace:~", diff --git a/examples/external-data/CHANGELOG.md b/examples/external-data/CHANGELOG.md index 0888490f96fb..6a57b46c4654 100644 --- a/examples/external-data/CHANGELOG.md +++ b/examples/external-data/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-integration-external-data +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/external-data/package.json b/examples/external-data/package.json index 936692e900bd..dd7d591f2cbc 100644 --- a/examples/external-data/package.json +++ b/examples/external-data/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-external-data", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Integrating an external data source with Fluid data.", "homepage": "https://fluidframework.com", @@ -83,7 +83,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/cors": "^2.8.4", diff --git a/examples/service-clients/azure-client/external-controller/CHANGELOG.md b/examples/service-clients/azure-client/external-controller/CHANGELOG.md index 3402c92de9dc..785434ddb965 100644 --- a/examples/service-clients/azure-client/external-controller/CHANGELOG.md +++ b/examples/service-clients/azure-client/external-controller/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-integration-external-controller +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/service-clients/azure-client/external-controller/package.json b/examples/service-clients/azure-client/external-controller/package.json index f4e01d1dcb39..b6c567de8c54 100644 --- a/examples/service-clients/azure-client/external-controller/package.json +++ b/examples/service-clients/azure-client/external-controller/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-external-controller", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", @@ -56,7 +56,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/devtools": "workspace:~", diff --git a/examples/service-clients/azure-client/external-controller/src/app.ts b/examples/service-clients/azure-client/external-controller/src/app.ts index 1853b3c5707e..13ac1a7edc08 100644 --- a/examples/service-clients/azure-client/external-controller/src/app.ts +++ b/examples/service-clients/azure-client/external-controller/src/app.ts @@ -183,7 +183,7 @@ async function start(): Promise { // eslint-disable-next-line @typescript-eslint/member-delimiter-style const lastRoll: { die1?: DieValue; die2?: DieValue } = {}; const presence = acquirePresenceViaDataObject(container.initialObjects.presence); - const states = buildDicePresence(presence); + const states = buildDicePresence(presence).props; // Initialize Devtools initializeDevtools({ diff --git a/examples/service-clients/azure-client/external-controller/src/view.ts b/examples/service-clients/azure-client/external-controller/src/view.ts index 8a1938ddf9c8..c3cb1aea37e9 100644 --- a/examples/service-clients/azure-client/external-controller/src/view.ts +++ b/examples/service-clients/azure-client/external-controller/src/view.ts @@ -167,7 +167,7 @@ function makePresenceView( logContentDiv.style.border = "1px solid black"; if (audience !== undefined) { presenceConfig.presence.events.on("attendeeJoined", (attendee) => { - const name = audience.getMembers().get(attendee.connectionId())?.name; + const name = audience.getMembers().get(attendee.getConnectionId())?.name; const update = `client ${name === undefined ? "(unnamed)" : `named ${name}`} with id ${attendee.sessionId} joined`; addLogEntry(logContentDiv, update); }); diff --git a/examples/service-clients/odsp-client/shared-tree-demo/CHANGELOG.md b/examples/service-clients/odsp-client/shared-tree-demo/CHANGELOG.md index 0af13293d26c..0c4f35ba7868 100644 --- a/examples/service-clients/odsp-client/shared-tree-demo/CHANGELOG.md +++ b/examples/service-clients/odsp-client/shared-tree-demo/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/shared-tree-demo +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/service-clients/odsp-client/shared-tree-demo/package.json b/examples/service-clients/odsp-client/shared-tree-demo/package.json index 61b199c17b17..671a6174050d 100644 --- a/examples/service-clients/odsp-client/shared-tree-demo/package.json +++ b/examples/service-clients/odsp-client/shared-tree-demo/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/shared-tree-demo", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "A shared tree demo using react and odsp client", "homepage": "https://fluidframework.com", @@ -45,9 +45,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/node": "^18.19.0", "@types/react": "^18.3.11", diff --git a/examples/utils/bundle-size-tests/CHANGELOG.md b/examples/utils/bundle-size-tests/CHANGELOG.md index 0eaee97c9c6c..1d5f72ab4ad3 100644 --- a/examples/utils/bundle-size-tests/CHANGELOG.md +++ b/examples/utils/bundle-size-tests/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/bundle-size-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/utils/bundle-size-tests/package.json b/examples/utils/bundle-size-tests/package.json index f439ee74a345..269436962c6b 100644 --- a/examples/utils/bundle-size-tests/package.json +++ b/examples/utils/bundle-size-tests/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bundle-size-tests", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "A package for understanding the bundle size of Fluid Framework", "homepage": "https://fluidframework.com", @@ -48,10 +48,10 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@cerner/duplicate-package-checker-webpack-plugin": "~2.3.0", - "@fluid-tools/version-tools": "^0.49.0", + "@fluid-tools/version-tools": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/bundle-size-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/bundle-size-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@mixer/webpack-bundle-compare": "^0.1.0", "@types/node": "^18.19.0", diff --git a/examples/utils/example-utils/CHANGELOG.md b/examples/utils/example-utils/CHANGELOG.md index 31b743fc807d..bd3e4fb1c62e 100644 --- a/examples/utils/example-utils/CHANGELOG.md +++ b/examples/utils/example-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/example-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/utils/example-utils/package.json b/examples/utils/example-utils/package.json index 72b183a4f32c..d31662bbcadf 100644 --- a/examples/utils/example-utils/package.json +++ b/examples/utils/example-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/example-utils", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Shared utilities used by examples.", "homepage": "https://fluidframework.com", @@ -80,9 +80,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/react": "^18.3.11", diff --git a/examples/utils/migration-tools/CHANGELOG.md b/examples/utils/migration-tools/CHANGELOG.md index fc97941f81fa..4c4e22f88165 100644 --- a/examples/utils/migration-tools/CHANGELOG.md +++ b/examples/utils/migration-tools/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/migration-tools +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/utils/migration-tools/package.json b/examples/utils/migration-tools/package.json index f1534d828bf3..7b979481e0d8 100644 --- a/examples/utils/migration-tools/package.json +++ b/examples/utils/migration-tools/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/migration-tools", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Tools for migrating data", "homepage": "https://fluidframework.com", @@ -85,9 +85,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/uuid": "^9.0.2", diff --git a/examples/utils/webpack-fluid-loader/.eslintrc.cjs b/examples/utils/webpack-fluid-loader/.eslintrc.cjs index 2e444fe30ae8..2ad95371bf3d 100644 --- a/examples/utils/webpack-fluid-loader/.eslintrc.cjs +++ b/examples/utils/webpack-fluid-loader/.eslintrc.cjs @@ -16,5 +16,6 @@ module.exports = { "@typescript-eslint/strict-boolean-expressions": "off", "import/no-nodejs-modules": "off", "no-case-declarations": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/examples/utils/webpack-fluid-loader/CHANGELOG.md b/examples/utils/webpack-fluid-loader/CHANGELOG.md index 07d1ac6348f4..97a8fbe949c9 100644 --- a/examples/utils/webpack-fluid-loader/CHANGELOG.md +++ b/examples/utils/webpack-fluid-loader/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/webpack-fluid-loader +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/utils/webpack-fluid-loader/package.json b/examples/utils/webpack-fluid-loader/package.json index c67a0b12e47c..cda52ac9fdfe 100644 --- a/examples/utils/webpack-fluid-loader/package.json +++ b/examples/utils/webpack-fluid-loader/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/webpack-fluid-loader", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Fluid object loader for webpack-dev-server", "homepage": "https://fluidframework.com", @@ -116,9 +116,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/express": "^4.17.21", "@types/fs-extra": "^9.0.11", diff --git a/examples/version-migration/live-schema-upgrade/CHANGELOG.md b/examples/version-migration/live-schema-upgrade/CHANGELOG.md index 80e80e9ab70c..e7eb0ddf8072 100644 --- a/examples/version-migration/live-schema-upgrade/CHANGELOG.md +++ b/examples/version-migration/live-schema-upgrade/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-integration-live-schema-upgrade +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/version-migration/live-schema-upgrade/package.json b/examples/version-migration/live-schema-upgrade/package.json index 29d0d0515d1a..1a0cd47e7ee3 100644 --- a/examples/version-migration/live-schema-upgrade/package.json +++ b/examples/version-migration/live-schema-upgrade/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-live-schema-upgrade", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example application that demonstrates how to add a data object to a live container.", "homepage": "https://fluidframework.com", @@ -53,9 +53,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/version-migration/same-container/CHANGELOG.md b/examples/version-migration/same-container/CHANGELOG.md index 71e0c34bfeb5..f6feaa73ac22 100644 --- a/examples/version-migration/same-container/CHANGELOG.md +++ b/examples/version-migration/same-container/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/version-migration-same-container +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/version-migration/same-container/package.json b/examples/version-migration/same-container/package.json index d4091ee168e4..896ebc014bd2 100644 --- a/examples/version-migration/same-container/package.json +++ b/examples/version-migration/same-container/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/version-migration-same-container", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Migrate data between two formats by exporting and reimporting in the same container", "homepage": "https://fluidframework.com", @@ -62,9 +62,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/version-migration/separate-container/.eslintrc.cjs b/examples/version-migration/separate-container/.eslintrc.cjs index 69dd265570ae..484c63b7e874 100644 --- a/examples/version-migration/separate-container/.eslintrc.cjs +++ b/examples/version-migration/separate-container/.eslintrc.cjs @@ -5,9 +5,5 @@ module.exports = { extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"], - rules: { - // TODO: AB#18875 - Re-enable react/no-deprecated once we replace uses of the deprecated ReactDOM.render() - // with the new React 18 createRoot(). - "react/no-deprecated": "off", - }, + rules: {}, }; diff --git a/examples/version-migration/separate-container/CHANGELOG.md b/examples/version-migration/separate-container/CHANGELOG.md index 1c8c8fb1afb5..33aac735d75c 100644 --- a/examples/version-migration/separate-container/CHANGELOG.md +++ b/examples/version-migration/separate-container/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/version-migration-separate-container +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/version-migration/separate-container/package.json b/examples/version-migration/separate-container/package.json index 64b79e3e3cff..c02e646e259b 100644 --- a/examples/version-migration/separate-container/package.json +++ b/examples/version-migration/separate-container/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/version-migration-separate-container", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Migrate data between two formats by exporting and reimporting in a new container", "homepage": "https://fluidframework.com", @@ -64,9 +64,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/version-migration/separate-container/src/dataTransform.ts b/examples/version-migration/separate-container/src/dataTransform.ts index f08fb8ceb45e..61e4935b7f2e 100644 --- a/examples/version-migration/separate-container/src/dataTransform.ts +++ b/examples/version-migration/separate-container/src/dataTransform.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-internal-modules import type { DataTransformationCallback } from "@fluid-example/migration-tools/internal"; export interface IParsedInventoryItemData { diff --git a/examples/version-migration/separate-container/src/modelVersion1/appModel.ts b/examples/version-migration/separate-container/src/modelVersion1/appModel.ts index efceedc02c57..a29d58058cea 100644 --- a/examples/version-migration/separate-container/src/modelVersion1/appModel.ts +++ b/examples/version-migration/separate-container/src/modelVersion1/appModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-internal-modules import type { IMigratableModel } from "@fluid-example/migration-tools/internal"; import { AttachState } from "@fluidframework/container-definitions"; import { IContainer } from "@fluidframework/container-definitions/internal"; diff --git a/examples/version-migration/separate-container/src/modelVersion1/containerCode.ts b/examples/version-migration/separate-container/src/modelVersion1/containerCode.ts index a0ab620be2e6..f00e2c4ef5a9 100644 --- a/examples/version-migration/separate-container/src/modelVersion1/containerCode.ts +++ b/examples/version-migration/separate-container/src/modelVersion1/containerCode.ts @@ -7,7 +7,6 @@ import { getDataStoreEntryPoint } from "@fluid-example/example-utils"; import { type IMigratableModel, instantiateMigratableRuntime, - // eslint-disable-next-line import/no-internal-modules } from "@fluid-example/migration-tools/internal"; import type { IContainer, diff --git a/examples/version-migration/separate-container/src/modelVersion2/appModel.ts b/examples/version-migration/separate-container/src/modelVersion2/appModel.ts index d38e63928096..83384aced95c 100644 --- a/examples/version-migration/separate-container/src/modelVersion2/appModel.ts +++ b/examples/version-migration/separate-container/src/modelVersion2/appModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-internal-modules import type { IMigratableModel } from "@fluid-example/migration-tools/internal"; import { AttachState } from "@fluidframework/container-definitions"; import { IContainer } from "@fluidframework/container-definitions/internal"; diff --git a/examples/version-migration/separate-container/src/modelVersion2/containerCode.ts b/examples/version-migration/separate-container/src/modelVersion2/containerCode.ts index a0ab620be2e6..f00e2c4ef5a9 100644 --- a/examples/version-migration/separate-container/src/modelVersion2/containerCode.ts +++ b/examples/version-migration/separate-container/src/modelVersion2/containerCode.ts @@ -7,7 +7,6 @@ import { getDataStoreEntryPoint } from "@fluid-example/example-utils"; import { type IMigratableModel, instantiateMigratableRuntime, - // eslint-disable-next-line import/no-internal-modules } from "@fluid-example/migration-tools/internal"; import type { IContainer, diff --git a/examples/version-migration/separate-container/src/start.ts b/examples/version-migration/separate-container/src/start.ts index d1ea14bfaf03..e346e638cece 100644 --- a/examples/version-migration/separate-container/src/start.ts +++ b/examples/version-migration/separate-container/src/start.ts @@ -7,9 +7,7 @@ import type { IMigratableModel, IMigrationTool, IVersionedModel, - // eslint-disable-next-line import/no-internal-modules } from "@fluid-example/migration-tools/internal"; -// eslint-disable-next-line import/no-internal-modules import { MigratableModelLoader, Migrator } from "@fluid-example/migration-tools/internal"; import { RouterliciousDocumentServiceFactory } from "@fluidframework/routerlicious-driver/internal"; import { @@ -18,8 +16,8 @@ import { createTinyliciousCreateNewRequest, } from "@fluidframework/tinylicious-driver/internal"; import { createElement } from "react"; -// eslint-disable-next-line import/no-deprecated -- TODO: AB#18875, migrate to React 18 APIs -import { render, unmountComponentAtNode } from "react-dom"; +// eslint-disable-next-line import/no-internal-modules +import { createRoot, type Root } from "react-dom/client"; import { inventoryListDataTransformationCallback } from "./dataTransform.js"; import { DemoCodeLoader } from "./demoCodeLoader.js"; @@ -42,29 +40,27 @@ const isIInventoryListAppModel = ( const getUrlForContainerId = (containerId: string): string => `/#${containerId}`; +let appRoot: Root | undefined; +let debugRoot: Root | undefined; + const renderModel = (model: IVersionedModel, migrationTool: IMigrationTool): void => { - const appDiv = document.querySelector("#app") as HTMLDivElement; - // eslint-disable-next-line import/no-deprecated -- TODO: AB#18875, migrate to React 18 APIs - unmountComponentAtNode(appDiv); // This demo uses the same view for both versions 1 & 2 - if we wanted to use different views for different model // versions, we could check its version here and select the appropriate view. Or we could even write ourselves a // view code loader to pull in the view dynamically based on the version we discover. if (isIInventoryListAppModel(model)) { - // eslint-disable-next-line import/no-deprecated -- TODO: AB#18875, migrate to React 18 APIs - render(createElement(InventoryListAppView, { model, migrationTool }), appDiv); + const appDiv = document.querySelector("#app") as HTMLDivElement; + appRoot ??= createRoot(appDiv); + appRoot.render(createElement(InventoryListAppView, { model, migrationTool })); // The DebugView is just for demo purposes, to manually control code proposal and inspect the state. const debugDiv = document.querySelector("#debug") as HTMLDivElement; - // eslint-disable-next-line import/no-deprecated -- TODO: AB#18875, migrate to React 18 APIs - unmountComponentAtNode(debugDiv); - // eslint-disable-next-line import/no-deprecated -- TODO: AB#18875, migrate to React 18 APIs - render( + debugRoot ??= createRoot(debugDiv); + debugRoot.render( createElement(DebugView, { model, migrationTool, getUrlForContainerId, }), - debugDiv, ); } else { throw new Error(`Don't know how to render version ${model.version}`); diff --git a/examples/version-migration/separate-container/src/view/appView.tsx b/examples/version-migration/separate-container/src/view/appView.tsx index 451c0285827e..7dd433d8c260 100644 --- a/examples/version-migration/separate-container/src/view/appView.tsx +++ b/examples/version-migration/separate-container/src/view/appView.tsx @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-internal-modules import type { IMigrationTool } from "@fluid-example/migration-tools/internal"; import React, { useEffect, useState } from "react"; diff --git a/examples/version-migration/separate-container/src/view/debugView.tsx b/examples/version-migration/separate-container/src/view/debugView.tsx index 0d0c3f48b393..8619c90b1ea6 100644 --- a/examples/version-migration/separate-container/src/view/debugView.tsx +++ b/examples/version-migration/separate-container/src/view/debugView.tsx @@ -7,7 +7,6 @@ import type { IMigratableModel, IMigrationTool, MigrationState, - // eslint-disable-next-line import/no-internal-modules } from "@fluid-example/migration-tools/internal"; import React, { useEffect, useState } from "react"; diff --git a/examples/version-migration/separate-container/tests/index.tsx b/examples/version-migration/separate-container/tests/index.tsx index 70678f199627..0e20bec2fde1 100644 --- a/examples/version-migration/separate-container/tests/index.tsx +++ b/examples/version-migration/separate-container/tests/index.tsx @@ -11,8 +11,8 @@ import { Migrator, } from "@fluid-example/migration-tools/internal"; -import React from "react"; -import ReactDOM from "react-dom"; +import { createElement } from "react"; +import { createRoot } from "react-dom/client"; import { inventoryListDataTransformationCallback } from "../src/dataTransform.js"; import { DemoCodeLoader } from "../src/demoCodeLoader.js"; @@ -72,26 +72,23 @@ export async function createContainerAndRenderInElement(element: HTMLDivElement) const appDiv = document.createElement("div"); const debugDiv = document.createElement("div"); + const appRoot = createRoot(appDiv); + const debugRoot = createRoot(debugDiv); + const render = (model: IVersionedModel, migrationTool: IMigrationTool) => { - ReactDOM.unmountComponentAtNode(appDiv); // This demo uses the same view for both versions 1 & 2 - if we wanted to use different views for different model // versions, we could check its version here and select the appropriate view. Or we could even write ourselves a // view code loader to pull in the view dynamically based on the version we discover. if (isIInventoryListAppModel(model)) { - ReactDOM.render( - React.createElement(InventoryListAppView, { model, migrationTool }), - appDiv, - ); + appRoot.render(createElement(InventoryListAppView, { model, migrationTool })); // The DebugView is just for demo purposes, to manually control code proposal and inspect the state. - ReactDOM.unmountComponentAtNode(debugDiv); - ReactDOM.render( - React.createElement(DebugView, { + debugRoot.render( + createElement(DebugView, { model, migrationTool, getUrlForContainerId, }), - debugDiv, ); } else { throw new Error(`Don't know how to render version ${model.version}`); diff --git a/examples/version-migration/separate-container/tests/separateContainer.test.ts b/examples/version-migration/separate-container/tests/separateContainer.test.ts index c093f43d061f..5f813bfefc4c 100644 --- a/examples/version-migration/separate-container/tests/separateContainer.test.ts +++ b/examples/version-migration/separate-container/tests/separateContainer.test.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. */ -import { IMigrator } from "@fluid-example/migration-tools/internal"; -import { IContainer } from "@fluidframework/container-definitions/internal"; +import type { IMigrator } from "@fluid-example/migration-tools/internal"; +import type { IContainer } from "@fluidframework/container-definitions/internal"; +import type { ISequencedClient } from "@fluidframework/driver-definitions/internal"; + import { globals } from "../jest.config.cjs"; describe("separate-container migration", () => { @@ -38,7 +40,7 @@ describe("separate-container migration", () => { const migrationStatusElement = document.querySelector(".migration-status"); return migrationStatusElement?.textContent?.includes("one") === true; }); - await expect(containsOne).toEqual(true); + expect(containsOne).toEqual(true); }); it("migrates and shows the correct code version after migration", async () => { @@ -55,17 +57,17 @@ describe("separate-container migration", () => { const migrationStatusElements = document.querySelectorAll(".migration-status"); return migrationStatusElements[1]?.textContent?.includes("one") === true; }); - await expect(leftContainsOne).toEqual(true); - await expect(rightContainsOne).toEqual(true); + expect(leftContainsOne).toEqual(true); + expect(rightContainsOne).toEqual(true); const migratorsLength = await page.evaluate(() => { - return window["migrators"].length; + return (window["migrators"] as IMigrator[]).length; }); - await expect(migratorsLength).toEqual(2); + expect(migratorsLength).toEqual(2); // Get a promise that will resolve when both sides have finished migration - const migrationP = page.evaluate(() => { - const migrationPs = (window["migrators"] as IMigrator[]).map((migrator) => { + const migrationP = page.evaluate(async () => { + const migrationPs = (window["migrators"] as IMigrator[]).map(async (migrator) => { return new Promise((resolve) => { migrator.events.once("migrated", resolve); }); @@ -86,8 +88,8 @@ describe("separate-container migration", () => { const migrationStatusElements = document.querySelectorAll(".migration-status"); return migrationStatusElements[1]?.textContent?.includes("two") === true; }); - await expect(leftContainsTwo).toEqual(true); - await expect(rightContainsTwo).toEqual(true); + expect(leftContainsTwo).toEqual(true); + expect(rightContainsTwo).toEqual(true); }); }); @@ -97,8 +99,7 @@ describe("separate-container migration", () => { await page.waitForFunction(() => window["fluidStarted"]); }); - // TODO:AB#14341: Investigate flakiness and re-enable. - it.skip("migrates after summarizer has connected", async () => { + it("migrates after summarizer has connected", async () => { // Validate the migration status shows "one" initially await Promise.all([ page.waitForSelector("#sbs-left .migration-status"), @@ -121,27 +122,28 @@ describe("separate-container migration", () => { const migrationStatusElements = document.querySelectorAll(".migration-status"); return migrationStatusElements[1]?.textContent?.includes("one") === true; }); - await expect(leftContainsOne).toEqual(true); - await expect(rightContainsOne).toEqual(true); + expect(leftContainsOne).toEqual(true); + expect(rightContainsOne).toEqual(true); // Wait until we see the summarizer join - await page.evaluate(() => { + await page.evaluate(async () => { // This is reaching a bit, but we just need to watch it for test purposes. const leftQuorum = ( - window["migrators"][0]._currentMigratable.model.container as IContainer - ).getQuorum(); - const alreadyHasSummarizer = - [...leftQuorum.getMembers().values()].find( - (client) => client.client.details.type === "summarizer", - ) !== undefined; + (window["migrators"] as IMigrator[])[0].currentModel as unknown as { + container: IContainer; + } + ).container.getQuorum(); + const alreadyHasSummarizer = [...leftQuorum.getMembers().values()].some( + (sequencedClient) => sequencedClient.client.details.type === "summarizer", + ); if (alreadyHasSummarizer) { // This should be the path taken since demo mode should spawn the summarizer instantly. return; } // In case the summarizer isn't quite connected yet, return a Promise so we can await for it to join. return new Promise((resolve) => { - const watchForSummarizer = (clientId, details) => { - if (details.type === "summarizer") { + const watchForSummarizer = (clientId, sequencedClient: ISequencedClient): void => { + if (sequencedClient.client.details.type === "summarizer") { resolve(); leftQuorum.off("addMember", watchForSummarizer); } @@ -151,13 +153,20 @@ describe("separate-container migration", () => { }); const migratorsLength = await page.evaluate(() => { - return window["migrators"].length; + return (window["migrators"] as IMigrator[]).length; }); - await expect(migratorsLength).toEqual(2); + expect(migratorsLength).toEqual(2); // Get a promise that will resolve when both sides have finished migration - const migrationP = page.evaluate(() => { - const migrationPs = (window["migrators"] as IMigrator[]).map((migrator) => { + const migrationP = page.evaluate(async () => { + const migrationPs = (window["migrators"] as IMigrator[]).map(async (migrator) => { + // Since we expect this to run before the button click below, nothing should have migrated. + // However, we are getting flaky errors and want to rule out the possibility that the puppeteer interaction + // is somehow permitting these to occur out of order. Throwing here will cause the returned migrationP + // promise to immediately reject (since Promise.all rejects as soon as the first rejection occurs). + if (migrator.currentModel.version !== "one") { + throw new Error("Unexpected early migration!"); + } return new Promise((resolve) => { migrator.events.once("migrated", resolve); }); @@ -178,8 +187,8 @@ describe("separate-container migration", () => { const migrationStatusElements = document.querySelectorAll(".migration-status"); return migrationStatusElements[1]?.textContent?.includes("two") === true; }); - await expect(leftContainsTwo).toEqual(true); - await expect(rightContainsTwo).toEqual(true); + expect(leftContainsTwo).toEqual(true); + expect(rightContainsTwo).toEqual(true); }); }); }); diff --git a/examples/version-migration/tree-shim/.eslintrc.cjs b/examples/version-migration/tree-shim/.eslintrc.cjs index 8826fa4dec21..4cbdde7864d5 100644 --- a/examples/version-migration/tree-shim/.eslintrc.cjs +++ b/examples/version-migration/tree-shim/.eslintrc.cjs @@ -8,5 +8,7 @@ module.exports = { require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), "prettier", ], - rules: {}, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/examples/version-migration/tree-shim/CHANGELOG.md b/examples/version-migration/tree-shim/CHANGELOG.md index 3f64ea854d03..3e933c8bba90 100644 --- a/examples/version-migration/tree-shim/CHANGELOG.md +++ b/examples/version-migration/tree-shim/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/tree-comparison +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/version-migration/tree-shim/package.json b/examples/version-migration/tree-shim/package.json index 26281294969b..04deb33a47f2 100644 --- a/examples/version-migration/tree-shim/package.json +++ b/examples/version-migration/tree-shim/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/tree-shim", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Migrating from legacy SharedTree to new SharedTree using a tree shim.", "homepage": "https://fluidframework.com", @@ -61,9 +61,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/view-integration/container-views/CHANGELOG.md b/examples/view-integration/container-views/CHANGELOG.md index fe822998e539..931099ae0fad 100644 --- a/examples/view-integration/container-views/CHANGELOG.md +++ b/examples/view-integration/container-views/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-integration-container-views +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/view-integration/container-views/package.json b/examples/view-integration/container-views/package.json index aadbea72de7b..6f7dc46625ea 100644 --- a/examples/view-integration/container-views/package.json +++ b/examples/view-integration/container-views/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-container-views", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid Container & data store sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", @@ -49,9 +49,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/view-integration/external-views/CHANGELOG.md b/examples/view-integration/external-views/CHANGELOG.md index a44b03ec9bc4..ce6edf483627 100644 --- a/examples/view-integration/external-views/CHANGELOG.md +++ b/examples/view-integration/external-views/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/app-integration-external-views +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/view-integration/external-views/package.json b/examples/view-integration/external-views/package.json index d3de8413ecc9..6e8d25fe3650 100644 --- a/examples/view-integration/external-views/package.json +++ b/examples/view-integration/external-views/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-external-views", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", @@ -49,9 +49,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/examples/view-integration/view-framework-sampler/CHANGELOG.md b/examples/view-integration/view-framework-sampler/CHANGELOG.md index 154ea6b2b781..2e260d21b41b 100644 --- a/examples/view-integration/view-framework-sampler/CHANGELOG.md +++ b/examples/view-integration/view-framework-sampler/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/view-framework-sampler +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/examples/view-integration/view-framework-sampler/package.json b/examples/view-integration/view-framework-sampler/package.json index b6199ee4e0a7..a8043afa32ec 100644 --- a/examples/view-integration/view-framework-sampler/package.json +++ b/examples/view-integration/view-framework-sampler/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/view-framework-sampler", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Example of integrating a Fluid data object with a variety of view frameworks.", "homepage": "https://fluidframework.com", @@ -50,9 +50,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@types/jest": "29.5.3", diff --git a/experimental/PropertyDDS/packages/property-changeset/CHANGELOG.md b/experimental/PropertyDDS/packages/property-changeset/CHANGELOG.md index 5807b1631561..19d2e57e18d8 100644 --- a/experimental/PropertyDDS/packages/property-changeset/CHANGELOG.md +++ b/experimental/PropertyDDS/packages/property-changeset/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/property-changeset +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/PropertyDDS/packages/property-changeset/package.json b/experimental/PropertyDDS/packages/property-changeset/package.json index 0a771a0cbbdb..fbce959aaf5a 100644 --- a/experimental/PropertyDDS/packages/property-changeset/package.json +++ b/experimental/PropertyDDS/packages/property-changeset/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-changeset", - "version": "2.5.0", + "version": "2.10.0", "description": "property changeset definitions and related functionalities", "homepage": "https://fluidframework.com", "repository": { @@ -95,7 +95,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@types/lodash": "^4.14.118", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/experimental/PropertyDDS/packages/property-common/CHANGELOG.md b/experimental/PropertyDDS/packages/property-common/CHANGELOG.md index 306ab37ff643..30d966cb6f96 100644 --- a/experimental/PropertyDDS/packages/property-common/CHANGELOG.md +++ b/experimental/PropertyDDS/packages/property-common/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/property-common +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/PropertyDDS/packages/property-common/package.json b/experimental/PropertyDDS/packages/property-common/package.json index 639c3e9286bb..c8ca0f3b2477 100644 --- a/experimental/PropertyDDS/packages/property-common/package.json +++ b/experimental/PropertyDDS/packages/property-common/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-common", - "version": "2.5.0", + "version": "2.10.0", "description": "common functions used in properties", "homepage": "https://fluidframework.com", "repository": { @@ -73,7 +73,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/chai": "^4.0.0", diff --git a/experimental/PropertyDDS/packages/property-common/platform-dependent/CHANGELOG.md b/experimental/PropertyDDS/packages/property-common/platform-dependent/CHANGELOG.md index b74bd939fc41..3b2cd9cfcdf2 100644 --- a/experimental/PropertyDDS/packages/property-common/platform-dependent/CHANGELOG.md +++ b/experimental/PropertyDDS/packages/property-common/platform-dependent/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/platform-dependent +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json b/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json index 193ad3a9f9da..e1a34b861ea2 100644 --- a/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json +++ b/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/platform-dependent", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Helper package that separates code for browser and server.", "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/packages/property-dds/.eslintrc.cjs b/experimental/PropertyDDS/packages/property-dds/.eslintrc.cjs index 3efadc5ada15..14bcffb99ede 100644 --- a/experimental/PropertyDDS/packages/property-dds/.eslintrc.cjs +++ b/experimental/PropertyDDS/packages/property-dds/.eslintrc.cjs @@ -14,5 +14,6 @@ module.exports = { rules: { "@typescript-eslint/strict-boolean-expressions": "off", "tsdoc/syntax": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/experimental/PropertyDDS/packages/property-dds/CHANGELOG.md b/experimental/PropertyDDS/packages/property-dds/CHANGELOG.md index f5c5dd0dc661..27b137ccd34c 100644 --- a/experimental/PropertyDDS/packages/property-dds/CHANGELOG.md +++ b/experimental/PropertyDDS/packages/property-dds/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/property-dds +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/PropertyDDS/packages/property-dds/package.json b/experimental/PropertyDDS/packages/property-dds/package.json index c6987ad56a0c..57587ae4619d 100644 --- a/experimental/PropertyDDS/packages/property-dds/package.json +++ b/experimental/PropertyDDS/packages/property-dds/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-dds", - "version": "2.5.0", + "version": "2.10.0", "description": "definition of the property distributed data store", "homepage": "https://fluidframework.com", "repository": { @@ -80,9 +80,9 @@ "@fluid-experimental/property-common": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-drivers": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", diff --git a/experimental/PropertyDDS/packages/property-properties/CHANGELOG.md b/experimental/PropertyDDS/packages/property-properties/CHANGELOG.md index e1e10210db6f..b3484e86c7d4 100644 --- a/experimental/PropertyDDS/packages/property-properties/CHANGELOG.md +++ b/experimental/PropertyDDS/packages/property-properties/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/property-properties +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/PropertyDDS/packages/property-properties/package.json b/experimental/PropertyDDS/packages/property-properties/package.json index bf085c165e68..fa9eff762b1d 100644 --- a/experimental/PropertyDDS/packages/property-properties/package.json +++ b/experimental/PropertyDDS/packages/property-properties/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-properties", - "version": "2.5.0", + "version": "2.10.0", "description": "definitions of properties", "homepage": "https://fluidframework.com", "repository": { @@ -78,7 +78,7 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", "@types/sinon": "^17.0.3", diff --git a/experimental/dds/attributable-map/.eslintrc.cjs b/experimental/dds/attributable-map/.eslintrc.cjs index 7eb010a2858b..c155127959ac 100644 --- a/experimental/dds/attributable-map/.eslintrc.cjs +++ b/experimental/dds/attributable-map/.eslintrc.cjs @@ -17,5 +17,6 @@ module.exports = { // TODO: consider re-enabling once we have addressed how this rule conflicts with our error codes. "unicorn/numeric-separators-style": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/experimental/dds/attributable-map/CHANGELOG.md b/experimental/dds/attributable-map/CHANGELOG.md index 78f841f185c9..e42ec3388924 100644 --- a/experimental/dds/attributable-map/CHANGELOG.md +++ b/experimental/dds/attributable-map/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/attributable-map +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/dds/attributable-map/package.json b/experimental/dds/attributable-map/package.json index 48cd4772a0cb..ee288b0dca48 100644 --- a/experimental/dds/attributable-map/package.json +++ b/experimental/dds/attributable-map/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/attributable-map", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed map", "homepage": "https://fluidframework.com", "repository": { @@ -101,14 +101,14 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-experimental/attributable-map-previous": "npm:@fluid-experimental/attributable-map@~2.4.0", + "@fluid-experimental/attributable-map-previous": "npm:@fluid-experimental/attributable-map@2.5.0", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", diff --git a/experimental/dds/attributable-map/src/packageVersion.ts b/experimental/dds/attributable-map/src/packageVersion.ts index f615ab29d0d4..cd4b6223309b 100644 --- a/experimental/dds/attributable-map/src/packageVersion.ts +++ b/experimental/dds/attributable-map/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/attributable-map"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/experimental/dds/ot/ot/CHANGELOG.md b/experimental/dds/ot/ot/CHANGELOG.md index f0962c0f17de..fa0fbfb3b2c9 100644 --- a/experimental/dds/ot/ot/CHANGELOG.md +++ b/experimental/dds/ot/ot/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/ot +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/dds/ot/ot/package.json b/experimental/dds/ot/ot/package.json index 9b5271693b55..66177f7e06b4 100644 --- a/experimental/dds/ot/ot/package.json +++ b/experimental/dds/ot/ot/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/ot", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed data structure for hosting ottypes", "homepage": "https://fluidframework.com", "repository": { @@ -97,9 +97,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/experimental/dds/ot/ot/src/packageVersion.ts b/experimental/dds/ot/ot/src/packageVersion.ts index cdb27e4079bc..3aa03d542c66 100644 --- a/experimental/dds/ot/ot/src/packageVersion.ts +++ b/experimental/dds/ot/ot/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/ot"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/experimental/dds/ot/sharejs/json1/CHANGELOG.md b/experimental/dds/ot/sharejs/json1/CHANGELOG.md index d2ca1bdf86f3..68f3b1028512 100644 --- a/experimental/dds/ot/sharejs/json1/CHANGELOG.md +++ b/experimental/dds/ot/sharejs/json1/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/sharejs-json1 +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/dds/ot/sharejs/json1/package.json b/experimental/dds/ot/sharejs/json1/package.json index 1f4e3db08ae4..5b005d1db4b2 100644 --- a/experimental/dds/ot/sharejs/json1/package.json +++ b/experimental/dds/ot/sharejs/json1/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/sharejs-json1", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed data structure for hosting ottypes", "homepage": "https://fluidframework.com", "repository": { @@ -97,9 +97,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/experimental/dds/ot/sharejs/json1/src/packageVersion.ts b/experimental/dds/ot/sharejs/json1/src/packageVersion.ts index a58b4783ec04..e12be05494dc 100644 --- a/experimental/dds/ot/sharejs/json1/src/packageVersion.ts +++ b/experimental/dds/ot/sharejs/json1/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/sharejs-json1"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/experimental/dds/sequence-deprecated/.eslintrc.cjs b/experimental/dds/sequence-deprecated/.eslintrc.cjs index 555917d0c362..145e4cb2fdc2 100644 --- a/experimental/dds/sequence-deprecated/.eslintrc.cjs +++ b/experimental/dds/sequence-deprecated/.eslintrc.cjs @@ -15,5 +15,6 @@ module.exports = { "import/no-deprecated": "off", // This package uses deprecated APIs by design. "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/experimental/dds/sequence-deprecated/CHANGELOG.md b/experimental/dds/sequence-deprecated/CHANGELOG.md index 0dded5bbfbd2..8101f8d8a184 100644 --- a/experimental/dds/sequence-deprecated/CHANGELOG.md +++ b/experimental/dds/sequence-deprecated/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/sequence-deprecated +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/dds/sequence-deprecated/package.json b/experimental/dds/sequence-deprecated/package.json index 766e578c9871..e301963c186e 100644 --- a/experimental/dds/sequence-deprecated/package.json +++ b/experimental/dds/sequence-deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/sequence-deprecated", - "version": "2.5.0", + "version": "2.10.0", "description": "Deprecated distributed sequences", "homepage": "https://fluidframework.com", "repository": { @@ -98,9 +98,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/experimental/dds/sequence-deprecated/src/packageVersion.ts b/experimental/dds/sequence-deprecated/src/packageVersion.ts index cc5e97fe184b..f61d5a0ba855 100644 --- a/experimental/dds/sequence-deprecated/src/packageVersion.ts +++ b/experimental/dds/sequence-deprecated/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/sequence-deprecated"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/experimental/dds/tree/.eslintrc.cjs b/experimental/dds/tree/.eslintrc.cjs index c6294c0700e3..a20566571747 100644 --- a/experimental/dds/tree/.eslintrc.cjs +++ b/experimental/dds/tree/.eslintrc.cjs @@ -24,6 +24,7 @@ module.exports = { 'no-shadow': 'off', '@typescript-eslint/no-unsafe-return': 'off', 'import/no-deprecated': 'off', + '@fluid-internal/fluid/no-unchecked-record-access': 'off', }, overrides: [ { diff --git a/experimental/dds/tree/CHANGELOG.md b/experimental/dds/tree/CHANGELOG.md index 7cf429b56ee2..958290869a2a 100644 --- a/experimental/dds/tree/CHANGELOG.md +++ b/experimental/dds/tree/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/tree +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/dds/tree/package.json b/experimental/dds/tree/package.json index 9fb88f83adb5..f5d319ecc8de 100644 --- a/experimental/dds/tree/package.json +++ b/experimental/dds/tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/tree", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed tree", "homepage": "https://fluidframework.com", "repository": { @@ -98,7 +98,7 @@ "@fluid-private/test-drivers": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime": "workspace:~", diff --git a/experimental/dds/tree/src/PayloadUtilities.ts b/experimental/dds/tree/src/PayloadUtilities.ts index de543dcc4ef3..4edd96e129e6 100644 --- a/experimental/dds/tree/src/PayloadUtilities.ts +++ b/experimental/dds/tree/src/PayloadUtilities.ts @@ -4,7 +4,7 @@ */ import { compareArrays } from '@fluidframework/core-utils/internal'; -import { isFluidHandle, toFluidHandleInternal } from '@fluidframework/runtime-utils/internal'; +import { compareFluidHandles, isFluidHandle } from '@fluidframework/runtime-utils'; import { Payload } from './persisted-types/index.js'; @@ -67,7 +67,7 @@ export function comparePayloads(a: Payload, b: Payload): boolean { // Special case IFluidHandles, comparing them only by their absolutePath if (isFluidHandle(a)) { if (isFluidHandle(b)) { - return toFluidHandleInternal(a).absolutePath === toFluidHandleInternal(b).absolutePath; + return compareFluidHandles(a, b); } return false; } diff --git a/experimental/framework/data-objects/CHANGELOG.md b/experimental/framework/data-objects/CHANGELOG.md index 4fb37ba11b9e..d4e6cb9c74ad 100644 --- a/experimental/framework/data-objects/CHANGELOG.md +++ b/experimental/framework/data-objects/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/data-objects +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/framework/data-objects/package.json b/experimental/framework/data-objects/package.json index 499d03d4a939..06f89b4a29e6 100644 --- a/experimental/framework/data-objects/package.json +++ b/experimental/framework/data-objects/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/data-objects", - "version": "2.5.0", + "version": "2.10.0", "description": "A collection of ready to use Fluid Data Objects", "homepage": "https://fluidframework.com", "repository": { @@ -63,9 +63,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/experimental/framework/last-edited/CHANGELOG.md b/experimental/framework/last-edited/CHANGELOG.md index 2ae662c72e11..a4aca10eb0eb 100644 --- a/experimental/framework/last-edited/CHANGELOG.md +++ b/experimental/framework/last-edited/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/last-edited +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/framework/last-edited/package.json b/experimental/framework/last-edited/package.json index 1c72040854fd..26a8c6167c3e 100644 --- a/experimental/framework/last-edited/package.json +++ b/experimental/framework/last-edited/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/last-edited", - "version": "2.5.0", + "version": "2.10.0", "description": "Tracks the last edited information in the Container.", "homepage": "https://fluidframework.com", "repository": { @@ -63,9 +63,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/experimental/framework/tree-react-api/CHANGELOG.md b/experimental/framework/tree-react-api/CHANGELOG.md index 293370a9f803..7bfa963395eb 100644 --- a/experimental/framework/tree-react-api/CHANGELOG.md +++ b/experimental/framework/tree-react-api/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/tree-react-api +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/experimental/framework/tree-react-api/package.json b/experimental/framework/tree-react-api/package.json index fc81ad6194f2..c2da8dc8f3ef 100644 --- a/experimental/framework/tree-react-api/package.json +++ b/experimental/framework/tree-react-api/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/tree-react-api", - "version": "2.5.0", + "version": "2.10.0", "description": "Experimental SharedTree API for React integration.", "homepage": "https://fluidframework.com", "repository": { @@ -110,9 +110,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/tinylicious-client": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/feeds/internal-build.txt b/feeds/internal-build.txt index 7f61b4e91db7..dd633fe224d4 100644 --- a/feeds/internal-build.txt +++ b/feeds/internal-build.txt @@ -37,7 +37,7 @@ fluid-framework @fluidframework/app-insights-logger @fluid-experimental/attributor @fluidframework/aqueduct -@fluid-experimental/ai-collab +@fluidframework/ai-collab @fluidframework/agent-scheduler @fluidframework/tinylicious-driver @fluidframework/routerlicious-urlresolver diff --git a/feeds/internal-test.txt b/feeds/internal-test.txt index f5e90bc1101e..976b2fd30796 100644 --- a/feeds/internal-test.txt +++ b/feeds/internal-test.txt @@ -43,7 +43,7 @@ fluid-framework @fluidframework/app-insights-logger @fluid-experimental/attributor @fluidframework/aqueduct -@fluid-experimental/ai-collab +@fluidframework/ai-collab @fluidframework/agent-scheduler @fluidframework/tinylicious-driver @fluidframework/routerlicious-urlresolver diff --git a/feeds/public.txt b/feeds/public.txt index acce396cef93..3fbf321ba570 100644 --- a/feeds/public.txt +++ b/feeds/public.txt @@ -36,7 +36,7 @@ fluid-framework @fluidframework/app-insights-logger @fluid-experimental/attributor @fluidframework/aqueduct -@fluid-experimental/ai-collab +@fluidframework/ai-collab @fluidframework/agent-scheduler @fluidframework/tinylicious-driver @fluidframework/routerlicious-urlresolver diff --git a/fluidBuild.config.cjs b/fluidBuild.config.cjs index bb94b926735b..3c5a4c28868a 100644 --- a/fluidBuild.config.cjs +++ b/fluidBuild.config.cjs @@ -631,9 +631,10 @@ module.exports = { releaseNotes: { sections: { feature: { heading: "✨ New Features" }, - tree: { heading: "🌳 SharedTree DDS changes" }, + tree: { heading: "🌳 SharedTree DDS Changes" }, fix: { heading: "🐛 Bug Fixes" }, deprecation: { heading: "⚠️ Deprecations" }, + legacy: { heading: "Legacy API Changes" }, other: { heading: "Other Changes" }, }, }, diff --git a/lerna.json b/lerna.json index 0eb102263491..485de7a24969 100644 --- a/lerna.json +++ b/lerna.json @@ -1 +1 @@ -{ "version": "2.5.0", "npmClient": "pnpm", "useWorkspaces": true } +{ "version": "2.10.0", "npmClient": "pnpm", "useWorkspaces": true } diff --git a/package.json b/package.json index 4fb824ed670f..161a09ae8ca6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "client-release-group-root", - "version": "2.5.0", + "version": "2.10.0", "private": true, "homepage": "https://fluidframework.com", "repository": { @@ -158,10 +158,10 @@ "@biomejs/biome": "~1.9.3", "@changesets/cli": "^2.27.8", "@fluid-private/changelog-generator-wrapper": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluid-tools/markdown-magic": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-tools": "^1.0.195075", "@microsoft/api-documenter": "^7.21.6", diff --git a/packages/common/client-utils/CHANGELOG.md b/packages/common/client-utils/CHANGELOG.md index fe8ccb19e6bc..7ddfb3c27edb 100644 --- a/packages/common/client-utils/CHANGELOG.md +++ b/packages/common/client-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/client-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/common/client-utils/package.json b/packages/common/client-utils/package.json index 1f787752d324..05b93a286ff5 100644 --- a/packages/common/client-utils/package.json +++ b/packages/common/client-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/client-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Not intended for use outside the Fluid Framework.", "homepage": "https://fluidframework.com", "repository": { @@ -135,11 +135,11 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-internal/client-utils-previous": "npm:@fluid-internal/client-utils@~2.4.0", + "@fluid-internal/client-utils-previous": "npm:@fluid-internal/client-utils@2.5.0", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/base64-js": "^1.3.0", diff --git a/packages/common/container-definitions/CHANGELOG.md b/packages/common/container-definitions/CHANGELOG.md index 5d8bab1ed11d..fb17ffa96369 100644 --- a/packages/common/container-definitions/CHANGELOG.md +++ b/packages/common/container-definitions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/container-definitions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/common/container-definitions/api-report/container-definitions.legacy.alpha.api.md b/packages/common/container-definitions/api-report/container-definitions.legacy.alpha.api.md index b6560b694619..1a9a9854a26b 100644 --- a/packages/common/container-definitions/api-report/container-definitions.legacy.alpha.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.legacy.alpha.api.md @@ -204,8 +204,6 @@ export interface IDeltaManager extends IEventProvider readonly active: boolean; readonly clientDetails: IClientDetails; readonly hasCheckpointSequenceNumber: boolean; - // @deprecated - readonly inbound: IDeltaQueue; readonly inboundSignal: IDeltaQueue; readonly initialSequenceNumber: number; readonly lastKnownSeqNumber: number; @@ -213,8 +211,6 @@ export interface IDeltaManager extends IEventProvider readonly lastSequenceNumber: number; readonly maxMessageSize: number; readonly minimumSequenceNumber: number; - // @deprecated - readonly outbound: IDeltaQueue; // (undocumented) readonly readOnlyInfo: ReadOnlyInfo; readonly serviceConfiguration: IClientConfiguration | undefined; diff --git a/packages/common/container-definitions/package.json b/packages/common/container-definitions/package.json index 38f334fdc904..de67c44de929 100644 --- a/packages/common/container-definitions/package.json +++ b/packages/common/container-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/container-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid container definitions", "homepage": "https://fluidframework.com", "repository": { @@ -99,10 +99,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/container-definitions-previous": "npm:@fluidframework/container-definitions@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/container-definitions-previous": "npm:@fluidframework/container-definitions@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", @@ -114,7 +114,17 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IContainer": { + "backCompat": false + }, + "Interface_IContainerContext": { + "backCompat": false + }, + "Interface_IDeltaManager": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/common/container-definitions/src/deltas.ts b/packages/common/container-definitions/src/deltas.ts index 4a708cca2503..6772fb27dacb 100644 --- a/packages/common/container-definitions/src/deltas.ts +++ b/packages/common/container-definitions/src/deltas.ts @@ -156,20 +156,6 @@ export interface IDeltaManagerEvents extends IEvent { export interface IDeltaManager extends IEventProvider, IDeltaSender { - /** - * The queue of inbound delta messages - * @deprecated Do not use, for internal use only. There are a lot of complications in core pieces of the runtime - * may break if this is used directly. For example summarization and op processing. - */ - readonly inbound: IDeltaQueue; - - /** - * The queue of outbound delta messages - * @deprecated Do not use, for internal use only. There are a lot of complications in core pieces of the runtime - * may break if this is used directly. For example op submission - */ - readonly outbound: IDeltaQueue; - /** * The queue of inbound delta signals */ @@ -241,6 +227,33 @@ export interface IDeltaManager submitSignal(content: any, targetClientId?: string): void; } +/** + * DeltaManager which is used internally by the Fluid layers and not exposed to the end users. + * @internal + */ +export interface IDeltaManagerFull + extends IDeltaManager { + /** + * The queue of inbound delta messages + */ + readonly inbound: IDeltaQueue; + + /** + * The queue of outbound delta messages + */ + readonly outbound: IDeltaQueue; +} + +/** + * Type guard to check if the given deltaManager is of type {@link @fluidframework/container-definitions#IDeltaManagerFull}. + * @internal + */ +export function isIDeltaManagerFull( + deltaManager: IDeltaManager, +): deltaManager is IDeltaManagerFull { + return "inbound" in deltaManager && "outbound" in deltaManager; +} + /** * Events emitted by {@link IDeltaQueue}. * @sealed diff --git a/packages/common/container-definitions/src/index.ts b/packages/common/container-definitions/src/index.ts index 71480fdbce97..945cabe3bc41 100644 --- a/packages/common/container-definitions/src/index.ts +++ b/packages/common/container-definitions/src/index.ts @@ -19,11 +19,13 @@ export type { IConnectionDetails, IDeltaManager, IDeltaManagerEvents, + IDeltaManagerFull, IDeltaQueue, IDeltaQueueEvents, IDeltaSender, ReadOnlyInfo, } from "./deltas.js"; +export { isIDeltaManagerFull } from "./deltas.js"; export type { ContainerWarning, ICriticalContainerError } from "./error.js"; export { ContainerErrorTypes } from "./error.js"; export type { diff --git a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts index b9eec20d469b..3edd6e5850cb 100644 --- a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts +++ b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts @@ -193,6 +193,7 @@ declare type old_as_current_for_Interface_IContainer = requireAssignableTo, TypeOnly> /* @@ -211,6 +212,7 @@ declare type old_as_current_for_Interface_IContainerContext = requireAssignableT * typeValidation.broken: * "Interface_IContainerContext": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IContainerContext = requireAssignableTo, TypeOnly> /* @@ -256,6 +258,7 @@ declare type current_as_old_for_Interface_IContainerLoadMode = requireAssignable * typeValidation.broken: * "Interface_IDeltaManager": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IDeltaManager = requireAssignableTo>, TypeOnly>> /* diff --git a/packages/common/core-interfaces/CHANGELOG.md b/packages/common/core-interfaces/CHANGELOG.md index fe6640c6d311..1663db9cf78b 100644 --- a/packages/common/core-interfaces/CHANGELOG.md +++ b/packages/common/core-interfaces/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/core-interfaces +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/common/core-interfaces/package.json b/packages/common/core-interfaces/package.json index 4ecf3098e4ed..ca1edfe0d413 100644 --- a/packages/common/core-interfaces/package.json +++ b/packages/common/core-interfaces/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/core-interfaces", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid object interfaces", "homepage": "https://fluidframework.com", "repository": { @@ -95,10 +95,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/core-interfaces-previous": "npm:@fluidframework/core-interfaces@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/core-interfaces-previous": "npm:@fluidframework/core-interfaces@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/common/core-utils/CHANGELOG.md b/packages/common/core-utils/CHANGELOG.md index cf4bfd26386c..0a23b0776dac 100644 --- a/packages/common/core-utils/CHANGELOG.md +++ b/packages/common/core-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/core-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/common/core-utils/package.json b/packages/common/core-utils/package.json index d1169ddd5cd0..59dae7de9b3d 100644 --- a/packages/common/core-utils/package.json +++ b/packages/common/core-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/core-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Not intended for use outside the Fluid client repo.", "homepage": "https://fluidframework.com", "repository": { @@ -121,10 +121,10 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/core-utils-previous": "npm:@fluidframework/core-utils@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/core-utils-previous": "npm:@fluidframework/core-utils@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/common/driver-definitions/CHANGELOG.md b/packages/common/driver-definitions/CHANGELOG.md index 1aeb13fd3807..ff5327f79e3a 100644 --- a/packages/common/driver-definitions/CHANGELOG.md +++ b/packages/common/driver-definitions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/driver-definitions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/common/driver-definitions/api-report/driver-definitions.beta.api.md b/packages/common/driver-definitions/api-report/driver-definitions.beta.api.md index 08412511382f..43fc101ca0d8 100644 --- a/packages/common/driver-definitions/api-report/driver-definitions.beta.api.md +++ b/packages/common/driver-definitions/api-report/driver-definitions.beta.api.md @@ -84,7 +84,6 @@ export interface ISummaryHandle { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/packages/common/driver-definitions/api-report/driver-definitions.legacy.alpha.api.md b/packages/common/driver-definitions/api-report/driver-definitions.legacy.alpha.api.md index 42045242e411..1ad7ec6861b9 100644 --- a/packages/common/driver-definitions/api-report/driver-definitions.legacy.alpha.api.md +++ b/packages/common/driver-definitions/api-report/driver-definitions.legacy.alpha.api.md @@ -600,7 +600,6 @@ export interface ISummaryProposal { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/packages/common/driver-definitions/api-report/driver-definitions.legacy.public.api.md b/packages/common/driver-definitions/api-report/driver-definitions.legacy.public.api.md index 73fbbf33ac01..12be7171ef17 100644 --- a/packages/common/driver-definitions/api-report/driver-definitions.legacy.public.api.md +++ b/packages/common/driver-definitions/api-report/driver-definitions.legacy.public.api.md @@ -84,7 +84,6 @@ export interface ISummaryHandle { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/packages/common/driver-definitions/api-report/driver-definitions.public.api.md b/packages/common/driver-definitions/api-report/driver-definitions.public.api.md index 73fbbf33ac01..12be7171ef17 100644 --- a/packages/common/driver-definitions/api-report/driver-definitions.public.api.md +++ b/packages/common/driver-definitions/api-report/driver-definitions.public.api.md @@ -84,7 +84,6 @@ export interface ISummaryHandle { // @public export interface ISummaryTree { groupId?: string; - // (undocumented) tree: { [path: string]: SummaryObject; }; diff --git a/packages/common/driver-definitions/package.json b/packages/common/driver-definitions/package.json index 8cac95db8a2a..1b6c3a06d75e 100644 --- a/packages/common/driver-definitions/package.json +++ b/packages/common/driver-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/driver-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid driver definitions", "homepage": "https://fluidframework.com", "repository": { @@ -94,10 +94,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/driver-definitions-previous": "npm:@fluidframework/driver-definitions@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/driver-definitions-previous": "npm:@fluidframework/driver-definitions@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", diff --git a/packages/common/driver-definitions/src/protocol/summary.ts b/packages/common/driver-definitions/src/protocol/summary.ts index c00ef54f3245..ffc165aa1b82 100644 --- a/packages/common/driver-definitions/src/protocol/summary.ts +++ b/packages/common/driver-definitions/src/protocol/summary.ts @@ -167,7 +167,11 @@ export interface ISummaryAttachment { export interface ISummaryTree { type: SummaryType.Tree; - // TODO type I can infer from SummaryObject. File mode I may want to directly specify so have symlink+exec access + /** + * The object containing all the tree's {@link SummaryObject} children. + * + * @param path - The key to store the SummaryObject at in the current summary tree being generated. Should not contain any "/" characters and should not change when encodeURIComponent is called on it. + */ tree: { [path: string]: SummaryObject }; /** diff --git a/packages/dds/cell/CHANGELOG.md b/packages/dds/cell/CHANGELOG.md index eb4544b47d1c..775a08dad358 100644 --- a/packages/dds/cell/CHANGELOG.md +++ b/packages/dds/cell/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/cell +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/cell/package.json b/packages/dds/cell/package.json index e4fc69aa67be..8ae3f46188a2 100644 --- a/packages/dds/cell/package.json +++ b/packages/dds/cell/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/cell", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed data structure for a single value", "homepage": "https://fluidframework.com", "repository": { @@ -112,10 +112,10 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/cell-previous": "npm:@fluidframework/cell@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/cell-previous": "npm:@fluidframework/cell@~2.5.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", diff --git a/packages/dds/cell/src/packageVersion.ts b/packages/dds/cell/src/packageVersion.ts index 78e234be643f..0263fc611a84 100644 --- a/packages/dds/cell/src/packageVersion.ts +++ b/packages/dds/cell/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/cell"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/counter/CHANGELOG.md b/packages/dds/counter/CHANGELOG.md index c35d24c93175..0fc3ebbc6ce5 100644 --- a/packages/dds/counter/CHANGELOG.md +++ b/packages/dds/counter/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/counter +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/counter/package.json b/packages/dds/counter/package.json index ce5e2d7332cf..0b99fa516e5d 100644 --- a/packages/dds/counter/package.json +++ b/packages/dds/counter/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/counter", - "version": "2.5.0", + "version": "2.10.0", "description": "Counter DDS", "homepage": "https://fluidframework.com", "repository": { @@ -129,11 +129,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", - "@fluidframework/counter-previous": "npm:@fluidframework/counter@~2.4.0", + "@fluidframework/counter-previous": "npm:@fluidframework/counter@2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/packages/dds/counter/src/packageVersion.ts b/packages/dds/counter/src/packageVersion.ts index 7c48a7371946..45aebc79bdc2 100644 --- a/packages/dds/counter/src/packageVersion.ts +++ b/packages/dds/counter/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/counter"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/ink/CHANGELOG.md b/packages/dds/ink/CHANGELOG.md index 452db61fd79f..3deb9b0ac26e 100644 --- a/packages/dds/ink/CHANGELOG.md +++ b/packages/dds/ink/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/ink +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/ink/package.json b/packages/dds/ink/package.json index a2b71455deb1..5888289f91dd 100644 --- a/packages/dds/ink/package.json +++ b/packages/dds/ink/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/ink", - "version": "2.5.0", + "version": "2.10.0", "description": "Ink DDS", "homepage": "https://fluidframework.com", "repository": { @@ -98,9 +98,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", diff --git a/packages/dds/ink/src/packageVersion.ts b/packages/dds/ink/src/packageVersion.ts index b89c2d83ff47..2966ed688712 100644 --- a/packages/dds/ink/src/packageVersion.ts +++ b/packages/dds/ink/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/ink"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/map/.eslintrc.cjs b/packages/dds/map/.eslintrc.cjs index 5a8399436555..c3fd19a9ba22 100644 --- a/packages/dds/map/.eslintrc.cjs +++ b/packages/dds/map/.eslintrc.cjs @@ -14,6 +14,7 @@ module.exports = { // TODO: consider re-enabling once we have addressed how this rule conflicts with our error codes. "unicorn/numeric-separators-style": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/dds/map/CHANGELOG.md b/packages/dds/map/CHANGELOG.md index 56ed436fe19e..d8f52a6f3f6e 100644 --- a/packages/dds/map/CHANGELOG.md +++ b/packages/dds/map/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/map +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/map/package.json b/packages/dds/map/package.json index 475a89024a55..558e0f349443 100644 --- a/packages/dds/map/package.json +++ b/packages/dds/map/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/map", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed map", "homepage": "https://fluidframework.com", "repository": { @@ -142,12 +142,12 @@ "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/map-previous": "npm:@fluidframework/map@~2.4.0", + "@fluidframework/map-previous": "npm:@fluidframework/map@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/dds/map/src/packageVersion.ts b/packages/dds/map/src/packageVersion.ts index ed97eb6981d8..c3b95ce369e3 100644 --- a/packages/dds/map/src/packageVersion.ts +++ b/packages/dds/map/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/map"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/matrix/.eslintrc.cjs b/packages/dds/matrix/.eslintrc.cjs index 9bc510aaeaad..8936746eb678 100644 --- a/packages/dds/matrix/.eslintrc.cjs +++ b/packages/dds/matrix/.eslintrc.cjs @@ -12,6 +12,7 @@ module.exports = { rules: { "@typescript-eslint/no-shadow": "off", "space-before-function-paren": "off", // Off because it conflicts with typescript-formatter + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/dds/matrix/CHANGELOG.md b/packages/dds/matrix/CHANGELOG.md index 158cda2638b8..692375764d0d 100644 --- a/packages/dds/matrix/CHANGELOG.md +++ b/packages/dds/matrix/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/matrix +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/matrix/package.json b/packages/dds/matrix/package.json index 8e89d590b33b..85fe84c2ae93 100644 --- a/packages/dds/matrix/package.json +++ b/packages/dds/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/matrix", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed matrix", "homepage": "https://fluidframework.com", "repository": { @@ -145,12 +145,12 @@ "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/matrix-previous": "npm:@fluidframework/matrix@~2.4.0", + "@fluidframework/matrix-previous": "npm:@fluidframework/matrix@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@tiny-calc/micro": "0.0.0-alpha.5", diff --git a/packages/dds/matrix/src/packageVersion.ts b/packages/dds/matrix/src/packageVersion.ts index e9ebf3961a69..40e9685345a1 100644 --- a/packages/dds/matrix/src/packageVersion.ts +++ b/packages/dds/matrix/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/matrix"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/matrix/src/test/matrix.spec.ts b/packages/dds/matrix/src/test/matrix.spec.ts index 2426469af36c..3780ca0a7754 100644 --- a/packages/dds/matrix/src/test/matrix.spec.ts +++ b/packages/dds/matrix/src/test/matrix.spec.ts @@ -12,6 +12,7 @@ import { IChannelServices, type IChannel, } from "@fluidframework/datastore-definitions/internal"; +import type { ISegmentInternal } from "@fluidframework/merge-tree/internal"; import { MockContainerRuntimeFactory, MockContainerRuntimeFactoryForReconnection, @@ -967,7 +968,7 @@ describe("Matrix1", () => { function findVectorReferenceCount(vector: PermutationVector): number { let count = 0; - vector.walkSegments((segment) => { + vector.walkSegments((segment: ISegmentInternal) => { count += [...(segment.localRefs ?? [])].length; return true; }); diff --git a/packages/dds/merge-tree/.eslintrc.cjs b/packages/dds/merge-tree/.eslintrc.cjs index 44207e07525f..fd0c39169dd0 100644 --- a/packages/dds/merge-tree/.eslintrc.cjs +++ b/packages/dds/merge-tree/.eslintrc.cjs @@ -15,5 +15,6 @@ module.exports = { "no-case-declarations": "off", "prefer-arrow/prefer-arrow-functions": "off", "unicorn/no-useless-spread": "off", // Off because it generates incorrect code in autofixes and cannot distinguish useful copies of arrays from useless ones + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/dds/merge-tree/CHANGELOG.md b/packages/dds/merge-tree/CHANGELOG.md index a0ae154f5d5c..9c054108a401 100644 --- a/packages/dds/merge-tree/CHANGELOG.md +++ b/packages/dds/merge-tree/CHANGELOG.md @@ -1,5 +1,35 @@ # @fluidframework/merge-tree +## 2.5.0 + +### Minor Changes + +- Compilation no longer fails when building with TypeScript's libCheck option ([#22923](https://github.com/microsoft/FluidFramework/pull/22923)) [a1b4cdd45e](https://github.com/microsoft/FluidFramework/commit/a1b4cdd45ee9812e2598ab8d2854333d26a06eb4) + + When compiling code using Fluid Framework with TypeScript's `libCheck` (meaning without [skipLibCheck](https://www.typescriptlang.org/tsconfig/#skipLibCheck)), two compile errors can be encountered: + + ``` + > tsc + + node_modules/@fluidframework/merge-tree/lib/client.d.ts:124:18 - error TS2368: Type parameter name cannot be 'undefined'. + + 124 walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; + ~~~~~~~~~ + + node_modules/@fluidframework/tree/lib/util/utils.d.ts:5:29 - error TS7016: Could not find a declaration file for module '@ungap/structured-clone'. 'node_modules/@ungap/structured-clone/esm/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/ungap__structured-clone` if it exists or add a new declaration (.d.ts) file containing `declare module '@ungap/structured-clone';` + + 5 import structuredClone from "@ungap/structured-clone"; + ~~~~~~~~~~~~~~~~~~~~~~~~~ + ``` + + The first error impacts projects using TypeScript 5.5 or greater and either of the `fluid-framework` or `@fluidframework/merge-tree` packages. + The second error impacts projects using the `noImplicitAny` tsconfig setting and the `fluid-framework` or `@fluidframework/tree` packages. + + Both errors have been fixed. + + This should allow `libCheck` to be reenabled in any impacted projects. + ## 2.4.0 ### Minor Changes diff --git a/packages/dds/merge-tree/api-report/merge-tree.legacy.alpha.api.md b/packages/dds/merge-tree/api-report/merge-tree.legacy.alpha.api.md index 3114c8ea5aa8..1af7c4bd8a1f 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.legacy.alpha.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.legacy.alpha.api.md @@ -5,24 +5,18 @@ ```ts // @alpha -export function appendToMergeTreeDeltaRevertibles(deltaArgs: IMergeTreeDeltaCallbackArgs, revertibles: MergeTreeDeltaRevertible[]): void; - -// @alpha @sealed @deprecated -export interface AttributionPolicy { - attach: (client: Client) => void; - detach: () => void; - // (undocumented) - isAttached: boolean; - serializer: IAttributionCollectionSerializer; +export interface AdjustParams { + delta: number; + max?: number | undefined; + min?: number | undefined; } +// @alpha +export function appendToMergeTreeDeltaRevertibles(deltaArgs: IMergeTreeDeltaCallbackArgs, revertibles: MergeTreeDeltaRevertible[]): void; + // @alpha (undocumented) export abstract class BaseSegment implements ISegment { constructor(properties?: PropertySet); - // @deprecated (undocumented) - ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; - // @deprecated - addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) protected addSerializedProps(jseg: IJSONSegment): void; // (undocumented) @@ -65,14 +59,10 @@ export abstract class BaseSegment implements ISegment { ordinal: string; // (undocumented) properties?: PropertySet; - // @deprecated - propertyManager?: PropertiesManager; // (undocumented) removedClientIds?: number[]; // (undocumented) removedSeq?: number; - // @deprecated (undocumented) - readonly segmentGroups: SegmentGroupCollection; // (undocumented) seq: number; // (undocumented) @@ -87,104 +77,6 @@ export abstract class BaseSegment implements ISegment { wasMovedOnInsert?: boolean | undefined; } -// @alpha @deprecated (undocumented) -export class Client extends TypedEventEmitter { - constructor(specToSegment: (spec: IJSONSegment) => ISegment, logger: ITelemetryLoggerExt, options?: IMergeTreeOptions & PropertySet, getMinInFlightRefSeq?: () => number | undefined); - // (undocumented) - addLongClientId(longClientId: string): void; - annotateMarker(marker: Marker, props: PropertySet): IMergeTreeAnnotateMsg | undefined; - annotateRangeLocal(start: number, end: number, props: PropertySet): IMergeTreeAnnotateMsg | undefined; - // (undocumented) - applyMsg(msg: ISequencedDocumentMessage, local?: boolean): void; - // (undocumented) - applyStashedOp(op: IMergeTreeOp): void; - createLocalReferencePosition(segment: ISegment | "start" | "end", offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; - // (undocumented) - createTextHelper(): IMergeTreeTextHelper; - findReconnectionPosition(segment: ISegment, localSeq: number): number; - // (undocumented) - getClientId(): number; - // (undocumented) - getCollabWindow(): CollaborationWindow; - // (undocumented) - getContainingSegment(pos: number, sequenceArgs?: Pick, localSeq?: number): { - segment: T | undefined; - offset: number | undefined; - }; - // (undocumented) - getCurrentSeq(): number; - // (undocumented) - getLength(): number; - // (undocumented) - getLongClientId(shortClientId: number): string; - // (undocumented) - getMarkerFromId(id: string): ISegment | undefined; - // (undocumented) - getOrAddShortClientId(longClientId: string): number; - getPosition(segment: ISegment | undefined, localSeq?: number): number; - // (undocumented) - getPropertiesAtPosition(pos: number): PropertySet | undefined; - // (undocumented) - getRangeExtentsOfPosition(pos: number): { - posStart: number | undefined; - posAfterEnd: number | undefined; - }; - // (undocumented) - protected getShortClientId(longClientId: string): number; - insertAtReferencePositionLocal(refPos: ReferencePosition, segment: ISegment): IMergeTreeInsertMsg | undefined; - insertSegmentLocal(pos: number, segment: ISegment): IMergeTreeInsertMsg | undefined; - // (undocumented) - load(runtime: IFluidDataStoreRuntime, storage: IChannelStorageService, serializer: IFluidSerializer): Promise<{ - catchupOpsP: Promise; - }>; - localReferencePositionToPosition(lref: ReferencePosition): number; - // (undocumented) - localTransaction(groupOp: IMergeTreeGroupMsg): void; - // (undocumented) - readonly logger: ITelemetryLoggerExt; - // (undocumented) - longClientId: string | undefined; - obliterateRangeLocal(start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg; - peekPendingSegmentGroups(): SegmentGroup | undefined; - // (undocumented) - peekPendingSegmentGroups(count: number): SegmentGroup | SegmentGroup[] | undefined; - posFromRelativePos(relativePos: IRelativePosition): number; - regeneratePendingOp(resetOp: IMergeTreeOp, segmentGroup: SegmentGroup | SegmentGroup[]): IMergeTreeOp; - removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; - removeRangeLocal(start: number, end: number): IMergeTreeRemoveMsg; - resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; - rollback?(op: unknown, localOpMetadata: unknown): void; - searchForMarker(startPos: number, markerLabel: string, forwards?: boolean): Marker | undefined; - serializeGCData(handle: IFluidHandle, handleCollectingSerializer: IFluidSerializer): void; - // (undocumented) - readonly specToSegment: (spec: IJSONSegment) => ISegment; - // (undocumented) - startOrUpdateCollaboration(longClientId: string | undefined, minSeq?: number, currentSeq?: number): void; - // (undocumented) - summarize(runtime: IFluidDataStoreRuntime, handle: IFluidHandle, serializer: IFluidSerializer, catchUpMsgs: ISequencedDocumentMessage[]): ISummaryTreeWithStats; - // (undocumented) - updateMinSeq(minSeq: number): void; - // (undocumented) - protected walkAllSegments(action: (segment: ISegment, accum?: TClientData) => boolean, accum?: TClientData): boolean; - // (undocumented) - walkSegments(handler: ISegmentAction, start: number | undefined, end: number | undefined, accum: TClientData, splitRange?: boolean): void; - // (undocumented) - walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; -} - -// @alpha @deprecated (undocumented) -export class CollaborationWindow { - // (undocumented) - clientId: number; - // (undocumented) - collaborating: boolean; - currentSeq: number; - // (undocumented) - loadFrom(a: CollaborationWindow): void; - localSeq: number; - minSeq: number; -} - // @alpha export function discardMergeTreeDeltaRevertible(revertibles: MergeTreeDeltaRevertible[]): void; @@ -244,16 +136,6 @@ export interface IAttributionCollectionSpec { }>; } -// @alpha @deprecated -export interface IClientEvents { - // (undocumented) - (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void; - // (undocumented) - (event: "delta", listener: (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, target: IEventThisPlaceHolder) => void): void; - // (undocumented) - (event: "maintenance", listener: (args: IMergeTreeMaintenanceCallbackArgs, deltaArgs: IMergeTreeDeltaOpArgs | undefined, target: IEventThisPlaceHolder) => void): void; -} - // @alpha (undocumented) export interface IJSONMarkerSegment extends IJSONSegment { // (undocumented) @@ -286,8 +168,28 @@ export interface IMergeNodeCommon { ordinal: string; } +// @alpha (undocumented) +export interface IMergeTreeAnnotateAdjustMsg extends IMergeTreeDelta { + // (undocumented) + adjust: Record; + // (undocumented) + pos1?: number; + // (undocumented) + pos2?: number; + // (undocumented) + props?: never; + // (undocumented) + relativePos1?: undefined; + // (undocumented) + relativePos2?: undefined; + // (undocumented) + type: typeof MergeTreeDeltaType.ANNOTATE; +} + // @alpha (undocumented) export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { + // (undocumented) + adjust?: never; // (undocumented) pos1?: number; // (undocumented) @@ -302,12 +204,6 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { type: typeof MergeTreeDeltaType.ANNOTATE; } -// @alpha @deprecated (undocumented) -export interface IMergeTreeAttributionOptions { - policyFactory?: () => AttributionPolicy; - track?: boolean; -} - // @alpha (undocumented) export interface IMergeTreeDelta { type: MergeTreeDeltaType; @@ -392,7 +288,6 @@ export type IMergeTreeOp = IMergeTreeDeltaOp | IMergeTreeGroupMsg; // @alpha (undocumented) export interface IMergeTreeOptions { - attribution?: IMergeTreeAttributionOptions; // (undocumented) catchUpBlobName?: string; mergeTreeEnableObliterate?: boolean; @@ -424,12 +319,6 @@ export interface IMergeTreeSegmentDelta { segment: ISegment; } -// @alpha @deprecated (undocumented) -export interface IMergeTreeTextHelper { - // (undocumented) - getText(refSeq: number, clientId: number, placeholder: string, start?: number, end?: number): string; -} - // @alpha export interface IMoveInfo { localMovedSeq?: number; @@ -464,10 +353,6 @@ export interface IRemovalInfo { // @alpha export interface ISegment extends IMergeNodeCommon, Partial, Partial { - // @deprecated (undocumented) - ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; - // @deprecated - addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) append(segment: ISegment): void; attribution?: IAttributionCollection; @@ -482,10 +367,6 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti localRemovedSeq?: number; localSeq?: number; properties?: PropertySet; - // @deprecated - propertyManager?: PropertiesManager; - // @deprecated (undocumented) - readonly segmentGroups: SegmentGroupCollection; seq?: number; // (undocumented) splitAt(pos: number): ISegment | undefined; @@ -594,18 +475,6 @@ export class Marker extends BaseSegment implements ReferencePosition, ISegment { readonly type = "Marker"; } -// @alpha @deprecated (undocumented) -export class MergeNode implements IMergeNodeCommon { - // (undocumented) - cachedLength: number; - // (undocumented) - index: number; - // (undocumented) - isLeaf(): this is ISegment; - // (undocumented) - ordinal: string; -} - // @alpha (undocumented) export type MergeTreeDeltaOperationType = typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE | typeof MergeTreeDeltaType.OBLITERATE; @@ -659,50 +528,11 @@ export interface MergeTreeRevertibleDriver { removeRange(start: number, end: number): void; } -// @alpha @deprecated (undocumented) -export interface ObliterateInfo { - // (undocumented) - clientId: number; - // (undocumented) - end: LocalReferencePosition; - // (undocumented) - localSeq: number | undefined; - // (undocumented) - refSeq: number; - // (undocumented) - segmentGroup: SegmentGroup | undefined; - // (undocumented) - seq: number; - // (undocumented) - start: LocalReferencePosition; -} - -// @alpha @deprecated (undocumented) -export class PropertiesManager { - // (undocumented) - ackPendingProperties(annotateOp: IMergeTreeAnnotateMsg): void; - // (undocumented) - addProperties(oldProps: PropertySet, newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; - // (undocumented) - copyTo(oldProps: PropertySet, newProps: PropertySet | undefined, newManager: PropertiesManager): PropertySet | undefined; - hasPendingProperties(props: PropertySet): boolean; - // (undocumented) - hasPendingProperty(key: string): boolean; -} - -// @alpha @deprecated (undocumented) -export enum PropertiesRollback { - None = 0, - Rollback = 1 -} - // @alpha export type PropertySet = MapLike; // @alpha export interface ReferencePosition { - // @deprecated (undocumented) - addProperties(newProps: PropertySet): void; getOffset(): number; getSegment(): ISegment | undefined; // (undocumented) @@ -737,39 +567,6 @@ export const reservedMarkerIdKey = "markerId"; // @alpha export function revertMergeTreeDeltaRevertibles(driver: MergeTreeRevertibleDriver, revertibles: MergeTreeDeltaRevertible[]): void; -// @alpha @deprecated (undocumented) -export interface SegmentGroup { - // (undocumented) - localSeq?: number; - // (undocumented) - obliterateInfo?: ObliterateInfo; - // (undocumented) - previousProps?: PropertySet[]; - // (undocumented) - refSeq: number; - // (undocumented) - segments: ISegment[]; -} - -// @alpha @deprecated (undocumented) -export class SegmentGroupCollection { - constructor(segment: ISegment); - // (undocumented) - copyTo(segment: ISegment): void; - // (undocumented) - dequeue(): SegmentGroup | undefined; - // (undocumented) - get empty(): boolean; - // (undocumented) - enqueue(segmentGroup: SegmentGroup): void; - // (undocumented) - pop?(): SegmentGroup | undefined; - // (undocumented) - remove?(segmentGroup: SegmentGroup): boolean; - // (undocumented) - get size(): number; -} - // @alpha (undocumented) export interface SequenceOffsets { // (undocumented) diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index c9c334125fa4..8de574b62450 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/merge-tree", - "version": "2.5.0", + "version": "2.10.0", "description": "Merge tree", "homepage": "https://fluidframework.com", "repository": { @@ -152,11 +152,11 @@ "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-pairwise-generator": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@~2.4.0", + "@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/diff": "^3.5.1", @@ -177,7 +177,87 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Class_BaseSegment": { + "backCompat": false + }, + "Class_Marker": { + "backCompat": false + }, + "Class_MergeNode": { + "backCompat": false, + "forwardCompat": false + }, + "Class_TextSegment": { + "backCompat": false + }, + "Class_TrackingGroup": { + "backCompat": false + }, + "Class_SegmentGroupCollection": { + "backCompat": false, + "forwardCompat": false + }, + "ClassStatics_BaseSegment": { + "backCompat": false + }, + "ClassStatics_Marker": { + "backCompat": false + }, + "ClassStatics_MergeNode": { + "backCompat": false + }, + "ClassStatics_TextSegment": { + "backCompat": false + }, + "ClassStatics_TrackingGroup": { + "backCompat": false + }, + "ClassStatics_SegmentGroupCollection": { + "backCompat": false + }, + "Interface_IMergeTreeDeltaCallbackArgs": { + "backCompat": false + }, + "Interface_IMergeTreeMaintenanceCallbackArgs": { + "backCompat": false + }, + "Interface_IMergeTreeSegmentDelta": { + "backCompat": false + }, + "Interface_IMoveInfo": { + "backCompat": false + }, + "Interface_ISegment": { + "backCompat": false + }, + "Interface_ITrackingGroup": { + "backCompat": false + }, + "Interface_ObliterateInfo": { + "backCompat": false + }, + "Interface_ReferencePosition": { + "backCompat": false + }, + "Interface_SegmentGroup": { + "backCompat": false + }, + + "TypeAlias_MergeTreeDeltaRevertible": { + "backCompat": false + }, + "TypeAlias_Trackable": { + "backCompat": false + }, + "Class_PropertiesManager": { + "forwardCompat": false, + "backCompat": false + }, + "ClassStatics_PropertiesManager": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index ac734cc5a70f..5acbd292f2e0 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -29,7 +29,11 @@ import { MergeTreeTextHelper } from "./MergeTreeTextHelper.js"; import { DoublyLinkedList, RedBlackTree } from "./collections/index.js"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js"; import { LocalReferencePosition, SlidingPreference } from "./localReference.js"; -import { IMergeTreeOptions, MergeTree, errorIfOptionNotTrue } from "./mergeTree.js"; +import { + MergeTree, + errorIfOptionNotTrue, + type IMergeTreeOptionsInternal, +} from "./mergeTree.js"; import type { IMergeTreeClientSequenceArgs, IMergeTreeDeltaCallbackArgs, @@ -100,9 +104,7 @@ export interface IIntegerRange { * Emitted before this client's merge-tree normalizes its segments on reconnect, potentially * ordering them. Useful for DDS-like consumers built atop the merge-tree to compute any information * they need for rebasing their ops on reconnection. - * @legacy - * @alpha - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export interface IClientEvents { (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void; @@ -125,9 +127,12 @@ export interface IClientEvents { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @legacy - * @alpha + * This class encapsulates a merge-tree, and provides a local client specific view over it and + * the capability to modify it as the local client. Additionally it provides + * binding for processing remote ops on the encapsulated merge tree, and projects local and remote events + * caused by all modification to the underlying merge-tree. + * + * @internal */ export class Client extends TypedEventEmitter { public longClientId: string | undefined; @@ -155,7 +160,7 @@ export class Client extends TypedEventEmitter { constructor( public readonly specToSegment: (spec: IJSONSegment) => ISegment, public readonly logger: ITelemetryLoggerExt, - options?: IMergeTreeOptions & PropertySet, + options?: IMergeTreeOptionsInternal & PropertySet, private readonly getMinInFlightRefSeq: () => number | undefined = (): undefined => undefined, ) { @@ -857,7 +862,7 @@ export class Client extends TypedEventEmitter { private resetPendingDeltaToOps( resetOp: IMergeTreeDeltaOp, // eslint-disable-next-line import/no-deprecated - segmentGroup: SegmentGroup, + segmentGroup: SegmentGroup, ): IMergeTreeDeltaOp[] { assert(!!segmentGroup, 0x033 /* "Segment group undefined" */); const NACKedSegmentGroup = this.pendingRebase?.shift()?.data; @@ -890,7 +895,7 @@ export class Client extends TypedEventEmitter { a.ordinal < b.ordinal ? -1 : 1, )) { assert( - segment.segmentGroups.remove?.(segmentGroup) === true, + segment.segmentGroups?.remove?.(segmentGroup) === true, 0x035 /* "Segment group not in segment pending queue" */, ); assert( @@ -1010,7 +1015,6 @@ export class Client extends TypedEventEmitter { opList.push(newOp); } } else if (newOp) { - // eslint-disable-next-line import/no-deprecated const newSegmentGroup: SegmentGroup = { segments: [], localSeq: segmentGroup.localSeq, @@ -1174,7 +1178,6 @@ export class Client extends TypedEventEmitter { segmentGroup: SegmentGroup | SegmentGroup[], ): IMergeTreeOp { if (this.pendingRebase === undefined || this.pendingRebase.empty) { - // eslint-disable-next-line import/no-deprecated let firstGroup: SegmentGroup; if (Array.isArray(segmentGroup)) { if (segmentGroup.length === 0) { diff --git a/packages/dds/merge-tree/src/collections/index.ts b/packages/dds/merge-tree/src/collections/index.ts index 933e71e206e2..bc9dc7711abc 100644 --- a/packages/dds/merge-tree/src/collections/index.ts +++ b/packages/dds/merge-tree/src/collections/index.ts @@ -3,7 +3,13 @@ * Licensed under the MIT License. */ -export { DoublyLinkedList, ListNode, ListNodeRange, walkList } from "./list.js"; +export { + DoublyLinkedList, + ListNode, + ListNodeRange, + walkList, + iterateListValuesWhile, +} from "./list.js"; export { ConflictAction, Dictionary, diff --git a/packages/dds/merge-tree/src/collections/list.ts b/packages/dds/merge-tree/src/collections/list.ts index 678827bdb071..1c878d02bd3a 100644 --- a/packages/dds/merge-tree/src/collections/list.ts +++ b/packages/dds/merge-tree/src/collections/list.ts @@ -273,3 +273,32 @@ export function walkList( } return true; } + +/** + * Creates a lazily evaluated iterable which returns values while the predicate returns true, + * and stops iterating at the first value where the predicate is false. + * @param start - the node to start the iteration from + * @param includePredicate - determine if the current value be included in the iteration or stop if iteration + */ +export function iterateListValuesWhile( + start: ListNode | undefined, + includePredicate: (n: ListNode) => boolean, +): Iterable { + let next: ListNode | undefined = start; + const iterator: IterableIterator = { + next: (): IteratorResult => { + if (next !== undefined) { + const current = next; + next = current.next; + if (includePredicate(current) === true) { + return { value: current.data, done: false }; + } + } + return { done: true, value: undefined }; + }, + [Symbol.iterator]() { + return this; + }, + }; + return iterator; +} diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index 641300799a5b..75d1c454ca89 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -42,6 +42,7 @@ export { AttributionPolicy, IMergeTreeAttributionOptions, IMergeTreeOptions, + IMergeTreeOptionsInternal, getSlideToSegoff, } from "./mergeTree.js"; export { @@ -65,7 +66,6 @@ export { ISegment, ISegmentAction, Marker, - MergeNode, reservedMarkerIdKey, reservedMarkerSimpleTypeKey, SegmentGroup, @@ -88,6 +88,7 @@ export { createObliterateRangeOp, } from "./opBuilder.js"; export { + AdjustParams, IJSONSegment, IMarkerDef, IMergeTreeAnnotateMsg, @@ -97,6 +98,7 @@ export { IMergeTreeInsertMsg, IMergeTreeOp, IMergeTreeRemoveMsg, + IMergeTreeAnnotateAdjustMsg, IRelativePosition, MergeTreeDeltaType, ReferenceType, @@ -123,8 +125,12 @@ export { reservedRangeLabelsKey, reservedTileLabelsKey, } from "./referencePositions.js"; -export { SegmentGroupCollection } from "./segmentGroupCollection.js"; -export { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js"; +export { + PropsOrAdjust, + copyPropertiesAndManager, + PropertiesManager, + PropertiesRollback, +} from "./segmentPropertiesManager.js"; export { InteriorSequencePlace, Side, diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 05ce7f06be2d..60f8aa20daa1 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -10,7 +10,6 @@ import { assert, Heap, IComparer } from "@fluidframework/core-utils/internal"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils/internal"; import { IAttributionCollectionSerializer } from "./attributionCollection.js"; -// eslint-disable-next-line import/no-deprecated import { Client } from "./client.js"; import { DoublyLinkedList, ListNode } from "./collections/index.js"; import { @@ -80,7 +79,7 @@ import { } from "./ops.js"; import { PartialSequenceLengths } from "./partialLengths.js"; import { PerspectiveImpl, isSegmentPresent } from "./perspective.js"; -import { PropertySet, clone, createMap, extend, extendIfUndefined } from "./properties.js"; +import { PropertySet, createMap, extend, extendIfUndefined } from "./properties.js"; import { DetachedReferencePosition, ReferencePosition, @@ -91,7 +90,11 @@ import { // eslint-disable-next-line import/no-deprecated import { SegmentGroupCollection } from "./segmentGroupCollection.js"; // eslint-disable-next-line import/no-deprecated -import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js"; +import { + copyPropertiesAndManager, + PropertiesManager, + PropertiesRollback, +} from "./segmentPropertiesManager.js"; import { Side, type InteriorSequencePlace } from "./sequencePlace.js"; import { SortedSegmentSet } from "./sortedSegmentSet.js"; import { zamboniSegments } from "./zamboni.js"; @@ -143,6 +146,74 @@ const LRUSegmentComparer: IComparer = { compare: (a, b) => a.maxSeq - b.maxSeq, }; +function ackSegment( + segment: ISegmentLeaf, + segmentGroup: SegmentGroup, + opArgs: IMergeTreeDeltaOpArgs, +): boolean { + const currentSegmentGroup = segment.segmentGroups?.dequeue(); + assert(currentSegmentGroup === segmentGroup, 0x043 /* "On ack, unexpected segmentGroup!" */); + assert(opArgs.sequencedMessage !== undefined, "must have sequencedMessage"); + const { + op, + sequencedMessage: { sequenceNumber, minimumSequenceNumber }, + } = opArgs; + switch (op.type) { + case MergeTreeDeltaType.ANNOTATE: { + assert( + !!segment.propertyManager, + 0x044 /* "On annotate ack, missing segment property manager!" */, + ); + segment.propertyManager.ack(sequenceNumber, minimumSequenceNumber, op); + return true; + } + + case MergeTreeDeltaType.INSERT: { + assert( + segment.seq === UnassignedSequenceNumber, + 0x045 /* "On insert, seq number already assigned!" */, + ); + segment.seq = sequenceNumber; + segment.localSeq = undefined; + return true; + } + + case MergeTreeDeltaType.REMOVE: { + const removalInfo: IRemovalInfo | undefined = toRemovalInfo(segment); + assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */); + segment.localRemovedSeq = undefined; + if (removalInfo.removedSeq === UnassignedSequenceNumber) { + removalInfo.removedSeq = sequenceNumber; + return true; + } + return false; + } + + case MergeTreeDeltaType.OBLITERATE: + case MergeTreeDeltaType.OBLITERATE_SIDED: { + const moveInfo: IMoveInfo | undefined = toMoveInfo(segment); + assert(moveInfo !== undefined, 0x86e /* On obliterate ack, missing move info! */); + const obliterateInfo = segmentGroup.obliterateInfo; + assert(obliterateInfo !== undefined, 0xa40 /* must have obliterate info */); + segment.localMovedSeq = obliterateInfo.localSeq = undefined; + const seqIdx = moveInfo.movedSeqs.indexOf(UnassignedSequenceNumber); + assert(seqIdx !== -1, 0x86f /* expected movedSeqs to contain unacked seq */); + moveInfo.movedSeqs[seqIdx] = sequenceNumber; + + if (moveInfo.movedSeq === UnassignedSequenceNumber) { + moveInfo.movedSeq = sequenceNumber; + return true; + } + + return false; + } + + default: { + throw new Error(`${op.type} is in unrecognized operation type`); + } + } +} + /** * @legacy * @alpha @@ -177,11 +248,6 @@ export interface IMergeTreeOptions { */ newMergeTreeSnapshotFormat?: boolean; - /** - * Options related to attribution - */ - attribution?: IMergeTreeAttributionOptions; - /** * Enables support for the obliterate operation -- a stronger form of remove * which deletes concurrently inserted segments @@ -212,6 +278,17 @@ export interface IMergeTreeOptions { */ mergeTreeEnableSidedObliterate?: boolean; } + +/** + * @internal + */ +export interface IMergeTreeOptionsInternal extends IMergeTreeOptions { + /** + * Options related to attribution + */ + attribution?: IMergeTreeAttributionOptions; +} + export function errorIfOptionNotTrue( options: IMergeTreeOptions | undefined, option: keyof IMergeTreeOptions, @@ -222,9 +299,7 @@ export function errorIfOptionNotTrue( } /** - * @legacy - * @alpha - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export interface IMergeTreeAttributionOptions { /** @@ -250,9 +325,7 @@ export interface IMergeTreeAttributionOptions { /** * Implements policy dictating which kinds of operations should be attributed and how. * @sealed - * @legacy - * @alpha - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export interface AttributionPolicy { /** @@ -262,7 +335,6 @@ export interface AttributionPolicy { * * This must be done in an eventually consistent fashion. */ - // eslint-disable-next-line import/no-deprecated attach: (client: Client) => void; /** * Disables tracking attribution information on segments. @@ -477,7 +549,6 @@ class Obliterates { const overlapping: ObliterateInfo[] = []; for (const start of this.startOrdered.items) { if (start.getSegment()!.ordinal <= seg.ordinal) { - // eslint-disable-next-line import/no-deprecated const ob = start.properties?.obliterate as ObliterateInfo; if (ob.end.getSegment()!.ordinal >= seg.ordinal) { overlapping.push(ob); @@ -527,7 +598,7 @@ export class MergeTree { private readonly obliterates = new Obliterates(this); - public constructor(public options?: IMergeTreeOptions) { + public constructor(public options?: IMergeTreeOptionsInternal) { this._root = this.makeBlock(0); this._root.mergeTree = this; this.attributionPolicy = options?.attribution?.policyFactory?.(); @@ -1234,7 +1305,7 @@ export class MergeTree { const deltaSegments: IMergeTreeSegmentDelta[] = []; const overlappingRemoves: boolean[] = []; pendingSegmentGroup.segments.map((pendingSegment: ISegmentLeaf) => { - const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs); + const overlappingRemove = !ackSegment(pendingSegment, pendingSegmentGroup, opArgs); overwrite = overlappingRemove || overwrite; @@ -1250,11 +1321,9 @@ export class MergeTree { }); }); - if ( - opArgs.op.type === MergeTreeDeltaType.OBLITERATE || - opArgs.op.type === MergeTreeDeltaType.OBLITERATE_SIDED - ) { - this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo!); + if (pendingSegmentGroup.obliterateInfo !== undefined) { + pendingSegmentGroup.obliterateInfo.seq = seq; + this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo); } // Perform slides after all segments have been acked, so that @@ -1538,10 +1607,8 @@ export class MergeTree { continue; } - // eslint-disable-next-line import/no-deprecated let oldest: ObliterateInfo | undefined; let normalizedOldestSeq: number = 0; - // eslint-disable-next-line import/no-deprecated let newest: ObliterateInfo | undefined; let normalizedNewestSeq: number = 0; const movedClientIds: number[] = []; @@ -1615,28 +1682,14 @@ export class MergeTree { const next: ISegmentLeaf = segment.splitAt(pos)!; if (segment?.segmentGroups) { - // eslint-disable-next-line import/no-deprecated next.segmentGroups ??= new SegmentGroupCollection(next); - segment.segmentGroups.copyTo(next); + segment.segmentGroups.copyTo(next.segmentGroups); } if (segment.prevObliterateByInserter) { next.prevObliterateByInserter = segment.prevObliterateByInserter; } - - if (segment.properties) { - if (segment.propertyManager === undefined) { - next.properties = clone(segment.properties); - } else { - // eslint-disable-next-line import/no-deprecated - next.propertyManager ??= new PropertiesManager(); - next.properties = segment.propertyManager.copyTo( - segment.properties, - next.properties, - next.propertyManager, - ); - } - } + copyPropertiesAndManager(segment, next); if (segment.localRefs) { segment.localRefs.split(pos, next); } @@ -1876,13 +1929,12 @@ export class MergeTree { 0x5ad /* Cannot change the markerId of an existing marker */, ); - // eslint-disable-next-line import/no-deprecated const propertyManager = (segment.propertyManager ??= new PropertiesManager()); - const properties = (segment.properties ??= createMap()); - const propertyDeltas = propertyManager.addProperties( - properties, - props, + const propertyDeltas = propertyManager.handleProperties( + { props }, + segment, seq, + this.collabWindow.minSeq, this.collabWindow.collaborating, rollback, ); @@ -2107,7 +2159,7 @@ export class MergeTree { this.slideAckedRemovedSegmentReferences(localOverlapWithRefs); // opArgs == undefined => test code - if (movedSegments.length > 0) { + if (start.pos !== end.pos || start.side !== end.side) { this.mergeTreeDeltaCallback?.(opArgs, { operation: MergeTreeDeltaType.OBLITERATE, deltaSegments: movedSegments, @@ -2363,7 +2415,6 @@ export class MergeTree { UniversalSequenceNumber, { op: annotateOp }, - // eslint-disable-next-line import/no-deprecated PropertiesRollback.Rollback, ); i++; diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 86cead72358e..955ff2e66988 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -15,9 +15,8 @@ import { UniversalSequenceNumber, } from "./constants.js"; import { LocalReferenceCollection, type LocalReferencePosition } from "./localReference.js"; -import { IMergeTreeDeltaOpArgs } from "./mergeTreeDeltaCallback.js"; import { TrackingGroupCollection } from "./mergeTreeTracking.js"; -import { IJSONSegment, IMarkerDef, MergeTreeDeltaType, ReferenceType } from "./ops.js"; +import { IJSONSegment, IMarkerDef, ReferenceType } from "./ops.js"; import { computeHierarchicalOrdinal } from "./ordinal.js"; import type { PartialSequenceLengths } from "./partialLengths.js"; import { PropertySet, clone, createMap, type MapLike } from "./properties.js"; @@ -29,7 +28,7 @@ import { // eslint-disable-next-line import/no-deprecated import { SegmentGroupCollection } from "./segmentGroupCollection.js"; // eslint-disable-next-line import/no-deprecated -import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js"; +import { PropertiesManager } from "./segmentPropertiesManager.js"; /** * Common properties for a node in a merge tree. @@ -77,7 +76,6 @@ export type ISegmentLeaf = ISegmentInternal & { segmentGroups?: SegmentGroupCollection; // eslint-disable-next-line import/no-deprecated propertyManager?: PropertiesManager; - /** * If a segment is inserted into an obliterated range, * but the newest obliteration of that range was by the inserting client, @@ -215,11 +213,7 @@ export function toMoveInfo(maybe: Partial | undefined): IMoveInfo | u */ export interface ISegment extends IMergeNodeCommon, Partial, Partial { readonly type: string; - /** - * @deprecated - This property should not be used externally and will be removed in a subsequent release. - */ - // eslint-disable-next-line import/no-deprecated - readonly segmentGroups: SegmentGroupCollection; + readonly trackingCollection: TrackingGroupCollection; /** * Whether or not this segment is a special segment denoting the start or @@ -253,18 +247,12 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti */ attribution?: IAttributionCollection; - /** - * Manages pending local state for properties on this segment. - * - * @deprecated - This property should not be used externally and will be removed in a subsequent release. - */ - // eslint-disable-next-line import/no-deprecated - propertyManager?: PropertiesManager; /** * Local seq at which this segment was inserted. * This is defined if and only if the insertion of the segment is pending ack, i.e. `seq` is UnassignedSequenceNumber. * Once the segment is acked, this field is cleared. * + * @privateRemarks * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq. */ localSeq?: number; @@ -274,6 +262,8 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti * will be updated to the seq at which that client removed this segment. * * Like {@link ISegment.localSeq}, this field is cleared once the local removal of the segment is acked. + * + * @privateRemarks * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq. */ localRemovedSeq?: number; @@ -295,21 +285,6 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti */ properties?: PropertySet; - /** - * Add properties to this segment via annotation. - * - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - * - * @remarks This function should not be called directly. Properties should - * be added through the `annotateRange` functions. - */ - addProperties( - newProps: PropertySet, - seq?: number, - collaborating?: boolean, - // eslint-disable-next-line import/no-deprecated - rollback?: PropertiesRollback, - ): PropertySet; clone(): ISegment; canAppend(segment: ISegment): boolean; append(segment: ISegment): void; @@ -317,20 +292,6 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti // Changing this to something other than any would break consumers. // eslint-disable-next-line @typescript-eslint/no-explicit-any toJSONObject(): any; - /** - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - * Acks the current segment against the segment group, op, and merge tree. - * - * @param segmentGroup - Pending segment group associated with this op. - * @param opArgs - Information about the op that was acked - * @returns `true` if the op modifies the segment, otherwise `false`. - * The only current false case is overlapping remove, where a segment is removed - * by a previously sequenced operation before the current operation is acked. - * @throws - error if the segment state doesn't match segment group or op. - * E.g. if the segment group is not first in the pending queue, or - * an inserted segment does not have unassigned sequence number. - */ - ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; } /** @@ -386,7 +347,7 @@ export interface BlockAction { export interface NodeAction { // eslint-disable-next-line @typescript-eslint/prefer-function-type ( - node: MergeNode, + node: IMergeNode, pos: number, refSeq: number, clientId: number, @@ -417,10 +378,7 @@ export interface SegmentActions { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @legacy - * @alpha - * @privateRemarks After deprecation period this interface should be made internal + * @internal */ export interface ObliterateInfo { start: LocalReferencePosition; @@ -433,33 +391,16 @@ export interface ObliterateInfo { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @legacy - * @alpha + * @internal */ -export interface SegmentGroup { - segments: ISegment[]; +export interface SegmentGroup { + segments: S[]; previousProps?: PropertySet[]; localSeq?: number; refSeq: number; obliterateInfo?: ObliterateInfo; } -/** - * @legacy - * @alpha - * @deprecated - unused and will be removed - */ -export class MergeNode implements IMergeNodeCommon { - index: number = 0; - ordinal: string = ""; - cachedLength: number = 0; - - isLeaf(): this is ISegment { - return false; - } -} - /** * Note that the actual branching factor of the MergeTree is `MaxNodesInBlock - 1`. This is because * the MergeTree always inserts first, then checks for overflow and splits if the child count equals @@ -561,23 +502,12 @@ export abstract class BaseSegment implements ISegment { public ordinal: string = ""; public cachedLength: number = 0; - /** - * {@inheritdoc ISegment.segmentGroups} - * @deprecated - This property should not be used externally and will be removed in a subsequent release. - */ - // eslint-disable-next-line import/no-deprecated - public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this); public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection( this, ); /***/ public attribution?: IAttributionCollection; - /** - * {@inheritdoc ISegment.propertyManager} - * @deprecated - This property should not be used externally and will be removed in a subsequent release. - */ - // eslint-disable-next-line import/no-deprecated - public propertyManager?: PropertiesManager; + public properties?: PropertySet; public localRefs?: LocalReferenceCollection; public abstract readonly type: string; @@ -591,31 +521,6 @@ export abstract class BaseSegment implements ISegment { } } - /** - * {@inheritdoc ISegment.addProperties} - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - */ - public addProperties( - newProps: PropertySet, - seq?: number, - collaborating?: boolean, - // eslint-disable-next-line import/no-deprecated - rollback: PropertiesRollback = PropertiesRollback.None, - ): PropertySet { - // eslint-disable-next-line import/no-deprecated - this.propertyManager ??= new PropertiesManager(); - // A property set must be able to hold properties of any type, so the any is needed. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.properties ??= createMap(); - return this.propertyManager.addProperties( - this.properties, - newProps, - seq, - collaborating, - rollback, - ); - } - public hasProperty(key: string): boolean { return !!this.properties && this.properties[key] !== undefined; } @@ -653,73 +558,6 @@ export abstract class BaseSegment implements ISegment { // eslint-disable-next-line @typescript-eslint/no-explicit-any public abstract toJSONObject(): any; - /** - * {@inheritdoc ISegment.ack} - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - */ - public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean { - const currentSegmentGroup = this.segmentGroups.dequeue(); - assert( - currentSegmentGroup === segmentGroup, - 0x043 /* "On ack, unexpected segmentGroup!" */, - ); - switch (opArgs.op.type) { - case MergeTreeDeltaType.ANNOTATE: { - assert( - !!this.propertyManager, - 0x044 /* "On annotate ack, missing segment property manager!" */, - ); - this.propertyManager.ackPendingProperties(opArgs.op); - return true; - } - - case MergeTreeDeltaType.INSERT: { - assert( - this.seq === UnassignedSequenceNumber, - 0x045 /* "On insert, seq number already assigned!" */, - ); - this.seq = opArgs.sequencedMessage!.sequenceNumber; - this.localSeq = undefined; - return true; - } - - case MergeTreeDeltaType.REMOVE: { - const removalInfo: IRemovalInfo | undefined = toRemovalInfo(this); - assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */); - this.localRemovedSeq = undefined; - if (removalInfo.removedSeq === UnassignedSequenceNumber) { - removalInfo.removedSeq = opArgs.sequencedMessage!.sequenceNumber; - return true; - } - return false; - } - - case MergeTreeDeltaType.OBLITERATE: - case MergeTreeDeltaType.OBLITERATE_SIDED: { - const moveInfo: IMoveInfo | undefined = toMoveInfo(this); - assert(moveInfo !== undefined, 0x86e /* On obliterate ack, missing move info! */); - const obliterateInfo = segmentGroup.obliterateInfo; - assert(obliterateInfo !== undefined, 0xa40 /* must have obliterate info */); - this.localMovedSeq = obliterateInfo.localSeq = undefined; - const seqIdx = moveInfo.movedSeqs.indexOf(UnassignedSequenceNumber); - assert(seqIdx !== -1, 0x86f /* expected movedSeqs to contain unacked seq */); - moveInfo.movedSeqs[seqIdx] = obliterateInfo.seq = - opArgs.sequencedMessage!.sequenceNumber; - - if (moveInfo.movedSeq === UnassignedSequenceNumber) { - moveInfo.movedSeq = opArgs.sequencedMessage!.sequenceNumber; - return true; - } - - return false; - } - - default: { - throw new Error(`${opArgs.op.type} is in unrecognized operation type`); - } - } - } - public splitAt(pos: number): ISegment | undefined { if (pos <= 0) { return undefined; @@ -896,9 +734,10 @@ export class Marker extends BaseSegment implements ReferencePosition, ISegment { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @legacy - * @alpha + * This class is used to track facts about the current window of collaboration. This window is defined by the server + * specified minimum sequence number to the last sequence number seen. Additionally, it track state for outstanding + * local operations. + * @internal */ export class CollaborationWindow { clientId = LocalClientId; diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index ccdc0b78a257..3281d56e496a 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -195,6 +195,44 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { relativePos2?: IRelativePosition; // eslint-disable-next-line @typescript-eslint/no-explicit-any props: Record; + adjust?: never; +} + +/** + * Used to define per key adjustments in an {@link IMergeTreeAnnotateAdjustMsg} + * @alpha + * @legacy + */ +export interface AdjustParams { + /** + * The adjustment delta which will be summed with the current value if it is a number, + * or summed with zero if the current value is not a number. + */ + delta: number; + /** + * An optional minimum value for the computed value of the key this adjustment is applied to. + * The minimum will be applied after the value is applied. + */ + min?: number | undefined; + /** + * An optional maximum value for the computed value of the key this adjustment is applied to. + * The maximum will be applied after the value is applied. + */ + max?: number | undefined; +} + +/** + * @legacy + * @alpha + */ +export interface IMergeTreeAnnotateAdjustMsg extends IMergeTreeDelta { + type: typeof MergeTreeDeltaType.ANNOTATE; + pos1?: number; + pos2?: number; + relativePos1?: undefined; + relativePos2?: undefined; + props?: never; + adjust: Record; } /** diff --git a/packages/dds/merge-tree/src/referencePositions.ts b/packages/dds/merge-tree/src/referencePositions.ts index bd334c92e066..f0d76681f0f7 100644 --- a/packages/dds/merge-tree/src/referencePositions.ts +++ b/packages/dds/merge-tree/src/referencePositions.ts @@ -97,18 +97,6 @@ export interface ReferencePosition { */ getOffset(): number; - /** - * @param newProps - Properties to add to this reference. - * @remarks Note that merge-tree does not broadcast changes to other clients. It is up to the consumer - * to ensure broadcast happens if that is desired. - * - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - * - * @privateRemarks This interface is used by both marker segments and local reference positions. We will remove - * this function from segments, but keep it on local reference positions for now. So it has been added to local reference - * positions, and must be removed here to not apply to marker segments. - */ - addProperties(newProps: PropertySet): void; isLeaf(): this is ISegment; } diff --git a/packages/dds/merge-tree/src/segmentGroupCollection.ts b/packages/dds/merge-tree/src/segmentGroupCollection.ts index 9eddf5f865c9..fca99f4eaf65 100644 --- a/packages/dds/merge-tree/src/segmentGroupCollection.ts +++ b/packages/dds/merge-tree/src/segmentGroupCollection.ts @@ -5,22 +5,15 @@ import { DoublyLinkedList, walkList } from "./collections/index.js"; // eslint-disable-next-line import/no-deprecated -import { ISegment, SegmentGroup } from "./mergeTreeNodes.js"; +import { SegmentGroup, type ISegmentLeaf } from "./mergeTreeNodes.js"; -/** - * @deprecated - This class should not be used externally and will be removed in a subsequent release. - * @legacy - * @alpha - * - * @privateRemarks After the deprecation period this class should be remove from this package's exports, and only be used internally - */ export class SegmentGroupCollection { // eslint-disable-next-line import/no-deprecated - private readonly segmentGroups: DoublyLinkedList; + private readonly segmentGroups: DoublyLinkedList>; - constructor(private readonly segment: ISegment) { + constructor(private readonly segment: ISegmentLeaf) { // eslint-disable-next-line import/no-deprecated - this.segmentGroups = new DoublyLinkedList(); + this.segmentGroups = new DoublyLinkedList>(); } public get size(): number { @@ -32,18 +25,18 @@ export class SegmentGroupCollection { } // eslint-disable-next-line import/no-deprecated - public enqueue(segmentGroup: SegmentGroup): void { + public enqueue(segmentGroup: SegmentGroup): void { this.segmentGroups.push(segmentGroup); segmentGroup.segments.push(this.segment); } // eslint-disable-next-line import/no-deprecated - public dequeue(): SegmentGroup | undefined { + public dequeue(): SegmentGroup | undefined { return this.segmentGroups.shift()?.data; } // eslint-disable-next-line import/no-deprecated - public remove?(segmentGroup: SegmentGroup): boolean { + public remove?(segmentGroup: SegmentGroup): boolean { const found = this.segmentGroups.find((v) => v.data === segmentGroup); if (found === undefined) { return false; @@ -53,18 +46,19 @@ export class SegmentGroupCollection { } // eslint-disable-next-line import/no-deprecated - public pop?(): SegmentGroup | undefined { + public pop?(): SegmentGroup | undefined { return this.segmentGroups.pop ? this.segmentGroups.pop()?.data : undefined; } - public copyTo(segment: ISegment): void { - walkList(this.segmentGroups, (sg) => - segment.segmentGroups.enqueueOnCopy(sg.data, this.segment), - ); + public copyTo(segmentGroups: SegmentGroupCollection): void { + walkList(this.segmentGroups, (sg) => segmentGroups.enqueueOnCopy(sg.data, this.segment)); } // eslint-disable-next-line import/no-deprecated - private enqueueOnCopy(segmentGroup: SegmentGroup, sourceSegment: ISegment): void { + private enqueueOnCopy( + segmentGroup: SegmentGroup, + sourceSegment: ISegmentLeaf, + ): void { this.enqueue(segmentGroup); if (segmentGroup.previousProps) { // duplicate the previousProps for this segment diff --git a/packages/dds/merge-tree/src/segmentPropertiesManager.ts b/packages/dds/merge-tree/src/segmentPropertiesManager.ts index 81f081dc444e..617388f431d9 100644 --- a/packages/dds/merge-tree/src/segmentPropertiesManager.ts +++ b/packages/dds/merge-tree/src/segmentPropertiesManager.ts @@ -3,21 +3,19 @@ * Licensed under the MIT License. */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ - import { assert } from "@fluidframework/core-utils/internal"; +import { DoublyLinkedList, iterateListValuesWhile } from "./collections/index.js"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js"; -import { IMergeTreeAnnotateMsg } from "./ops.js"; -import { MapLike, PropertySet, clone, createMap, extend } from "./properties.js"; +import type { + AdjustParams, + IMergeTreeAnnotateAdjustMsg, + IMergeTreeAnnotateMsg, +} from "./ops.js"; +import { MapLike, PropertySet, clone, createMap } from "./properties.js"; /** - * @legacy - * @alpha - * - * @deprecated - This enum should not be used externally and will be removed in a subsequent release. - * - * @privateRemarks This enum should be made internal after the deprecation period + * @internal */ export enum PropertiesRollback { /** @@ -30,130 +28,339 @@ export enum PropertiesRollback { */ Rollback, } - /** - * @legacy - * @alpha - * - * @deprecated - This class should not be used externally and will be removed in a subsequent release. - * - * @privateRemarks This class should be made internal after the deprecation period + * Minimally copies properties and the property manager from source to destination. + * @internal */ -export class PropertiesManager { - private pendingKeyUpdateCount: MapLike | undefined; - - public ackPendingProperties(annotateOp: IMergeTreeAnnotateMsg): void { - this.decrementPendingCounts(annotateOp.props); +export function copyPropertiesAndManager( + source: { + properties?: PropertySet; + propertyManager?: PropertiesManager; + }, + destination: { + properties?: PropertySet; + propertyManager?: PropertiesManager; + }, +): void { + if (source.properties) { + if (source.propertyManager === undefined) { + destination.properties = clone(source.properties); + } else { + destination.propertyManager ??= new PropertiesManager(); + source.propertyManager.copyTo(source.properties, destination); + } } +} - private decrementPendingCounts(props: PropertySet): void { - for (const [key, value] of Object.entries(props)) { - if (value !== undefined && this.pendingKeyUpdateCount?.[key] !== undefined) { - assert( - this.pendingKeyUpdateCount[key]! > 0, - 0x05c /* "Trying to update more annotate props than do exist!" */, - ); - this.pendingKeyUpdateCount[key]--; - if (this.pendingKeyUpdateCount?.[key] === 0) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this.pendingKeyUpdateCount[key]; +type PropertyChange = { + seq: number; +} & ({ adjust: AdjustParams; raw?: undefined } | { raw: unknown; adjust?: undefined }); + +interface PropertyChanges { + msnConsensus: unknown; + remote: DoublyLinkedList; + local: DoublyLinkedList; +} + +function computePropertyValue( + consensus: unknown, + ...changes: Iterable[] +): unknown { + let computedValue: unknown = consensus; + for (const change of changes) { + for (const op of change) { + const { raw, adjust } = op; + if (adjust === undefined) { + computedValue = raw; + } else { + const adjusted = + (typeof computedValue === "number" ? computedValue : 0) + adjust.delta; + if (adjust.max !== undefined && adjusted > adjust.max) { + computedValue = adjust.max; + } else if (adjust.min !== undefined && adjusted < adjust.min) { + computedValue = adjust.min; + } else { + computedValue = adjusted; } } } } + return computedValue; +} - public addProperties( - oldProps: PropertySet, - newProps: PropertySet, - seq?: number, - collaborating: boolean = false, - rollback: PropertiesRollback = PropertiesRollback.None, - ): PropertySet { - this.pendingKeyUpdateCount ??= createMap(); +/** + * @internal + */ +export type PropsOrAdjust = + | Pick + | Pick; + +const opToChanges = (op: PropsOrAdjust, seq: number): [string, PropertyChange][] => [ + ...Object.entries(op.props ?? {}) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + .map<[string, PropertyChange]>(([k, raw]) => [k, { raw, seq }]) + .filter(([_, v]) => v.raw !== undefined), + ...Object.entries(op.adjust ?? {}).map<[string, PropertyChange]>(([k, adjust]) => [ + k, + { adjust, seq }, + ]), +]; - // Clean up counts for rolled back edits before modifying oldProps - if (collaborating && rollback === PropertiesRollback.Rollback) { - this.decrementPendingCounts(newProps); +function applyChanges( + op: PropsOrAdjust, + seg: { properties?: MapLike }, + seq: number, + run: ( + properties: MapLike, + deltas: MapLike, + key: string, + value: PropertyChange, + ) => void, +): MapLike { + const properties = (seg.properties ??= createMap()); + const deltas: MapLike = {}; + for (const [key, value] of opToChanges(op, seq)) { + run(properties, deltas, key, value); + if (properties[key] === null) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete properties[key]; } + } + return deltas; +} - const shouldModifyKey = (key: string): boolean => { - if ( - seq === UnassignedSequenceNumber || - seq === UniversalSequenceNumber || - this.pendingKeyUpdateCount?.[key] === undefined - ) { - return true; - } - return false; - }; +/** + * The PropertiesManager class handles changes to properties, both remote and local. + * It manages the lifecycle for local property changes, ensures all property changes are eventually consistent, + * and provides methods to acknowledge changes, update the minimum sequence number (msn), and copy properties to another manager. + * This class is essential for maintaining the integrity and consistency of property changes in collaborative environments. + * @internal + */ +export class PropertiesManager { + private readonly changes = new Map(); - const deltas: PropertySet = {}; + /** + * Rolls back local property changes. + * This method reverts property changes based on the provided operation and segment. + * If the operation is part of a collaborative session, it ensures that the changes are consistent with the remote state. + * + * @param op - The operation containing property changes. This can be an adjustment or a set of properties. + * @param seg - The segment containing properties. This object may have a properties map that will be modified. + * @param collaborating - Indicates if the operation is part of a collaborative session. Defaults to false. + * @returns The deltas of the rolled-back properties. This is a map-like object representing the changes that were reverted. + */ + public rollbackProperties( + op: PropsOrAdjust, + seg: { properties?: MapLike }, + collaborating: boolean = false, + ): MapLike { + return applyChanges(op, seg, UniversalSequenceNumber, (properties, deltas, key, value) => { + // eslint-disable-next-line unicorn/no-null + const previousValue = properties[key] ?? null; - for (const [key, newValue] of Object.entries(newProps)) { - if (newValue === undefined) { - continue; + const pending = this.changes.get(key); + if (collaborating) { + assert( + pending !== undefined, + "Pending changes must exist for rollback when collaborating", + ); + pending.local.pop(); + properties[key] = computePropertyValue( + pending.msnConsensus, + pending.remote.map((n) => n.data), + pending.local.map((n) => n.data), + ); + if (pending.local.empty && pending.remote.empty) { + this.changes.delete(key); + } + } else { + assert(pending === undefined, "Pending changes must not exist when not collaborating"); + properties[key] = computePropertyValue(previousValue, [value]); } + deltas[key] = previousValue; + }); + } + /** + * Handles property changes. + * This method applies property changes based on the provided operation, segment, sequence number, and collaboration state. + * It also handles rolling back changes if specified. + * + * @param op - The operation containing property changes. + * @param seg - The segment containing properties. + * @param seq - The sequence number for the operation. + * @param msn - The minimum sequence number for the operation. + * @param collaborating - Indicates if the operation is part of a collaborative session. Defaults to false. + * @param rollback - Specifies if the changes should be rolled back. Defaults to PropertiesRollback.None. + * @returns The deltas of the applied or rolled-back properties. This is a map-like object representing the changes. + */ + public handleProperties( + op: PropsOrAdjust, + seg: { properties?: MapLike }, + seq: number, + msn: number, + collaborating: boolean = false, + rollback: PropertiesRollback = PropertiesRollback.None, + ): MapLike { + if (rollback === PropertiesRollback.Rollback) { + return this.rollbackProperties(op, seg, collaborating); + } + const rtn = applyChanges(op, seg, seq, (properties, deltas, key, value) => { + // eslint-disable-next-line unicorn/no-null + const previousValue = properties[key] ?? null; if (collaborating) { - if (seq === UnassignedSequenceNumber) { - if (this.pendingKeyUpdateCount?.[key] === undefined) { - this.pendingKeyUpdateCount[key] = 0; + const pending: PropertyChanges | undefined = this.changes.get(key) ?? { + msnConsensus: previousValue, + remote: new DoublyLinkedList(), + local: new DoublyLinkedList(), + }; + this.changes.set(key, pending); + const local = seq === UnassignedSequenceNumber; + if (local) { + pending.local.push(value); + } else { + // we only track remotes if there are adjusts, as only adjusts make application anti-commutative + // this will limit the impact of this change to only those using adjusts. Additionally, we only + // need to track remotes at all to support emitting the legacy snapshot format, which only sharedstring + // uses. when we remove the ability to emit that format, we can remove all remote op tracking + if (value.raw !== undefined && pending.remote.empty) { + pending.msnConsensus = computePropertyValue(pending.msnConsensus, [value]); + } else { + pending.remote.push(value); } - this.pendingKeyUpdateCount[key]++; - } else if (!shouldModifyKey(key)) { - continue; } + properties[key] = computePropertyValue( + pending.msnConsensus, + pending.remote.map((n) => n.data), + pending.local.map((n) => n.data), + ); + if (local || pending.local.empty || properties[key] !== previousValue) { + deltas[key] = previousValue; + } + } else { + properties[key] = computePropertyValue(previousValue, [value]); + deltas[key] = previousValue; } + }); + this.updateMsn(msn); + return rtn; + } - const previousValue: unknown = oldProps[key]; - // The delta should be null if undefined, as that's how we encode delete - // eslint-disable-next-line unicorn/no-null - deltas[key] = previousValue === undefined ? null : previousValue; - if (newValue === null) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete oldProps[key]; + /** + * Acknowledges property changes. + * This method acknowledges the property changes based on the provided sequence number and operation. + * + * @param seq - The sequence number for the operation. + * @param msn - The minimum sequence number for the operation. + * @param op - The operation containing property changes. + */ + public ack(seq: number, msn: number, op: PropsOrAdjust): void { + for (const [key, value] of opToChanges(op, seq)) { + const change = this.changes.get(key); + const acked = change?.local?.shift(); + assert(change !== undefined && acked !== undefined, "must have local change to ack"); + // we only track remotes if there are adjusts, as only adjusts make application anti-commutative + // this will limit the impact of this change to only those using adjusts. Additionally, we only + // need to track remotes at all to support emitting the legacy snapshot format, which only sharedstring + // uses. when we remove the ability to emit that format, we can remove all remote op tracking + if (value.raw !== undefined && change.remote.empty) { + change.msnConsensus = computePropertyValue(change.msnConsensus, [value]); } else { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - oldProps[key] = newValue; + change.remote.push(value); } } + this.updateMsn(msn); + } - return deltas; + /** + * Updates the minimum sequence number (msn). + * This method updates the minimum sequence number and removes any changes that have been acknowledged. + * + * @param msn - The minimum sequence number to update. + */ + public updateMsn(msn: number): void { + for (const [key, pending] of this.changes) { + pending.msnConsensus = computePropertyValue( + pending.msnConsensus, + iterateListValuesWhile(pending.remote.first, (n) => { + if (n.data.seq <= msn) { + n.list?.remove(n); + return true; + } + return false; + }), + ); + if (pending.local.empty && pending.remote.empty) { + this.changes.delete(key); + } + } } + /** + * Copies properties to another manager. + * This method copies the properties and their changes from the current manager to the destination manager. + * + * @param oldProps - The old properties to be copied. + * @param dest - The destination object containing properties and property manager. + */ public copyTo( - oldProps: PropertySet, - newProps: PropertySet | undefined, - newManager: PropertiesManager, - ): PropertySet | undefined { - if (oldProps) { - // eslint-disable-next-line no-param-reassign - newProps ??= createMap(); - if (!newManager) { - throw new Error("Must provide new PropertyManager"); - } - extend(newProps, oldProps); + oldProps: PropertySet | undefined, + dest: { + properties?: PropertySet; + propertyManager?: PropertiesManager; + }, + ): void { + const newManager = (dest.propertyManager ??= new PropertiesManager()); + dest.properties = clone(oldProps); + for (const [key, { local, remote, msnConsensus }] of this.changes.entries()) { + newManager.changes.set(key, { + msnConsensus, + remote: new DoublyLinkedList(remote.empty ? undefined : remote.map((c) => c.data)), + local: new DoublyLinkedList(local.empty ? undefined : local.map((c) => c.data)), + }); + } + } - if (this.pendingKeyUpdateCount) { - newManager.pendingKeyUpdateCount = clone(this.pendingKeyUpdateCount); + /** + * Gets properties at a specific sequence number. + * This method retrieves the properties at the given sequence number. + * This is only needed to support emitting snapshots in the legacy format. + * If we remove the ability to emit the legacy format, we can remove this method, along with the need to track remote changes at all. + * + * @param oldProps - The old properties to be retrieved. + * @param sequenceNumber - The sequence number to get properties at. + * @returns The properties at the given sequence number. + */ + public getAtSeq( + oldProps: MapLike | undefined, + sequenceNumber: number, + ): MapLike { + const properties: MapLike = { ...oldProps }; + for (const [key, changes] of this.changes) { + properties[key] = computePropertyValue( + changes.msnConsensus, + iterateListValuesWhile(changes.remote.first, (c) => c.data.seq <= sequenceNumber), + ); + if (properties[key] === null) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete properties[key]; } } - return newProps; + return properties; } /** * Determines if all of the defined properties in a given property set are pending. + * + * @param props - The properties to check. + * @returns True if all the properties are pending, false otherwise. */ public hasPendingProperties(props: PropertySet): boolean { for (const [key, value] of Object.entries(props)) { - if (value !== undefined && this.pendingKeyUpdateCount?.[key] === undefined) { + if (value !== undefined && this.changes.get(key)?.local.empty !== false) { return false; } } return true; } - - public hasPendingProperty(key: string): boolean { - return (this.pendingKeyUpdateCount?.[key] ?? 0) > 0; - } } diff --git a/packages/dds/merge-tree/src/sortedSegmentSet.ts b/packages/dds/merge-tree/src/sortedSegmentSet.ts index 652ce78b7c93..13aed1065bb2 100644 --- a/packages/dds/merge-tree/src/sortedSegmentSet.ts +++ b/packages/dds/merge-tree/src/sortedSegmentSet.ts @@ -9,7 +9,6 @@ import { ISegment } from "./mergeTreeNodes.js"; import { SortedSet } from "./sortedSet.js"; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export type SortedSegmentSetItem = diff --git a/packages/dds/merge-tree/src/sortedSet.ts b/packages/dds/merge-tree/src/sortedSet.ts index 5b186272fd88..fe2f6831fb48 100644 --- a/packages/dds/merge-tree/src/sortedSet.ts +++ b/packages/dds/merge-tree/src/sortedSet.ts @@ -4,7 +4,6 @@ */ /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export abstract class SortedSet { diff --git a/packages/dds/merge-tree/src/test/index.ts b/packages/dds/merge-tree/src/test/index.ts index 51fd4fab6f13..5ddf260507d5 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -39,7 +39,12 @@ export { runMergeTreeOperationRunner, TestOperation, } from "./mergeTreeOperationRunner.js"; -export { LRUSegment, MergeTree } from "../mergeTree.js"; +export { + LRUSegment, + MergeTree, + IMergeTreeOptions, + IMergeTreeOptionsInternal, +} from "../mergeTree.js"; export { MergeTreeTextHelper } from "../MergeTreeTextHelper.js"; export { SnapshotLegacy } from "../snapshotlegacy.js"; export { @@ -90,7 +95,6 @@ export { Marker, matchProperties, maxReferencePosition, - MergeNode, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, MergeTreeDeltaRevertible, @@ -116,7 +120,6 @@ export { reservedTileLabelsKey, revertMergeTreeDeltaRevertibles, SegmentGroup, - SegmentGroupCollection, SortedSegmentSet, SortedSegmentSetItem, SortedSet, diff --git a/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts b/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts index c1359a48b3b3..ca7104f4baec 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts @@ -555,7 +555,7 @@ describe("MergeTree", () => { currentSequenceNumber, localClientId, ); - assert(segmentInfo.segment?.segmentGroups?.empty); + assert(segmentInfo.segment?.segmentGroups?.size !== 0); }); it("remote only", () => { const segmentInfo = mergeTree.getContainingSegment( @@ -615,7 +615,7 @@ describe("MergeTree", () => { currentSequenceNumber, localClientId, ); - assert(segmentInfo.segment?.segmentGroups?.empty); + assert(segmentInfo.segment?.segmentGroups?.empty !== false); mergeTree.annotateRange( annotateStart, @@ -627,7 +627,7 @@ describe("MergeTree", () => { undefined as never, ); - assert.equal(segmentInfo.segment?.segmentGroups.size, 1); + assert.equal(segmentInfo.segment?.segmentGroups?.size, 1); mergeTree.ackPendingSegment({ op: { @@ -641,7 +641,7 @@ describe("MergeTree", () => { } as unknown as ISequencedDocumentMessage, }); - assert(segmentInfo.segment?.segmentGroups.empty); + assert(segmentInfo.segment?.segmentGroups?.empty); assert.equal(segmentInfo.segment?.properties?.propertySource, "local"); assert.equal(segmentInfo.segment?.properties?.remoteProperty, 1); }); diff --git a/packages/dds/merge-tree/src/test/propertyManager.spec.ts b/packages/dds/merge-tree/src/test/propertyManager.spec.ts new file mode 100644 index 000000000000..183a274991fc --- /dev/null +++ b/packages/dds/merge-tree/src/test/propertyManager.spec.ts @@ -0,0 +1,210 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { UnassignedSequenceNumber } from "../constants.js"; +import type { ISegmentLeaf } from "../mergeTreeNodes.js"; +import { matchProperties } from "../properties.js"; +import { + PropertiesManager, + PropertiesRollback, + type PropsOrAdjust, +} from "../segmentPropertiesManager.js"; + +describe("PropertiesManager", () => { + describe("handleProperties", () => { + it("should handle properties without collaboration", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const deltas = propertiesManager.handleProperties(op, seg, 1, 0, false); + assert.deepEqual(deltas, { key: "value" }); + assert.deepEqual(seg.properties, { key: "newValue" }); + }); + + it("should handle properties with collaboration", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const deltas = propertiesManager.handleProperties( + op, + seg, + UnassignedSequenceNumber, + 0, + true, + ); + assert.deepEqual(deltas, { key: "value" }); + assert.deepEqual(seg.properties, { key: "newValue" }); + }); + + it("should handle properties with rollback", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + // Simulate pending state for rollback + propertiesManager.handleProperties(op, seg, UnassignedSequenceNumber, 0, true); + const deltas = propertiesManager.handleProperties( + op, + seg, + UnassignedSequenceNumber, + 0, + true, + PropertiesRollback.Rollback, + ); + assert.deepEqual(deltas, { key: "newValue" }); + assert.deepEqual(seg.properties, { key: "value" }); + }); + + it("should handle properties with seq as a number and collaborating true", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const deltas = propertiesManager.handleProperties(op, seg, 2, 1, true); + assert.deepEqual(deltas, { key: "value" }); + assert.deepEqual(seg.properties, { key: "newValue" }); + }); + + it("should handle properties with seq as a number and collaborating false", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const deltas = propertiesManager.handleProperties(op, seg, 2, 1, false); + assert.deepEqual(deltas, { key: "value" }); + assert.deepEqual(seg.properties, { key: "newValue" }); + }); + + it("should handle properties with adjusts", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: 1 }, + }; + const op: PropsOrAdjust = { adjust: { key: { delta: 1 } } }; + const deltas = propertiesManager.handleProperties(op, seg, 2, 1, true); + assert.deepEqual(deltas, { key: 1 }); + assert.deepEqual(seg.properties, { key: 2 }); + }); + + it("should handle properties with props and adjusts interleaved", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: 1, otherKey: "value" }, + }; + const op1: PropsOrAdjust = { props: { otherKey: "newValue" } }; + const op2: PropsOrAdjust = { adjust: { key: { delta: 1 } } }; + propertiesManager.handleProperties(op1, seg, 2, 1, true); + const deltas = propertiesManager.handleProperties(op2, seg, 3, 2, true); + assert.deepEqual(deltas, { key: 1 }); + assert.deepEqual(seg.properties, { key: 2, otherKey: "newValue" }); + }); + }); + + describe("rollbackProperties", () => { + it("should rollback properties when collaborating is true", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const rollbackDeltas = propertiesManager.handleProperties( + op, + seg, + UnassignedSequenceNumber, + 0, + true, + ); + const deltas = propertiesManager.rollbackProperties( + { props: rollbackDeltas }, + seg, + true, + ); + assert.deepEqual(deltas, { key: "newValue" }); + assert.deepEqual(seg.properties, { key: "value" }); + }); + + it("should rollback properties when collaborating is false", () => { + const propertiesManager = new PropertiesManager(); + const seg: Pick = { + properties: { key: "value" }, + }; + const op: PropsOrAdjust = { props: { key: "newValue" } }; + const rollbackDeltas = propertiesManager.handleProperties( + op, + seg, + UnassignedSequenceNumber, + 0, + false, + ); + const deltas = propertiesManager.rollbackProperties( + { props: rollbackDeltas }, + seg, + false, + ); + assert.deepEqual(deltas, { key: "newValue" }); + assert.deepEqual(seg.properties, { key: "value" }); + }); + }); + + describe("ack", () => { + it("should acknowledge property changes", () => { + const propertiesManager = new PropertiesManager(); + const op: PropsOrAdjust = { props: { key: "value" } }; + const seg: Pick = { properties: {} }; + + propertiesManager.handleProperties(op, seg, UnassignedSequenceNumber, 1, true); + assert(propertiesManager.hasPendingProperties({ key: "value" })); + propertiesManager.ack(1, 0, op); + }); + }); + + describe("copyTo", () => { + it("should copy properties and manager state", () => { + const propertiesManager = new PropertiesManager(); + const op: PropsOrAdjust = { props: { key: "value" } }; + const seg: Pick = { properties: {} }; + + propertiesManager.handleProperties(op, seg, UnassignedSequenceNumber, 1, true); + assert(propertiesManager.hasPendingProperties({ key: "value" })); + const dest: Pick = {}; + propertiesManager.copyTo({ key: "value" }, dest); + assert(dest.propertyManager instanceof PropertiesManager); + assert(dest.propertyManager.hasPendingProperties({ key: "value" })); + }); + }); + + describe("getAtSeq", () => { + it("should retrieve properties at a specific sequence number", () => { + const propertiesManager = new PropertiesManager(); + const op: PropsOrAdjust = { adjust: { key: { delta: 5 } } }; + const seg: Pick = { properties: {} }; + + propertiesManager.handleProperties(op, seg, 1, 0, true); + const properties = propertiesManager.getAtSeq(seg.properties, 0); + assert(matchProperties(properties, {})); + }); + }); + + describe("hasPendingProperties", () => { + it("should check for pending properties", () => { + const propertiesManager = new PropertiesManager(); + const op: PropsOrAdjust = { props: { key: "value" } }; + const seg: Pick = { properties: {} }; + + propertiesManager.handleProperties(op, seg, UnassignedSequenceNumber, 1, true); + assert(propertiesManager.hasPendingProperties({ key: "value" })); + assert(!propertiesManager.hasPendingProperties({ otherKey: "otherValue" })); + }); + }); +}); diff --git a/packages/dds/merge-tree/src/test/segmentGroupCollection.spec.ts b/packages/dds/merge-tree/src/test/segmentGroupCollection.spec.ts index 9772393ccfc9..cc3115965ea2 100644 --- a/packages/dds/merge-tree/src/test/segmentGroupCollection.spec.ts +++ b/packages/dds/merge-tree/src/test/segmentGroupCollection.spec.ts @@ -5,43 +5,46 @@ import { strict as assert } from "node:assert"; -import { ISegment } from "../mergeTreeNodes.js"; +import { type ISegmentLeaf } from "../mergeTreeNodes.js"; +import { SegmentGroupCollection } from "../segmentGroupCollection.js"; import { TextSegment } from "../textSegment.js"; describe("segmentGroupCollection", () => { - let segment: ISegment; + let segment: ISegmentLeaf; + let segmentGroups: SegmentGroupCollection; beforeEach(() => { segment = TextSegment.make("abc"); + segmentGroups = segment.segmentGroups = new SegmentGroupCollection(segment); }); it(".empty", () => { - assert(segment.segmentGroups.empty); + assert(segmentGroups.empty); }); it(".size", () => { - assert.equal(segment.segmentGroups.size, 0); + assert.equal(segmentGroups.size, 0); }); it(".enqueue", () => { const segmentGroup = { segments: [], localSeq: 1, refSeq: 0 }; - segment.segmentGroups.enqueue(segmentGroup); + segmentGroups.enqueue(segmentGroup); - assert(!segment.segmentGroups.empty); - assert.equal(segment.segmentGroups.size, 1); + assert(!segmentGroups.empty); + assert.equal(segmentGroups.size, 1); assert.equal(segmentGroup.segments.length, 1); assert.equal(segmentGroup.segments[0], segment); }); it(".dequeue", () => { const segmentGroup = { segments: [], localSeq: 1, refSeq: 0 }; - segment.segmentGroups.enqueue(segmentGroup); + segmentGroups.enqueue(segmentGroup); const segmentGroupCount = 6; - while (segment.segmentGroups.size < segmentGroupCount) { - segment.segmentGroups.enqueue({ segments: [], localSeq: 1, refSeq: 0 }); + while (segmentGroups.size < segmentGroupCount) { + segmentGroups.enqueue({ segments: [], localSeq: 1, refSeq: 0 }); } - const dequeuedSegmentGroup = segment.segmentGroups.dequeue(); + const dequeuedSegmentGroup = segmentGroups.dequeue(); - assert.equal(segment.segmentGroups.size, segmentGroupCount - 1); + assert.equal(segmentGroups.size, segmentGroupCount - 1); assert.equal(dequeuedSegmentGroup?.segments.length, 1); assert.equal(dequeuedSegmentGroup.segments[0], segment); assert.equal(dequeuedSegmentGroup, segmentGroup); @@ -49,19 +52,20 @@ describe("segmentGroupCollection", () => { it(".copyTo", () => { const segmentGroupCount = 6; - while (segment.segmentGroups.size < segmentGroupCount) { - segment.segmentGroups.enqueue({ segments: [], localSeq: 1, refSeq: 0 }); + while (segmentGroups.size < segmentGroupCount) { + segmentGroups.enqueue({ segments: [], localSeq: 1, refSeq: 0 }); } const segmentCopy = TextSegment.make(""); - segment.segmentGroups.copyTo(segmentCopy); + const segmentGroupCopy = new SegmentGroupCollection(segmentCopy); + segmentGroups.copyTo(segmentGroupCopy); - assert.equal(segment.segmentGroups.size, segmentGroupCount); - assert.equal(segmentCopy.segmentGroups.size, segmentGroupCount); + assert.equal(segmentGroups.size, segmentGroupCount); + assert.equal(segmentGroupCopy.size, segmentGroupCount); - while (!segment.segmentGroups.empty || !segmentCopy.segmentGroups.empty) { - const segmentGroup = segment.segmentGroups.dequeue(); - const copySegmentGroup = segmentCopy.segmentGroups.dequeue(); + while (!segmentGroups.empty || !segmentGroupCopy.empty) { + const segmentGroup = segmentGroups.dequeue(); + const copySegmentGroup = segmentGroupCopy.dequeue(); assert.equal(segmentGroup, copySegmentGroup); assert.equal(segmentGroup?.segments.length, 2); diff --git a/packages/dds/merge-tree/src/test/snapshot.spec.ts b/packages/dds/merge-tree/src/test/snapshot.spec.ts index 706cfc0be3a4..4e5e8059f268 100644 --- a/packages/dds/merge-tree/src/test/snapshot.spec.ts +++ b/packages/dds/merge-tree/src/test/snapshot.spec.ts @@ -9,12 +9,12 @@ import { createInsertOnlyAttributionPolicy, createPropertyTrackingAttributionPolicyFactory, } from "../attributionPolicy.js"; -import { IMergeTreeOptions } from "../mergeTree.js"; +import { IMergeTreeOptionsInternal } from "../mergeTree.js"; import { SnapshotV1 } from "../snapshotV1.js"; import { TestString, loadSnapshot } from "./snapshot.utils.js"; -function makeSnapshotSuite(options?: IMergeTreeOptions): void { +function makeSnapshotSuite(options?: IMergeTreeOptionsInternal): void { describe("from an empty initial state", () => { let str: TestString; beforeEach(() => { diff --git a/packages/dds/merge-tree/src/test/snapshot.utils.ts b/packages/dds/merge-tree/src/test/snapshot.utils.ts index cabdac4dc431..49d88b4b97e0 100644 --- a/packages/dds/merge-tree/src/test/snapshot.utils.ts +++ b/packages/dds/merge-tree/src/test/snapshot.utils.ts @@ -12,7 +12,7 @@ import { ISummaryTree } from "@fluidframework/driver-definitions"; import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal"; import { MockStorage } from "@fluidframework/test-runtime-utils/internal"; -import { IMergeTreeOptions } from "../mergeTree.js"; +import { type IMergeTreeOptionsInternal } from "../mergeTree.js"; import { ISegment } from "../mergeTreeNodes.js"; import { IMergeTreeOp, ReferenceType } from "../ops.js"; import { PropertySet } from "../properties.js"; @@ -25,7 +25,7 @@ import { TestSerializer } from "./testSerializer.js"; // Reconstitutes a MergeTree client from a summary export async function loadSnapshot( summary: ISummaryTree, - options?: IMergeTreeOptions, + options?: IMergeTreeOptionsInternal, ): Promise { const services = MockStorage.createFromSummary(summary); const client2 = new TestClient(options); @@ -52,7 +52,7 @@ export class TestString { constructor( id: string, - private readonly options?: IMergeTreeOptions, + private readonly options?: IMergeTreeOptionsInternal, initialState: string = "", ) { this.client = createClientsAtInitialState({ initialState, options }, id)[id]; @@ -109,7 +109,7 @@ export class TestString { } // Ensures the MergeTree client's contents successfully roundtrip through a snapshot. - public async checkSnapshot(options?: IMergeTreeOptions): Promise { + public async checkSnapshot(options?: IMergeTreeOptionsInternal): Promise { this.applyPendingOps(); const expectedAttributionKeys = this.client.getAllAttributionSeqs(); const summary = this.getSummary(); diff --git a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts index eb74b0ef4329..5e22fdc9da62 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -31,6 +31,7 @@ declare type old_as_current_for_Class_BaseSegment = requireAssignableTo, TypeOnly> /* @@ -94,6 +95,7 @@ declare type old_as_current_for_Class_Marker = requireAssignableTo, TypeOnly> /* @@ -103,6 +105,7 @@ declare type current_as_old_for_Class_Marker = requireAssignableTo, TypeOnly> /* @@ -112,6 +115,7 @@ declare type old_as_current_for_Class_MergeNode = requireAssignableTo, TypeOnly> /* @@ -121,6 +125,7 @@ declare type current_as_old_for_Class_MergeNode = requireAssignableTo, TypeOnly> /* @@ -130,6 +135,7 @@ declare type old_as_current_for_Class_PropertiesManager = requireAssignableTo, TypeOnly> /* @@ -139,6 +145,7 @@ declare type current_as_old_for_Class_PropertiesManager = requireAssignableTo, TypeOnly> /* @@ -148,6 +155,7 @@ declare type old_as_current_for_Class_SegmentGroupCollection = requireAssignable * typeValidation.broken: * "Class_SegmentGroupCollection": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Class_SegmentGroupCollection = requireAssignableTo, TypeOnly> /* @@ -166,6 +174,7 @@ declare type old_as_current_for_Class_TextSegment = requireAssignableTo, TypeOnly> /* @@ -184,6 +193,7 @@ declare type old_as_current_for_Class_TrackingGroup = requireAssignableTo, TypeOnly> /* @@ -211,6 +221,7 @@ declare type current_as_old_for_Class_TrackingGroupCollection = requireAssignabl * typeValidation.broken: * "ClassStatics_BaseSegment": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_BaseSegment = requireAssignableTo, TypeOnly> /* @@ -247,6 +258,7 @@ declare type current_as_old_for_ClassStatics_LocalReferenceCollection = requireA * typeValidation.broken: * "ClassStatics_Marker": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_Marker = requireAssignableTo, TypeOnly> /* @@ -256,6 +268,7 @@ declare type current_as_old_for_ClassStatics_Marker = requireAssignableTo, TypeOnly> /* @@ -265,6 +278,7 @@ declare type current_as_old_for_ClassStatics_MergeNode = requireAssignableTo, TypeOnly> /* @@ -274,6 +288,7 @@ declare type current_as_old_for_ClassStatics_PropertiesManager = requireAssignab * typeValidation.broken: * "ClassStatics_SegmentGroupCollection": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_SegmentGroupCollection = requireAssignableTo, TypeOnly> /* @@ -283,6 +298,7 @@ declare type current_as_old_for_ClassStatics_SegmentGroupCollection = requireAss * typeValidation.broken: * "ClassStatics_TextSegment": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_TextSegment = requireAssignableTo, TypeOnly> /* @@ -292,6 +308,7 @@ declare type current_as_old_for_ClassStatics_TextSegment = requireAssignableTo, TypeOnly> /* @@ -634,6 +651,7 @@ declare type old_as_current_for_Interface_IMergeTreeDeltaCallbackArgs = requireA * typeValidation.broken: * "Interface_IMergeTreeDeltaCallbackArgs": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IMergeTreeDeltaCallbackArgs = requireAssignableTo, TypeOnly> /* @@ -706,6 +724,7 @@ declare type old_as_current_for_Interface_IMergeTreeMaintenanceCallbackArgs = re * typeValidation.broken: * "Interface_IMergeTreeMaintenanceCallbackArgs": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IMergeTreeMaintenanceCallbackArgs = requireAssignableTo, TypeOnly> /* @@ -796,6 +815,7 @@ declare type old_as_current_for_Interface_IMergeTreeSegmentDelta = requireAssign * typeValidation.broken: * "Interface_IMergeTreeSegmentDelta": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IMergeTreeSegmentDelta = requireAssignableTo, TypeOnly> /* @@ -832,6 +852,7 @@ declare type old_as_current_for_Interface_IMoveInfo = requireAssignableTo, TypeOnly> /* @@ -904,6 +925,7 @@ declare type old_as_current_for_Interface_ISegment = requireAssignableTo, TypeOnly> /* @@ -940,6 +962,7 @@ declare type old_as_current_for_Interface_ITrackingGroup = requireAssignableTo, TypeOnly> /* @@ -1003,6 +1026,7 @@ declare type old_as_current_for_Interface_ObliterateInfo = requireAssignableTo, TypeOnly> /* @@ -1021,6 +1045,7 @@ declare type old_as_current_for_Interface_ReferencePosition = requireAssignableT * typeValidation.broken: * "Interface_ReferencePosition": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ReferencePosition = requireAssignableTo, TypeOnly> /* @@ -1039,6 +1064,7 @@ declare type old_as_current_for_Interface_SegmentGroup = requireAssignableTo, TypeOnly> /* @@ -1165,6 +1191,7 @@ declare type old_as_current_for_TypeAlias_MergeTreeDeltaRevertible = requireAssi * typeValidation.broken: * "TypeAlias_MergeTreeDeltaRevertible": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_MergeTreeDeltaRevertible = requireAssignableTo, TypeOnly> /* @@ -1273,6 +1300,7 @@ declare type old_as_current_for_TypeAlias_Trackable = requireAssignableTo, TypeOnly> /* diff --git a/packages/dds/merge-tree/src/textSegment.ts b/packages/dds/merge-tree/src/textSegment.ts index 4f65056daafd..899f9a9382c4 100644 --- a/packages/dds/merge-tree/src/textSegment.ts +++ b/packages/dds/merge-tree/src/textSegment.ts @@ -106,9 +106,7 @@ export class TextSegment extends BaseSegment { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @legacy - * @alpha + * @internal */ export interface IMergeTreeTextHelper { getText( diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 8caf4b65a778..30b861044d28 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -35,6 +35,9 @@ export function zamboniSegments( for (let i = 0; i < zamboniSegmentsMaxCount; i++) { let segmentToScour = mergeTree.segmentsToScour.peek()?.value; + + segmentToScour?.segment?.propertyManager?.updateMsn(mergeTree.collabWindow.minSeq); + if (!segmentToScour || segmentToScour.maxSeq > mergeTree.collabWindow.minSeq) { break; } diff --git a/packages/dds/ordered-collection/CHANGELOG.md b/packages/dds/ordered-collection/CHANGELOG.md index 011f77504962..3e1724b542f5 100644 --- a/packages/dds/ordered-collection/CHANGELOG.md +++ b/packages/dds/ordered-collection/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/ordered-collection +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/ordered-collection/package.json b/packages/dds/ordered-collection/package.json index 4db14cc03409..20599e79855b 100644 --- a/packages/dds/ordered-collection/package.json +++ b/packages/dds/ordered-collection/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/ordered-collection", - "version": "2.5.0", + "version": "2.10.0", "description": "Consensus Collection", "homepage": "https://fluidframework.com", "repository": { @@ -133,11 +133,11 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/ordered-collection-previous": "npm:@fluidframework/ordered-collection@~2.4.0", + "@fluidframework/ordered-collection-previous": "npm:@fluidframework/ordered-collection@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/dds/ordered-collection/src/packageVersion.ts b/packages/dds/ordered-collection/src/packageVersion.ts index 3423c98170f0..66ed8460a5dc 100644 --- a/packages/dds/ordered-collection/src/packageVersion.ts +++ b/packages/dds/ordered-collection/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/ordered-collection"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/pact-map/CHANGELOG.md b/packages/dds/pact-map/CHANGELOG.md index 2477a69f5a2d..20a4ce7fe6ef 100644 --- a/packages/dds/pact-map/CHANGELOG.md +++ b/packages/dds/pact-map/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/pact-map +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/pact-map/package.json b/packages/dds/pact-map/package.json index 089b1f2e2df1..c8de7b5585c7 100644 --- a/packages/dds/pact-map/package.json +++ b/packages/dds/pact-map/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/pact-map", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed data structure for key-value pairs using pact consensus", "homepage": "https://fluidframework.com", "repository": { @@ -98,9 +98,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/packages/dds/pact-map/src/packageVersion.ts b/packages/dds/pact-map/src/packageVersion.ts index 950770ae49cf..d0a95d330f55 100644 --- a/packages/dds/pact-map/src/packageVersion.ts +++ b/packages/dds/pact-map/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/pact-map"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/register-collection/CHANGELOG.md b/packages/dds/register-collection/CHANGELOG.md index 7f18eab2bbe1..b8f69ae265e2 100644 --- a/packages/dds/register-collection/CHANGELOG.md +++ b/packages/dds/register-collection/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/register-collection +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/register-collection/package.json b/packages/dds/register-collection/package.json index c471f5a903ea..67946953ea48 100644 --- a/packages/dds/register-collection/package.json +++ b/packages/dds/register-collection/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/register-collection", - "version": "2.5.0", + "version": "2.10.0", "description": "Consensus Register", "homepage": "https://fluidframework.com", "repository": { @@ -131,11 +131,11 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/register-collection-previous": "npm:@fluidframework/register-collection@~2.4.0", + "@fluidframework/register-collection-previous": "npm:@fluidframework/register-collection@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/dds/register-collection/src/packageVersion.ts b/packages/dds/register-collection/src/packageVersion.ts index a6773e0266a7..7519e0322cb7 100644 --- a/packages/dds/register-collection/src/packageVersion.ts +++ b/packages/dds/register-collection/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/register-collection"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/sequence/.eslintrc.cjs b/packages/dds/sequence/.eslintrc.cjs index dc5c38448407..29d7b99306d1 100644 --- a/packages/dds/sequence/.eslintrc.cjs +++ b/packages/dds/sequence/.eslintrc.cjs @@ -14,6 +14,7 @@ module.exports = { rules: { "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, settings: { "import/extensions": [".ts", ".tsx", ".d.ts", ".js", ".jsx"], diff --git a/packages/dds/sequence/CHANGELOG.md b/packages/dds/sequence/CHANGELOG.md index 77c1c974fca3..7b5657c0f927 100644 --- a/packages/dds/sequence/CHANGELOG.md +++ b/packages/dds/sequence/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/sequence +## 2.5.0 + +Dependency updates only. + ## 2.4.0 ### Minor Changes diff --git a/packages/dds/sequence/api-report/sequence.legacy.alpha.api.md b/packages/dds/sequence/api-report/sequence.legacy.alpha.api.md index a013316fe8bc..67026ebe5dba 100644 --- a/packages/dds/sequence/api-report/sequence.legacy.alpha.api.md +++ b/packages/dds/sequence/api-report/sequence.legacy.alpha.api.md @@ -183,12 +183,8 @@ export interface ISequenceDeltaRange { - // @deprecated - constructor(opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, mergeTreeClient: Client); +export interface SequenceDeltaEvent extends SequenceEvent { readonly isLocal: boolean; // (undocumented) readonly opArgs: IMergeTreeDeltaOpArgs; } // @alpha -export abstract class SequenceEvent { - // @deprecated - constructor( - deltaArgs: IMergeTreeDeltaCallbackArgs, mergeTreeClient: Client); - get clientId(): string | undefined; +export interface SequenceEvent { + readonly clientId: string | undefined; + // (undocumented) readonly deltaArgs: IMergeTreeDeltaCallbackArgs; // (undocumented) readonly deltaOperation: TOperation; - get first(): Readonly>; - get last(): Readonly>; - get ranges(): readonly Readonly>[]; + readonly first: Readonly>; + readonly last: Readonly>; + readonly ranges: readonly Readonly>[]; } // @alpha -export class SequenceInterval implements ISerializableInterval { - // @deprecated - constructor(client: Client, - start: LocalReferencePosition, - end: LocalReferencePosition, intervalType: IntervalType, props?: PropertySet, startSide?: Side, endSide?: Side); +export interface SequenceInterval extends ISerializableInterval { addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void; // (undocumented) - addProperties(newProps: PropertySet, collab?: boolean, seq?: number): PropertySet | undefined; - // (undocumented) clone(): SequenceInterval; compare(b: SequenceInterval): number; compareEnd(b: SequenceInterval): number; compareStart(b: SequenceInterval): number; - end: LocalReferencePosition; + readonly end: LocalReferencePosition; // (undocumented) readonly endSide: Side; - getIntervalId(): string; // (undocumented) - intervalType: IntervalType; - modify(label: string, start: SequencePlace | undefined, end: SequencePlace | undefined, op?: ISequencedDocumentMessage, localSeq?: number, useNewSlidingBehavior?: boolean): SequenceInterval; + readonly intervalType: IntervalType; + modify(label: string, start: SequencePlace | undefined, end: SequencePlace | undefined, op?: ISequencedDocumentMessage, localSeq?: number, useNewSlidingBehavior?: boolean): SequenceInterval | undefined; // (undocumented) overlaps(b: SequenceInterval): boolean; // (undocumented) overlapsPos(bstart: number, bend: number): boolean; - properties: PropertySet; - // (undocumented) - propertyManager: PropertiesManager; removePositionChangeListeners(): void; // (undocumented) - serialize(): ISerializedInterval; - start: LocalReferencePosition; + readonly start: LocalReferencePosition; // (undocumented) readonly startSide: Side; // (undocumented) - get stickiness(): IntervalStickiness; + readonly stickiness: IntervalStickiness; union(b: SequenceInterval): SequenceInterval; } // @alpha -export class SequenceMaintenanceEvent extends SequenceEvent { - // @deprecated - constructor( - opArgs: IMergeTreeDeltaOpArgs | undefined, deltaArgs: IMergeTreeMaintenanceCallbackArgs, mergeTreeClient: Client); +export interface SequenceMaintenanceEvent extends SequenceEvent { + // (undocumented) readonly opArgs: IMergeTreeDeltaOpArgs | undefined; } export { SequencePlace } -// @alpha @deprecated (undocumented) -export abstract class SharedSegmentSequence extends SharedObject implements ISharedSegmentSequence { - constructor(dataStoreRuntime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, segmentFromSpec: (spec: IJSONSegment) => ISegment); - // (undocumented) - annotateRange(start: number, end: number, props: PropertySet): void; - protected applyStashedOp(content: any): void; - // (undocumented) - protected client: Client; - // (undocumented) - createLocalReferencePosition(segment: T, offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; - protected didAttach(): void; - // (undocumented) - getContainingSegment(pos: number): { - segment: T | undefined; - offset: number | undefined; - }; - // (undocumented) - getCurrentSeq(): number; - // (undocumented) - getIntervalCollection(label: string): IIntervalCollection; - // (undocumented) - getIntervalCollectionLabels(): IterableIterator; - // (undocumented) - getLength(): number; - // (undocumented) - getPosition(segment: ISegment): number; - // (undocumented) - getPropertiesAtPosition(pos: number): PropertySet | undefined; - // (undocumented) - getRangeExtentsOfPosition(pos: number): { - posStart: number | undefined; - posAfterEnd: number | undefined; - }; - // (undocumented) - groupOperation(groupOp: IMergeTreeGroupMsg): void; - protected guardReentrancy: (callback: () => TRet) => TRet; - // (undocumented) - id: string; - protected initializeLocalCore(): void; - // (undocumented) - insertAtReferencePosition(pos: ReferencePosition, segment: T): void; - // (undocumented) - insertFromSpec(pos: number, spec: IJSONSegment): void; - protected loadCore(storage: IChannelStorageService): Promise; - // @deprecated - get loaded(): Promise; - // (undocumented) - localReferencePositionToPosition(lref: ReferencePosition): number; - // (undocumented) - obliterateRange(start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): void; - protected onConnect(): void; - protected onDisconnect(): void; - // (undocumented) - posFromRelativePos(relativePos: IRelativePosition): number; - protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; - protected processGCDataCore(serializer: IFluidSerializer): void; - // (undocumented) - removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; - // (undocumented) - removeRange(start: number, end: number): void; - protected replaceRange(start: number, end: number, segment: ISegment): void; - // (undocumented) - resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; - protected reSubmitCore(content: any, localOpMetadata: unknown): void; - // (undocumented) - readonly segmentFromSpec: (spec: IJSONSegment) => ISegment; - protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; - // (undocumented) - walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; -} - // @alpha export const SharedString: ISharedObjectKind & SharedObjectKind; // @alpha export type SharedString = ISharedString; -// @alpha @deprecated -export class SharedStringClass extends SharedSegmentSequence implements ISharedString { - constructor(document: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes); - annotateMarker(marker: Marker, props: PropertySet): void; - getMarkerFromId(id: string): ISegment | undefined; - getText(start?: number, end?: number): string; - // (undocumented) - getTextRangeWithMarkers(start: number, end: number): string; - getTextWithPlaceholders(start?: number, end?: number): string; - // (undocumented) - id: string; - insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): void; - insertMarkerRelative(relativePos1: IRelativePosition, refType: ReferenceType, props?: PropertySet): void; - insertText(pos: number, text: string, props?: PropertySet): void; - insertTextRelative(relativePos1: IRelativePosition, text: string, props?: PropertySet): void; - // (undocumented) - get ISharedString(): ISharedString; - removeText(start: number, end: number): void; - replaceText(start: number, end: number, text: string, props?: PropertySet): void; - protected rollback(content: any, localOpMetadata: unknown): void; - searchForMarker(startPos: number, markerLabel: string, forwards?: boolean): Marker | undefined; -} - // @alpha export type SharedStringRevertible = MergeTreeDeltaRevertible | IntervalRevertible; diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index a0465e4997d5..89cc5abab922 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/sequence", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed sequence", "homepage": "https://fluidframework.com", "repository": { @@ -155,12 +155,12 @@ "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/sequence-previous": "npm:@fluidframework/sequence@~2.4.0", + "@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/diff": "^3.5.1", @@ -192,7 +192,77 @@ } }, "typeValidation": { - "broken": {}, + "broken": { + "ClassStatics_SequenceDeltaEvent": { + "backCompat": false + }, + "ClassStatics_SequenceEvent": { + "backCompat": false + }, + "ClassStatics_SequenceInterval": { + "backCompat": false + }, + "ClassStatics_SequenceMaintenanceEvent": { + "backCompat": false + }, + "Class_BaseSegment": { + "backCompat": false + }, + "Class_Marker": { + "backCompat": false + }, + "Class_SequenceDeltaEvent": { + "backCompat": false + }, + "Class_SequenceEvent": { + "backCompat": false + }, + "Class_SequenceInterval": { + "backCompat": false + }, + "Class_SequenceMaintenanceEvent": { + "backCompat": false + }, + "Class_TextSegment": { + "backCompat": false + }, + "Class_TrackingGroup": { + "backCompat": false + }, + "ClassStatics_BaseSegment": { + "backCompat": false + }, + "ClassStatics_Marker": { + "backCompat": false + }, + "ClassStatics_TextSegment": { + "backCompat": false + }, + "ClassStatics_TrackingGroup": { + "backCompat": false + }, + "Interface_ISegment": { + "backCompat": false + }, + "Interface_ISequenceDeltaRange": { + "backCompat": false + }, + "Interface_ISerializableInterval": { + "backCompat": false + }, + "Interface_ReferencePosition": { + "backCompat": false + }, + "TypeAlias_IntervalRevertible": { + "backCompat": false + }, + "TypeAlias_SharedStringRevertible": { + "backCompat": false + }, + "TypeAlias_SharedStringSegment": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/dds/sequence/src/intervalCollection.ts b/packages/dds/sequence/src/intervalCollection.ts index b2eb58d8cfa6..c8c11d72e916 100644 --- a/packages/dds/sequence/src/intervalCollection.ts +++ b/packages/dds/sequence/src/intervalCollection.ts @@ -15,7 +15,6 @@ import { DetachedReferencePosition, ISegment, LocalReferencePosition, - MergeTreeDeltaType, PropertySet, ReferenceType, SlidingPreference, @@ -29,6 +28,8 @@ import { Side, SequencePlace, endpointPosAndSide, + PropertiesManager, + type ISegmentInternal, } from "@fluidframework/merge-tree/internal"; import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal"; import { v4 as uuid } from "uuid"; @@ -60,12 +61,14 @@ import { IntervalStickiness, IntervalType, SequenceInterval, + SequenceIntervalClass, SerializedIntervalDelta, createInterval, createPositionReferenceFromSegoff, endReferenceSlidingPreference, sequenceIntervalHelpers, startReferenceSlidingPreference, + type ISerializableIntervalPrivate, } from "./intervals/index.js"; export const reservedIntervalIdKey = "intervalId"; @@ -313,7 +316,7 @@ export class LocalIntervalCollection { } private linkEndpointsToInterval(interval: TInterval): void { - if (interval instanceof SequenceInterval) { + if (interval instanceof SequenceIntervalClass) { interval.start.addProperties({ interval }); interval.end.addProperties({ interval }); } @@ -382,15 +385,15 @@ export class LocalIntervalCollection { ref.canSlideToEndpoint, ); }; - if (interval instanceof SequenceInterval) { - let previousInterval: (TInterval & SequenceInterval) | undefined; + if (interval instanceof SequenceIntervalClass) { + let previousInterval: (TInterval & SequenceIntervalClass) | undefined; let pendingChanges = 0; interval.addPositionChangeListeners( () => { pendingChanges++; // Note: both start and end can change and invoke beforeSlide on each endpoint before afterSlide. if (!previousInterval) { - previousInterval = interval.clone() as TInterval & SequenceInterval; + previousInterval = interval.clone() as TInterval & SequenceIntervalClass; previousInterval.start = cloneRef(previousInterval.start); previousInterval.end = cloneRef(previousInterval.end); this.removeIntervalFromIndexes(interval); @@ -413,7 +416,7 @@ export class LocalIntervalCollection { } private removeIntervalListeners(interval: TInterval) { - if (interval instanceof SequenceInterval) { + if (interval instanceof SequenceIntervalClass) { interval.removePositionChangeListeners(); } } @@ -1147,7 +1150,7 @@ export class IntervalCollection // is restored as single-endpoint changes re-use previous references. let startRefType: ReferenceType; let endRefType: ReferenceType; - if (previousInterval instanceof SequenceInterval) { + if (previousInterval instanceof SequenceIntervalClass) { startRefType = previousInterval.start.refType; endRefType = previousInterval.end.refType; previousInterval.start.refType = ReferenceType.Transient; @@ -1165,7 +1168,7 @@ export class IntervalCollection /** * {@inheritdoc IIntervalCollection.getIntervalById} */ - public getIntervalById(id: string): TInterval | undefined { + public getIntervalById(id: string): ISerializableIntervalPrivate | undefined { if (!this.localCollection) { throw new LoggingError("attach must be called before accessing intervals"); } @@ -1221,7 +1224,7 @@ export class IntervalCollection ); if (interval) { - if (!this.isCollaborating && interval instanceof SequenceInterval) { + if (!this.isCollaborating && interval instanceof SequenceIntervalClass) { setSlideOnRemove(interval.start); setSlideOnRemove(interval.end); } @@ -1324,15 +1327,18 @@ export class IntervalCollection let deltaProps: PropertySet | undefined; let newInterval: TInterval | undefined; if (props !== undefined) { - deltaProps = interval.addProperties( - props, - true, + interval.propertyManager ??= new PropertiesManager(); + deltaProps = interval.propertyManager.handleProperties( + { props }, + interval, this.isCollaborating ? UnassignedSequenceNumber : UniversalSequenceNumber, + UniversalSequenceNumber, + true, ); } if (start !== undefined && end !== undefined) { newInterval = this.localCollection.changeInterval(interval, start, end); - if (!this.isCollaborating && newInterval instanceof SequenceInterval) { + if (!this.isCollaborating && newInterval instanceof SequenceIntervalClass) { setSlideOnRemove(newInterval.start); setSlideOnRemove(newInterval.end); } @@ -1370,7 +1376,7 @@ export class IntervalCollection if (newInterval) { this.addPendingChange(id, serializedInterval); this.emitChange(newInterval, interval, true, false); - if (interval instanceof SequenceInterval) { + if (interval instanceof SequenceIntervalClass) { this.client?.removeLocalReferencePosition(interval.start); this.client?.removeLocalReferencePosition(interval.end); } @@ -1476,17 +1482,18 @@ export class IntervalCollection // strip it out of the properties here. const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties ?? {}; assert(id !== undefined, 0x3fe /* id must exist on the interval */); - const interval: TInterval | undefined = this.getIntervalById(id); + const interval: ISerializableIntervalPrivate | undefined = + this.getIntervalById(id); if (!interval) { // The interval has been removed locally; no-op. return; } if (local) { + interval.propertyManager ??= new PropertiesManager(); // Let the propertyManager prune its pending change-properties set. - interval.propertyManager?.ackPendingProperties({ - type: MergeTreeDeltaType.ANNOTATE, - props: serializedInterval.properties ?? {}, + interval.propertyManager.ack(op.sequenceNumber, op.minimumSequenceNumber, { + props: newProps, }); this.ackInterval(interval, op); @@ -1515,7 +1522,14 @@ export class IntervalCollection op, ) ?? interval; } - const deltaProps = newInterval.addProperties(newProps, true, op.sequenceNumber); + newInterval.propertyManager ??= new PropertiesManager(); + const deltaProps = newInterval.propertyManager.handleProperties( + { props: newProps }, + newInterval, + op.sequenceNumber, + op.minimumSequenceNumber, + true, + ); if (this.onDeserialize) { this.onDeserialize(newInterval); } @@ -1611,7 +1625,7 @@ export class IntervalCollection if (localInterval !== undefined) { // we know we must be using `SequenceInterval` because `this.client` exists assert( - localInterval instanceof SequenceInterval, + localInterval instanceof SequenceIntervalClass, 0x3a0 /* localInterval must be `SequenceInterval` when used with client */, ); // The rebased op may place this interval's endpoints on different segments. Calling `changeInterval` here @@ -1635,7 +1649,10 @@ export class IntervalCollection if (!this.client) { throw new LoggingError("client does not exist"); } - const segoff = { segment: lref.getSegment(), offset: lref.getOffset() }; + const segoff: { segment: ISegmentInternal | undefined; offset: number | undefined } = { + segment: lref.getSegment(), + offset: lref.getOffset(), + }; if (segoff.segment?.localRefs?.has(lref) !== true) { return undefined; } @@ -1653,7 +1670,7 @@ export class IntervalCollection private ackInterval(interval: TInterval, op: ISequencedDocumentMessage): void { // Only SequenceIntervals need potential sliding - if (!(interval instanceof SequenceInterval)) { + if (!(interval instanceof SequenceIntervalClass)) { return; } @@ -1719,7 +1736,7 @@ export class IntervalCollection if (props) { interval.start.addProperties(props); } - const oldSeg = oldInterval.start.getSegment(); + const oldSeg: ISegmentInternal | undefined = oldInterval.start.getSegment(); // remove and rebuild start interval as transient for event this.client.removeLocalReferencePosition(oldInterval.start); oldInterval.start.refType = ReferenceType.Transient; @@ -1741,7 +1758,7 @@ export class IntervalCollection interval.end.addProperties(props); } // remove and rebuild end interval as transient for event - const oldSeg = oldInterval.end.getSegment(); + const oldSeg: ISegmentInternal | undefined = oldInterval.end.getSegment(); this.client.removeLocalReferencePosition(oldInterval.end); oldInterval.end.refType = ReferenceType.Transient; oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset()); diff --git a/packages/dds/sequence/src/intervalIndex/overlappingSequenceIntervalsIndex.ts b/packages/dds/sequence/src/intervalIndex/overlappingSequenceIntervalsIndex.ts index c550dbfb0096..05e9d9b2ba52 100644 --- a/packages/dds/sequence/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +++ b/packages/dds/sequence/src/intervalIndex/overlappingSequenceIntervalsIndex.ts @@ -16,6 +16,7 @@ import { import { IntervalType, SequenceInterval, + SequenceIntervalClass, createPositionReferenceFromSegoff, sequenceIntervalHelpers, } from "../intervals/index.js"; @@ -56,7 +57,7 @@ class OverlappingSequenceIntervalsIndex return []; } - const transientInterval = new SequenceInterval( + const transientInterval = new SequenceIntervalClass( this.client, startLref, endLref, diff --git a/packages/dds/sequence/src/intervals/index.ts b/packages/dds/sequence/src/intervals/index.ts index 16340b243089..9f52bdce3063 100644 --- a/packages/dds/sequence/src/intervals/index.ts +++ b/packages/dds/sequence/src/intervals/index.ts @@ -12,6 +12,7 @@ export { IntervalDeltaOpType, IIntervalHelpers, IntervalStickiness, + ISerializableIntervalPrivate, SerializedIntervalDelta, CompressedSerializedInterval, endReferenceSlidingPreference, @@ -20,6 +21,7 @@ export { export { Interval, createInterval, intervalHelpers } from "./interval.js"; export { SequenceInterval, + SequenceIntervalClass, createSequenceInterval, createPositionReferenceFromSegoff, sequenceIntervalHelpers, diff --git a/packages/dds/sequence/src/intervals/interval.ts b/packages/dds/sequence/src/intervals/interval.ts index 5aefdd8c85c8..fad254118bec 100644 --- a/packages/dds/sequence/src/intervals/interval.ts +++ b/packages/dds/sequence/src/intervals/interval.ts @@ -14,6 +14,7 @@ import { reservedRangeLabelsKey, SequencePlace, addProperties, + copyPropertiesAndManager, } from "@fluidframework/merge-tree/internal"; import { UsageError } from "@fluidframework/telemetry-utils/internal"; @@ -38,10 +39,7 @@ export class Interval implements ISerializableInterval { /***/ public auxProps: PropertySet[] | undefined; - /** - * {@inheritDoc ISerializableInterval.propertyManager} - */ - public readonly propertyManager: PropertiesManager = new PropertiesManager(); + public propertyManager?: PropertiesManager; constructor( public start: number, @@ -172,19 +170,6 @@ export class Interval implements ISerializableInterval { return this.properties; } - /** - * {@inheritDoc ISerializableInterval.addProperties} - */ - public addProperties( - newProps: PropertySet, - collaborating: boolean = false, - seq?: number, - ): PropertySet | undefined { - if (newProps) { - return this.propertyManager.addProperties(this.properties, newProps, seq, collaborating); - } - } - /** * {@inheritDoc IInterval.modify} */ @@ -208,13 +193,7 @@ export class Interval implements ISerializableInterval { return; } const newInterval = new Interval(startPos, endPos); - if (this.properties) { - this.propertyManager.copyTo( - this.properties, - newInterval.properties, - newInterval.propertyManager, - ); - } + copyPropertiesAndManager(this, newInterval); return newInterval; } } diff --git a/packages/dds/sequence/src/intervals/intervalUtils.ts b/packages/dds/sequence/src/intervals/intervalUtils.ts index 2c32acd04f09..91f36d491539 100644 --- a/packages/dds/sequence/src/intervals/intervalUtils.ts +++ b/packages/dds/sequence/src/intervals/intervalUtils.ts @@ -160,31 +160,22 @@ export interface ISerializedInterval { export interface ISerializableInterval extends IInterval { /** Serializable bag of properties associated with the interval. */ properties: PropertySet; - /** - * @deprecated - This property should not be used externally and will be removed in a subsequent release. - */ - // eslint-disable-next-line import/no-deprecated - propertyManager: PropertiesManager; + /***/ serialize(): ISerializedInterval; - /** - * @deprecated - This function should not be used externally and will be removed in a subsequent release. - */ - addProperties( - props: PropertySet, - collaborating?: boolean, - seq?: number, - ): PropertySet | undefined; + /** * Gets the id associated with this interval. * When the interval is used as part of an interval collection, this id can be used to modify or remove the * interval. - * @remarks This signature includes `undefined` strictly for backwards-compatibility reasons, as older versions - * of Fluid didn't always write interval ids. */ - getIntervalId(): string | undefined; + getIntervalId(): string; } +export type ISerializableIntervalPrivate = T & { + propertyManager?: PropertiesManager; +}; + /** * Represents a change that should be applied to an existing interval. * Changes can modify any of start/end/properties, with `undefined` signifying no change should be made. diff --git a/packages/dds/sequence/src/intervals/sequenceInterval.ts b/packages/dds/sequence/src/intervals/sequenceInterval.ts index 928b50af66a9..1068b5d891f9 100644 --- a/packages/dds/sequence/src/intervals/sequenceInterval.ts +++ b/packages/dds/sequence/src/intervals/sequenceInterval.ts @@ -28,6 +28,7 @@ import { Side, endpointPosAndSide, addProperties, + copyPropertiesAndManager, } from "@fluidframework/merge-tree/internal"; import { UsageError } from "@fluidframework/telemetry-utils/internal"; @@ -38,13 +39,13 @@ import { } from "../intervalCollection.js"; import { - IIntervalHelpers, ISerializableInterval, ISerializedInterval, IntervalStickiness, IntervalType, endReferenceSlidingPreference, startReferenceSlidingPreference, + type IIntervalHelpers, } from "./intervalUtils.js"; function compareSides(sideA: Side, sideB: Side): number { @@ -101,10 +102,88 @@ function maxSide(sideA: Side, sideB: Side): Side { * `mergeTreeReferencesCanSlideToEndpoint` feature flag set to true, the endpoints * of the interval that are exclusive will have the ability to slide to these * special endpoint segments. - * @legacy * @alpha + * @legacy */ -export class SequenceInterval implements ISerializableInterval { +export interface SequenceInterval extends ISerializableInterval { + readonly start: LocalReferencePosition; + /** + * End endpoint of this interval. + * @remarks This endpoint can be resolved into a character position using the SharedString it's a part of. + */ + readonly end: LocalReferencePosition; + readonly intervalType: IntervalType; + readonly startSide: Side; + readonly endSide: Side; + readonly stickiness: IntervalStickiness; + + /** + * @returns a new interval object with identical semantics. + */ + clone(): SequenceInterval; + /** + * Compares this interval to `b` with standard comparator semantics: + * - returns -1 if this is less than `b` + * - returns 1 if this is greater than `b` + * - returns 0 if this is equivalent to `b` + * @param b - Interval to compare against + */ + compare(b: SequenceInterval): number; + /** + * Compares the start endpoint of this interval to `b`'s start endpoint. + * Standard comparator semantics apply. + * @param b - Interval to compare against + */ + compareStart(b: SequenceInterval): number; + /** + * Compares the end endpoint of this interval to `b`'s end endpoint. + * Standard comparator semantics apply. + * @param b - Interval to compare against + */ + compareEnd(b: SequenceInterval): number; + /** + * Modifies one or more of the endpoints of this interval, returning a new interval representing the result. + */ + modify( + label: string, + start: SequencePlace | undefined, + end: SequencePlace | undefined, + op?: ISequencedDocumentMessage, + localSeq?: number, + useNewSlidingBehavior?: boolean, + ): SequenceInterval | undefined; + /** + * @returns whether this interval overlaps with `b`. + * Intervals are considered to overlap if their intersection is non-empty. + */ + overlaps(b: SequenceInterval): boolean; + /** + * Unions this interval with `b`, returning a new interval. + * The union operates as a convex hull, i.e. if the two intervals are disjoint, the return value includes + * intermediate values between the two intervals. + */ + union(b: SequenceInterval): SequenceInterval; + + /** + * Subscribes to position change events on this interval if there are no current listeners. + */ + addPositionChangeListeners( + beforePositionChange: () => void, + afterPositionChange: () => void, + ): void; + + /** + * Removes the currently subscribed position change listeners. + */ + removePositionChangeListeners(): void; + + /** + * @returns whether this interval overlaps two numerical positions. + */ + overlapsPos(bstart: number, bend: number): boolean; +} + +export class SequenceIntervalClass implements SequenceInterval { /** * {@inheritDoc ISerializableInterval.properties} */ @@ -113,7 +192,7 @@ export class SequenceInterval implements ISerializableInterval { /** * {@inheritDoc ISerializableInterval.propertyManager} */ - public propertyManager: PropertiesManager = new PropertiesManager(); + public propertyManager?: PropertiesManager; /***/ public get stickiness(): IntervalStickiness { @@ -127,9 +206,6 @@ export class SequenceInterval implements ISerializableInterval { ); } - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ constructor( private readonly client: Client, /** @@ -215,8 +291,8 @@ export class SequenceInterval implements ISerializableInterval { /** * {@inheritDoc IInterval.clone} */ - public clone() { - return new SequenceInterval( + public clone(): SequenceInterval { + return new SequenceIntervalClass( this.client, this.start, this.end, @@ -320,7 +396,7 @@ export class SequenceInterval implements ISerializableInterval { endSide = this.end === newEnd ? this.endSide : b.endSide; } - return new SequenceInterval( + return new SequenceIntervalClass( this.client, newStart, newEnd, @@ -331,17 +407,6 @@ export class SequenceInterval implements ISerializableInterval { ); } - /** - * {@inheritDoc ISerializableInterval.addProperties} - */ - public addProperties( - newProps: PropertySet, - collab: boolean = false, - seq?: number, - ): PropertySet | undefined { - return this.propertyManager.addProperties(this.properties, newProps, seq, collab); - } - /** * @returns whether this interval overlaps two numerical positions. */ @@ -414,7 +479,7 @@ export class SequenceInterval implements ISerializableInterval { } } - const newInterval = new SequenceInterval( + const newInterval = new SequenceIntervalClass( this.client, startRef, endRef, @@ -423,13 +488,7 @@ export class SequenceInterval implements ISerializableInterval { startSide ?? this.startSide, endSide ?? this.endSide, ); - if (this.properties) { - this.propertyManager.copyTo( - this.properties, - newInterval.properties, - newInterval.propertyManager, - ); - } + copyPropertiesAndManager(this, newInterval); return newInterval; } } @@ -604,7 +663,7 @@ export function createSequenceInterval( startLref.addProperties(rangeProp); endLref.addProperties(rangeProp); - const ival = new SequenceInterval( + const ival = new SequenceIntervalClass( client, startLref, endLref, diff --git a/packages/dds/sequence/src/packageVersion.ts b/packages/dds/sequence/src/packageVersion.ts index b460754004f7..90217111c28f 100644 --- a/packages/dds/sequence/src/packageVersion.ts +++ b/packages/dds/sequence/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/sequence"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/sequence/src/revertibles.ts b/packages/dds/sequence/src/revertibles.ts index 6db0f9b38ce8..4c7420eae9eb 100644 --- a/packages/dds/sequence/src/revertibles.ts +++ b/packages/dds/sequence/src/revertibles.ts @@ -26,7 +26,7 @@ import { type ISegmentInternal, } from "@fluidframework/merge-tree/internal"; -import { IntervalOpType, SequenceInterval } from "./intervals/index.js"; +import { IntervalOpType, SequenceInterval, SequenceIntervalClass } from "./intervals/index.js"; import { ISequenceDeltaRange, SequenceDeltaEvent } from "./sequenceDeltaEvent.js"; import { ISharedString, SharedStringSegment } from "./sharedString.js"; @@ -244,13 +244,13 @@ function addIfIntervalEndpoint( ) { if (refTypeIncludesFlag(ref.refType, ReferenceType.RangeBegin)) { const interval = ref.properties?.interval; - if (interval && interval instanceof SequenceInterval) { + if (interval && interval instanceof SequenceIntervalClass) { startIntervals.push({ offset: segmentLengths + interval.start.getOffset(), interval }); return true; } } else if (refTypeIncludesFlag(ref.refType, ReferenceType.RangeEnd)) { const interval = ref.properties?.interval; - if (interval && interval instanceof SequenceInterval) { + if (interval && interval instanceof SequenceIntervalClass) { endIntervals.push({ offset: segmentLengths + interval.end.getOffset(), interval }); return true; } diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 2dcaa1e0af14..c97517f9c5e6 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -81,7 +81,12 @@ import { type SequenceOptions, } from "./intervalCollectionMapInterfaces.js"; import { SequenceInterval } from "./intervals/index.js"; -import { SequenceDeltaEvent, SequenceMaintenanceEvent } from "./sequenceDeltaEvent.js"; +import { + SequenceDeltaEvent, + SequenceDeltaEventClass, + SequenceMaintenanceEvent, + SequenceMaintenanceEventClass, +} from "./sequenceDeltaEvent.js"; import { ISharedIntervalCollection } from "./sharedIntervalCollection.js"; const snapshotFileName = "header"; @@ -362,9 +367,7 @@ export interface ISharedSegmentSequence } /** - * @legacy - * @alpha - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export abstract class SharedSegmentSequence extends SharedObject @@ -534,15 +537,21 @@ export abstract class SharedSegmentSequence ); this.client.prependListener("delta", (opArgs, deltaArgs) => { - const event = new SequenceDeltaEvent(opArgs, deltaArgs, this.client); + const event = new SequenceDeltaEventClass(opArgs, deltaArgs, this.client); if (event.isLocal) { this.submitSequenceMessage(opArgs.op); } - this.emit("sequenceDelta", event, this); + if (deltaArgs.deltaSegments.length > 0) { + this.emit("sequenceDelta", event, this); + } }); this.client.on("maintenance", (args, opArgs) => { - this.emit("maintenance", new SequenceMaintenanceEvent(opArgs, args, this.client), this); + this.emit( + "maintenance", + new SequenceMaintenanceEventClass(opArgs, args, this.client), + this, + ); }); this.intervalCollections = new IntervalCollectionMap( diff --git a/packages/dds/sequence/src/sequenceDeltaEvent.ts b/packages/dds/sequence/src/sequenceDeltaEvent.ts index 2c3bcde4b881..e33e00f46b17 100644 --- a/packages/dds/sequence/src/sequenceDeltaEvent.ts +++ b/packages/dds/sequence/src/sequenceDeltaEvent.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { assert } from "@fluidframework/core-utils/internal"; +import { assert, Lazy } from "@fluidframework/core-utils/internal"; import { // eslint-disable-next-line import/no-deprecated Client, @@ -13,6 +13,7 @@ import { ISegment, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, + MergeTreeDeltaType, MergeTreeMaintenanceType, PropertySet, // eslint-disable-next-line import/no-deprecated SortedSegmentSet, @@ -27,19 +28,49 @@ import { * @legacy * @alpha */ -export abstract class SequenceEvent< +export interface SequenceEvent< TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, > { + readonly deltaOperation: TOperation; + + readonly deltaArgs: IMergeTreeDeltaCallbackArgs; + /** + * The in-order ranges affected by this delta. + * These are not necessarily contiguous. + * + * @remarks - If processing code doesn't care about the order of the ranges, it may instead consider using the + * {@link @fluidframework/merge-tree#IMergeTreeDeltaCallbackArgs.deltaSegments|deltaSegments} field on {@link SequenceEvent.deltaArgs|deltaArgs}. + */ + readonly ranges: readonly Readonly>[]; + + /** + * The client id of the client that made the change which caused the delta event + */ + readonly clientId: string | undefined; + + /** + * The first of the modified ranges. + */ + readonly first: Readonly>; + + /** + * The last of the modified ranges. + */ + readonly last: Readonly>; +} +export abstract class SequenceEventClass< + TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, +> implements SequenceEvent +{ + public readonly isLocal: boolean; public readonly deltaOperation: TOperation; // eslint-disable-next-line import/no-deprecated private readonly sortedRanges: Lazy>>; private readonly pFirst: Lazy>; private readonly pLast: Lazy>; - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ constructor( + public readonly opArgs: IMergeTreeDeltaOpArgs | undefined, /** * Arguments reflecting the type of change that caused this event. */ @@ -47,11 +78,17 @@ export abstract class SequenceEvent< // eslint-disable-next-line import/no-deprecated private readonly mergeTreeClient: Client, ) { - assert( - deltaArgs.deltaSegments.length > 0, - 0x2d8 /* "Empty change event should not be emitted." */, - ); + if ( + deltaArgs.operation !== MergeTreeDeltaType.OBLITERATE && + deltaArgs.operation !== MergeTreeMaintenanceType.ACKNOWLEDGED + ) { + assert( + deltaArgs.deltaSegments.length > 0, + 0x2d8 /* "Empty change event should not be emitted." */, + ); + } this.deltaOperation = deltaArgs.operation; + this.isLocal = opArgs?.sequencedMessage === undefined; // eslint-disable-next-line import/no-deprecated this.sortedRanges = new Lazy>>(() => { @@ -124,23 +161,25 @@ export abstract class SequenceEvent< * @legacy * @alpha */ -export class SequenceDeltaEvent extends SequenceEvent { - /** - * Whether the event was caused by a locally-made change. - */ - public readonly isLocal: boolean; +export interface SequenceDeltaEvent extends SequenceEvent { + readonly opArgs: IMergeTreeDeltaOpArgs; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * Whether the event was caused by a locally-made change. */ + readonly isLocal: boolean; +} +export class SequenceDeltaEventClass + extends SequenceEventClass + implements SequenceDeltaEvent +{ constructor( public readonly opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, // eslint-disable-next-line import/no-deprecated mergeTreeClient: Client, ) { - super(deltaArgs, mergeTreeClient); - this.isLocal = opArgs.sequencedMessage === undefined; + super(opArgs, deltaArgs, mergeTreeClient); } } @@ -153,10 +192,13 @@ export class SequenceDeltaEvent extends SequenceEvent { - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ +export interface SequenceMaintenanceEvent extends SequenceEvent { + readonly opArgs: IMergeTreeDeltaOpArgs | undefined; +} +export class SequenceMaintenanceEventClass + extends SequenceEventClass + implements SequenceMaintenanceEvent +{ constructor( /** * Defined iff `deltaArgs.operation` is {@link @fluidframework/merge-tree#MergeTreeMaintenanceType.ACKNOWLEDGED|MergeTreeMaintenanceType.ACKNOWLEDGED}. @@ -168,7 +210,7 @@ export class SequenceMaintenanceEvent extends SequenceEvent { - private pValue: T | undefined; - private pEvaluated: boolean; - constructor(private readonly valueGenerator: () => T) { - this.pEvaluated = false; - } - - public get evaluated(): boolean { - return this.pEvaluated; - } - - public get value(): T { - if (!this.pEvaluated) { - this.pEvaluated = true; - this.pValue = this.valueGenerator(); - } - return this.pValue as T; - } -} diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 56bed9b70c7a..be60dd29cd3f 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -140,9 +140,7 @@ export type SharedStringSegment = TextSegment | Marker; * In addition to text, a Shared String can also contain markers. Markers can be * used to store metadata at positions within the text, like the details of an * image or Fluid object that should be rendered with the text. - * @legacy - * @alpha - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export class SharedStringClass // eslint-disable-next-line import/no-deprecated diff --git a/packages/dds/sequence/src/test/overlappingSequenceIntervalsIndex.spec.ts b/packages/dds/sequence/src/test/overlappingSequenceIntervalsIndex.spec.ts index ea2f950b98b8..e8200ca2913a 100644 --- a/packages/dds/sequence/src/test/overlappingSequenceIntervalsIndex.spec.ts +++ b/packages/dds/sequence/src/test/overlappingSequenceIntervalsIndex.spec.ts @@ -18,7 +18,7 @@ import { createOverlappingIntervalsIndex, createOverlappingSequenceIntervalsIndex, } from "../intervalIndex/index.js"; -import { SequenceInterval } from "../intervals/index.js"; +import { SequenceInterval, SequenceIntervalClass } from "../intervals/index.js"; import { SharedStringFactory, type SharedString } from "../sequenceFactory.js"; import { SharedStringClass } from "../sharedString.js"; @@ -38,7 +38,7 @@ function assertSequenceIntervalsEqual( let expectedStart; let expectedEnd; - if (expected[i] instanceof SequenceInterval) { + if (expected[i] instanceof SequenceIntervalClass) { expectedStart = string.localReferencePositionToPosition( expected[i].start as LocalReferencePosition, ); diff --git a/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts b/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts index b625d967fd9e..f32903f1ab21 100644 --- a/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts +++ b/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts @@ -16,7 +16,7 @@ import { } from "@fluidframework/merge-tree/internal"; import { TestClient } from "@fluidframework/merge-tree/internal/test"; -import { SequenceDeltaEvent } from "../sequenceDeltaEvent.js"; +import { SequenceDeltaEventClass } from "../sequenceDeltaEvent.js"; interface IExpectedSegmentInfo { offset: number; @@ -68,7 +68,7 @@ describe("non-collab", () => { assert.equal(deltaArgs.deltaSegments.length, 1); assert(op); - const event = new SequenceDeltaEvent({ op }, deltaArgs, client); + const event = new SequenceDeltaEventClass({ op }, deltaArgs, client); assert(event.isLocal); assert.equal(event.ranges.length, 1); @@ -117,7 +117,7 @@ describe("non-collab", () => { assert.equal(deltaArgs.deltaSegments.length, 1); assert(op); - const event = new SequenceDeltaEvent({ op }, deltaArgs, client); + const event = new SequenceDeltaEventClass({ op }, deltaArgs, client); assert(event.isLocal); assert.equal(event.ranges.length, 1); @@ -237,7 +237,7 @@ describe("non-collab", () => { assert.equal(deltaArgs.deltaSegments.length, expected.length); assert(op); - const event = new SequenceDeltaEvent({ op }, deltaArgs, client); + const event = new SequenceDeltaEventClass({ op }, deltaArgs, client); assert(event.isLocal); assert.equal(event.first.position, start); @@ -291,9 +291,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -341,9 +341,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -391,9 +391,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -442,9 +442,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -494,9 +494,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -544,9 +544,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -600,9 +600,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -674,9 +674,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage1 = client.makeOpMessage( @@ -749,9 +749,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -806,9 +806,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -863,9 +863,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - const events: SequenceDeltaEvent[] = []; + const events: SequenceDeltaEventClass[] = []; client.on("delta", (clientArgs, mergeTreeArgs) => { - events.push(new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client)); + events.push(new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client)); }); const localRemoveMessage = client.makeOpMessage( @@ -907,9 +907,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - const events: SequenceDeltaEvent[] = []; + const events: SequenceDeltaEventClass[] = []; client.on("delta", (clientArgs, mergeTreeArgs) => { - events.push(new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client)); + events.push(new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client)); }); const localRemoveMessage = client.makeOpMessage( @@ -951,9 +951,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - const events: SequenceDeltaEvent[] = []; + const events: SequenceDeltaEventClass[] = []; client.on("delta", (clientArgs, mergeTreeArgs) => { - events.push(new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client)); + events.push(new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client)); }); const localRemoveMessage = client.makeOpMessage( @@ -995,9 +995,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - const events: SequenceDeltaEvent[] = []; + const events: SequenceDeltaEventClass[] = []; client.on("delta", (clientArgs, mergeTreeArgs) => { - events.push(new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client)); + events.push(new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client)); }); const localRemoveMessage = client.makeOpMessage( client.removeRangeLocal(localRemovePosStart, localRemovePosEnd), @@ -1039,9 +1039,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1094,9 +1094,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1149,9 +1149,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1204,9 +1204,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1258,9 +1258,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1311,9 +1311,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -1372,9 +1372,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1424,9 +1424,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1475,9 +1475,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1527,9 +1527,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1578,9 +1578,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1630,9 +1630,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1721,9 +1721,9 @@ describe("collab", () => { function initialize() { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage1 = client.makeOpMessage( @@ -1784,9 +1784,9 @@ describe("collab", () => { } function step1(seqnum: number, refseqnum: number) { - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const remoteMessage = client.makeOpMessage( @@ -1846,9 +1846,9 @@ describe("collab", () => { } function step2(seqnum: number, refseqnum: number) { - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1879,9 +1879,9 @@ describe("collab", () => { } function step3(seqnum: number, refseqnum: number) { - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const remoteMessage = client.makeOpMessage( @@ -1920,9 +1920,9 @@ describe("collab", () => { } function step4(seqnum: number, refseqnum: number) { - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localMessage = client.makeOpMessage( @@ -1974,7 +1974,7 @@ describe("collab", () => { } function verifyEventForAnnotate( - event: SequenceDeltaEvent, + event: SequenceDeltaEventClass, isLocal: boolean, start: number, end: number, @@ -2032,9 +2032,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2089,9 +2089,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2146,9 +2146,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2200,9 +2200,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2254,9 +2254,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2308,9 +2308,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2362,9 +2362,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2416,9 +2416,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2470,9 +2470,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2526,9 +2526,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2583,9 +2583,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2637,9 +2637,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2691,9 +2691,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2745,9 +2745,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2799,9 +2799,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2853,9 +2853,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -2907,9 +2907,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -2973,9 +2973,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localInsertMessage = client.makeOpMessage( @@ -3040,9 +3040,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -3094,9 +3094,9 @@ describe("collab", () => { const currentSeqNumber = client.mergeTree.collabWindow.currentSeq; - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); const localRemoveMessage = client.makeOpMessage( @@ -3141,7 +3141,7 @@ describe("collab", () => { }); }); -describe("SequenceDeltaEvent", () => { +describe("SequenceDeltaEventClass", () => { const localUserLongId = "localUser"; let client: TestClient; @@ -3163,7 +3163,7 @@ describe("SequenceDeltaEvent", () => { assert.equal(deltaArgs.deltaSegments.length, 1); assert(op); - const event = new SequenceDeltaEvent({ op }, deltaArgs, client); + const event = new SequenceDeltaEventClass({ op }, deltaArgs, client); assert(event.isLocal); assert.equal(event.ranges.length, 1); @@ -3195,7 +3195,7 @@ describe("SequenceDeltaEvent", () => { assert.equal(deltaArgs.deltaSegments.length, segmentCount); assert(op); - const event = new SequenceDeltaEvent({ op }, deltaArgs, client); + const event = new SequenceDeltaEventClass({ op }, deltaArgs, client); assert(event.isLocal); assert.equal(event.ranges.length, segmentCount); @@ -3232,9 +3232,9 @@ describe("SequenceDeltaEvent", () => { client.insertTextLocal(i * 2 * textCount, "b".repeat(textCount)); } - let event: SequenceDeltaEvent | undefined; + let event: SequenceDeltaEventClass | undefined; client.on("delta", (clientArgs, mergeTreeArgs) => { - event = new SequenceDeltaEvent(clientArgs, mergeTreeArgs, client); + event = new SequenceDeltaEventClass(clientArgs, mergeTreeArgs, client); }); client.applyMsg(remoteRemoveMessage); diff --git a/packages/dds/sequence/src/test/sharedString.spec.ts b/packages/dds/sequence/src/test/sharedString.spec.ts index bde1189e8f7e..77afd011f94a 100644 --- a/packages/dds/sequence/src/test/sharedString.spec.ts +++ b/packages/dds/sequence/src/test/sharedString.spec.ts @@ -887,9 +887,7 @@ describe("Shared String Obliterate", () => { sharedString2.connect(services2); }); - // TODO: this test currently fails with 0x2d8 - // AB#19722 - it.skip("zero length obliterate in the middle of the string", () => { + it("zero length obliterate in the middle of the string", () => { sharedString.insertText(0, "0123456789"); containerRuntimeFactory.processAllMessages(); assert.equal( @@ -909,6 +907,6 @@ describe("Shared String Obliterate", () => { sharedString2.getText(), "end state should be equal after obliterate", ); - assert.equal(sharedString2.getText(), "01234AAA56789", "obliterate failed"); + assert.equal(sharedString2.getText(), "01234BBB56789", "obliterate failed"); }); }); diff --git a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index 60fc17997ead..5be31ee52cd7 100644 --- a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts +++ b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts @@ -31,6 +31,7 @@ declare type old_as_current_for_Class_BaseSegment = requireAssignableTo, TypeOnly> /* @@ -49,6 +50,7 @@ declare type old_as_current_for_Class_Marker = requireAssignableTo, TypeOnly> /* @@ -67,6 +69,7 @@ declare type old_as_current_for_Class_SequenceDeltaEvent = requireAssignableTo, TypeOnly> /* @@ -85,6 +88,7 @@ declare type old_as_current_for_Class_SequenceEvent = requireAssignableTo, TypeOnly> /* @@ -103,6 +107,7 @@ declare type old_as_current_for_Class_SequenceInterval = requireAssignableTo, TypeOnly> /* @@ -121,6 +126,7 @@ declare type old_as_current_for_Class_SequenceMaintenanceEvent = requireAssignab * typeValidation.broken: * "Class_SequenceMaintenanceEvent": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Class_SequenceMaintenanceEvent = requireAssignableTo, TypeOnly> /* @@ -175,6 +181,7 @@ declare type old_as_current_for_Class_TextSegment = requireAssignableTo, TypeOnly> /* @@ -193,6 +200,7 @@ declare type old_as_current_for_Class_TrackingGroup = requireAssignableTo, TypeOnly> /* @@ -202,6 +210,7 @@ declare type current_as_old_for_Class_TrackingGroup = requireAssignableTo, TypeOnly> /* @@ -211,6 +220,7 @@ declare type current_as_old_for_ClassStatics_BaseSegment = requireAssignableTo, TypeOnly> /* @@ -220,6 +230,7 @@ declare type current_as_old_for_ClassStatics_Marker = requireAssignableTo, TypeOnly> /* @@ -229,6 +240,7 @@ declare type current_as_old_for_ClassStatics_SequenceDeltaEvent = requireAssigna * typeValidation.broken: * "ClassStatics_SequenceEvent": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_SequenceEvent = requireAssignableTo, TypeOnly> /* @@ -238,6 +250,7 @@ declare type current_as_old_for_ClassStatics_SequenceEvent = requireAssignableTo * typeValidation.broken: * "ClassStatics_SequenceInterval": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_SequenceInterval = requireAssignableTo, TypeOnly> /* @@ -247,6 +260,7 @@ declare type current_as_old_for_ClassStatics_SequenceInterval = requireAssignabl * typeValidation.broken: * "ClassStatics_SequenceMaintenanceEvent": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_SequenceMaintenanceEvent = requireAssignableTo, TypeOnly> /* @@ -274,6 +288,7 @@ declare type current_as_old_for_ClassStatics_SharedStringClass = requireAssignab * typeValidation.broken: * "ClassStatics_TextSegment": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_TextSegment = requireAssignableTo, TypeOnly> /* @@ -283,6 +298,7 @@ declare type current_as_old_for_ClassStatics_TextSegment = requireAssignableTo, TypeOnly> /* @@ -535,6 +551,7 @@ declare type old_as_current_for_Interface_ISegment = requireAssignableTo, TypeOnly> /* @@ -553,6 +570,7 @@ declare type old_as_current_for_Interface_ISequenceDeltaRange = requireAssignabl * typeValidation.broken: * "Interface_ISequenceDeltaRange": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ISequenceDeltaRange = requireAssignableTo, TypeOnly> /* @@ -571,6 +589,7 @@ declare type old_as_current_for_Interface_ISerializableInterval = requireAssigna * typeValidation.broken: * "Interface_ISerializableInterval": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ISerializableInterval = requireAssignableTo, TypeOnly> /* @@ -706,6 +725,7 @@ declare type old_as_current_for_Interface_ReferencePosition = requireAssignableT * typeValidation.broken: * "Interface_ReferencePosition": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ReferencePosition = requireAssignableTo, TypeOnly> /* @@ -760,6 +780,7 @@ declare type old_as_current_for_TypeAlias_IntervalRevertible = requireAssignable * typeValidation.broken: * "TypeAlias_IntervalRevertible": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_IntervalRevertible = requireAssignableTo, TypeOnly> /* @@ -868,6 +889,7 @@ declare type old_as_current_for_TypeAlias_SharedStringRevertible = requireAssign * typeValidation.broken: * "TypeAlias_SharedStringRevertible": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_SharedStringRevertible = requireAssignableTo, TypeOnly> /* @@ -886,6 +908,7 @@ declare type old_as_current_for_TypeAlias_SharedStringSegment = requireAssignabl * typeValidation.broken: * "TypeAlias_SharedStringSegment": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_SharedStringSegment = requireAssignableTo, TypeOnly> /* diff --git a/packages/dds/shared-object-base/CHANGELOG.md b/packages/dds/shared-object-base/CHANGELOG.md index a1be20497c8c..e38e56037a9b 100644 --- a/packages/dds/shared-object-base/CHANGELOG.md +++ b/packages/dds/shared-object-base/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/shared-object-base +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/shared-object-base/package.json b/packages/dds/shared-object-base/package.json index 60e23a79433a..ee0461686cfc 100644 --- a/packages/dds/shared-object-base/package.json +++ b/packages/dds/shared-object-base/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/shared-object-base", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid base class for shared distributed data structures", "homepage": "https://fluidframework.com", "repository": { @@ -136,11 +136,11 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-pairwise-generator": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/shared-object-base-previous": "npm:@fluidframework/shared-object-base@~2.4.0", + "@fluidframework/shared-object-base-previous": "npm:@fluidframework/shared-object-base@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/benchmark": "^2.1.0", diff --git a/packages/dds/shared-object-base/src/packageVersion.ts b/packages/dds/shared-object-base/src/packageVersion.ts index 09c68b1d1b7f..bbd7c9a878d3 100644 --- a/packages/dds/shared-object-base/src/packageVersion.ts +++ b/packages/dds/shared-object-base/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/shared-object-base"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/shared-summary-block/CHANGELOG.md b/packages/dds/shared-summary-block/CHANGELOG.md index e18aa047521e..5f900b6b4ad5 100644 --- a/packages/dds/shared-summary-block/CHANGELOG.md +++ b/packages/dds/shared-summary-block/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/shared-summary-block +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/shared-summary-block/package.json b/packages/dds/shared-summary-block/package.json index 512ec8f27820..c66b41c2d216 100644 --- a/packages/dds/shared-summary-block/package.json +++ b/packages/dds/shared-summary-block/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/shared-summary-block", - "version": "2.5.0", + "version": "2.10.0", "description": "A DDS that does not generate ops but is part of summary", "homepage": "https://fluidframework.com", "repository": { @@ -129,12 +129,12 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/shared-summary-block-previous": "npm:@fluidframework/shared-summary-block@~2.4.0", + "@fluidframework/shared-summary-block-previous": "npm:@fluidframework/shared-summary-block@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/benchmark": "^2.1.0", diff --git a/packages/dds/shared-summary-block/src/packageVersion.ts b/packages/dds/shared-summary-block/src/packageVersion.ts index 24b46b0228d8..93bdc755c307 100644 --- a/packages/dds/shared-summary-block/src/packageVersion.ts +++ b/packages/dds/shared-summary-block/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/shared-summary-block"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/task-manager/CHANGELOG.md b/packages/dds/task-manager/CHANGELOG.md index 9bd67415b870..5d440bbbf7bd 100644 --- a/packages/dds/task-manager/CHANGELOG.md +++ b/packages/dds/task-manager/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/task-manager +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/task-manager/package.json b/packages/dds/task-manager/package.json index a6cc8fb2924d..2b204c4afcbb 100644 --- a/packages/dds/task-manager/package.json +++ b/packages/dds/task-manager/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/task-manager", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed data structure for queueing exclusive tasks", "homepage": "https://fluidframework.com", "repository": { @@ -134,11 +134,11 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/task-manager-previous": "npm:@fluidframework/task-manager@~2.4.0", + "@fluidframework/task-manager-previous": "npm:@fluidframework/task-manager@2.5.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/dds/task-manager/src/packageVersion.ts b/packages/dds/task-manager/src/packageVersion.ts index 8dec1a4aa04c..1272d1f2418d 100644 --- a/packages/dds/task-manager/src/packageVersion.ts +++ b/packages/dds/task-manager/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/task-manager"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/test-dds-utils/CHANGELOG.md b/packages/dds/test-dds-utils/CHANGELOG.md index 73c0684a24a1..3fe1ad09176e 100644 --- a/packages/dds/test-dds-utils/CHANGELOG.md +++ b/packages/dds/test-dds-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-dds-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/dds/test-dds-utils/package.json b/packages/dds/test-dds-utils/package.json index cf02994dad37..49ddf956cc26 100644 --- a/packages/dds/test-dds-utils/package.json +++ b/packages/dds/test-dds-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-dds-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid DDS test utilities", "homepage": "https://fluidframework.com", "repository": { @@ -102,9 +102,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/dds/tree/.eslintrc.cjs b/packages/dds/tree/.eslintrc.cjs index 4936d1b06758..3bd3d011bc37 100644 --- a/packages/dds/tree/.eslintrc.cjs +++ b/packages/dds/tree/.eslintrc.cjs @@ -4,16 +4,14 @@ */ module.exports = { - extends: [ - require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), - "prettier", - ], + extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"], parserOptions: { project: ["./tsconfig.json"], }, rules: { "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-empty-interface": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", // This package is build with noUnusedLocals disabled for a specific use case (see note in tsconfig.json), // but should reject other cases using this rule: @@ -26,28 +24,8 @@ module.exports = { }, ], - // TODO: Remove this override once dependency on eslint-config-fluid has been updated to 5.2.0+ - "import/order": "off", - - // TODO: Remove these overrides once this config has been updated to extend at least the "recommended" base config. - "@typescript-eslint/no-explicit-any": [ - "error", - { - ignoreRestArgs: true, - }, - ], - "@typescript-eslint/explicit-function-return-type": [ - "error", - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, - allowDirectConstAssertionInArrowFunctions: true, - allowConciseArrowFunctionExpressionsStartingWithVoid: false, - }, - ], + // #region TODO: Remove these overrides once this config has been updated to extend the "strict" base config. - // TODO: Remove these overrides once this config has been updated to extend the "strict" base config. "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/consistent-type-exports": [ "error", @@ -58,6 +36,59 @@ module.exports = { { fixStyle: "inline-type-imports" }, ], "@typescript-eslint/no-import-type-side-effects": "error", + + // #endregion + + // #region TODO:AB#6983: Remove these overrides and fix violations + + "@typescript-eslint/explicit-module-boundary-types": "off", + + // Causes eslint to stack-overflow in this package. Will need investigation. + "@typescript-eslint/no-unsafe-argument": "off", + + // Causes eslint to stack-overflow in this package. Will need investigation. + "@typescript-eslint/no-unsafe-assignment": "off", + + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + + "import/order": "off", + + "jsdoc/multiline-blocks": "off", + + // Set to a warning to encourage adding docs :) + "jsdoc/require-description": "warn", + + "unicorn/consistent-destructuring": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/explicit-length-check": "off", + "unicorn/no-array-callback-reference": "off", + "unicorn/no-array-for-each": "off", + "unicorn/prefer-array-index-of": "off", + "unicorn/no-array-method-this-argument": "off", + "unicorn/no-array-reduce": "off", + "unicorn/no-await-expression-member": "off", + "unicorn/no-lonely-if": "off", + "unicorn/no-negated-condition": "off", + "unicorn/no-new-array": "off", + "unicorn/no-null": "off", + "unicorn/no-object-as-default-parameter": "off", + "unicorn/no-useless-fallback-in-spread": "off", + "unicorn/no-zero-fractions": "off", + "unicorn/prefer-array-some": "off", + "unicorn/prefer-code-point": "off", + "unicorn/prefer-default-parameters": "off", + "unicorn/prefer-dom-node-remove": "off", + "unicorn/prefer-export-from": "off", + "unicorn/prefer-math-trunc": "off", + "unicorn/prefer-native-coercion-functions": "off", + "unicorn/prefer-set-has": "off", + "unicorn/prefer-spread": "off", + "unicorn/prefer-string-slice": "off", + "unicorn/switch-case-braces": "off", + "unicorn/text-encoding-identifier-case": "off", + + // #endregion }, overrides: [ { diff --git a/packages/dds/tree/CHANGELOG.md b/packages/dds/tree/CHANGELOG.md index 43e15e7ef53b..dbad25e0a64d 100644 --- a/packages/dds/tree/CHANGELOG.md +++ b/packages/dds/tree/CHANGELOG.md @@ -1,5 +1,376 @@ # @fluidframework/tree +## 2.5.0 + +### Minor Changes + +- ✨ New! Alpha APIs for tree data import and export ([#22566](https://github.com/microsoft/FluidFramework/pull/22566)) [18a23e8816](https://github.com/microsoft/FluidFramework/commit/18a23e8816467f2ed0c9d6d8637b70d99aa48b7a) + + A collection of new `@alpha` APIs for importing and exporting tree content and schema from SharedTrees has been added to `TreeAlpha`. + These include import and export APIs for `VerboseTree`, `ConciseTree` and compressed tree formats. + + `TreeAlpha.create` is also added to allow constructing trees with a more general API instead of having to use the schema constructor directly (since that doesn't handle polymorphic roots, or non-schema aware code). + + The function `independentInitializedView` has been added to provide a way to combine data from the existing `extractPersistedSchema` and new `TreeAlpha.exportCompressed` back into a `TreeView` in a way which can support safely importing data which could have been exported with a different schema. + This allows replicating the schema evolution process for Fluid documents stored in a service, but entirely locally without involving any collaboration services. + `independentView` has also been added, which is similar but handles the case of creating a new view without an existing schema or tree. + + Together these APIs address several use-cases: + + 1. Using SharedTree as an in-memory non-collaborative datastore. + 2. Importing and exporting data from a SharedTree to and from other services or storage locations (such as locally saved files). + 3. Testing various scenarios without relying on a service. + 4. Using SharedTree libraries for just the schema system and encode/decode support. + +- Compilation no longer fails when building with TypeScript's libCheck option ([#22923](https://github.com/microsoft/FluidFramework/pull/22923)) [a1b4cdd45e](https://github.com/microsoft/FluidFramework/commit/a1b4cdd45ee9812e2598ab8d2854333d26a06eb4) + + When compiling code using Fluid Framework with TypeScript's `libCheck` (meaning without [skipLibCheck](https://www.typescriptlang.org/tsconfig/#skipLibCheck)), two compile errors can be encountered: + + ``` + > tsc + + node_modules/@fluidframework/merge-tree/lib/client.d.ts:124:18 - error TS2368: Type parameter name cannot be 'undefined'. + + 124 walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; + ~~~~~~~~~ + + node_modules/@fluidframework/tree/lib/util/utils.d.ts:5:29 - error TS7016: Could not find a declaration file for module '@ungap/structured-clone'. 'node_modules/@ungap/structured-clone/esm/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/ungap__structured-clone` if it exists or add a new declaration (.d.ts) file containing `declare module '@ungap/structured-clone';` + + 5 import structuredClone from "@ungap/structured-clone"; + ~~~~~~~~~~~~~~~~~~~~~~~~~ + ``` + + The first error impacts projects using TypeScript 5.5 or greater and either of the `fluid-framework` or `@fluidframework/merge-tree` packages. + The second error impacts projects using the `noImplicitAny` tsconfig setting and the `fluid-framework` or `@fluidframework/tree` packages. + + Both errors have been fixed. + + This should allow `libCheck` to be reenabled in any impacted projects. + +- A `.schema` member has been added to the alpha enum schema APIs ([#22874](https://github.com/microsoft/FluidFramework/pull/22874)) [645b9ed695](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + + The return value from `@alpha` APIs `enumFromStrings` and `adaptEnum` now has a property named `schema` which can be used to include it in a parent schema. + This replaces the use of `typedObjectValues` which has been removed. + + Use of these APIs now look like: + + ```typescript + const schemaFactory = new SchemaFactory("com.myApp"); + const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); + type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; + class Parent extends schemaFactory.object("Parent", { mode: Mode.schema }) {} + ``` + + Previously, the last two lines would have been: + + ```typescript + type Mode = NodeFromSchema<(typeof Mode)[keyof typeof Mode]>; // This no longer works + class Parent extends schemaFactory.object("Parent", { mode: typedObjectValues(Mode) }) {} // This no longer works + ``` + +- TreeNodeSchemaClass now specifies its TNode as TreeNode ([#22938](https://github.com/microsoft/FluidFramework/pull/22938)) [b669a6efdb](https://github.com/microsoft/FluidFramework/commit/b669a6efdba685c71897cade4f907304f1a73910) + + `TreeNodeSchemaClass`'s `TNode` parameter was formerly `unknown` and has been improved to be the more specific `TreeNode | TreeLeafValue`. + This change further narrows this to `TreeNode`. + + `TreeNodeSchema`, which is more commonly used, still permits `TNode` of `TreeNode | TreeLeafValue`, so this change should have little impact on most code, but in some edge cases it can result in slightly more specific typing. + +- Array and Map nodes can now be explicitly constructed with undefined or no argument ([#22946](https://github.com/microsoft/FluidFramework/pull/22946)) [176335ce88](https://github.com/microsoft/FluidFramework/commit/176335ce88d005159819c559b445a1655ec429d5) + + The input parameter to the constructor and `create` methods of Array and Map nodes is now optional. When the optional parameter is omitted, an empty map or array will be created. + + #### Examples + + ```typescript + class Schema extends schemaFactory.array("x", schemaFactory.number) {} + + // Existing support + const _fromIterable: Schema = new Schema([]); + + // New + const _fromUndefined: Schema = new Schema(undefined); + const _fromNothing: Schema = new Schema(); + ``` + + ```typescript + class Schema extends schemaFactory.map("x", schemaFactory.number) {} + + // Existing support + const _fromIterable: Schema = new Schema([]); + const _fromObject: Schema = new Schema({}); + + // New + const _fromUndefined: Schema = new Schema(undefined); + const _fromNothing: Schema = new Schema(); + ``` + + ```typescript + const Schema = schemaFactory.array(schemaFactory.number); + type Schema = NodeFromSchema; + + // Existing support + const _fromIterable: Schema = Schema.create([]); + + // New + const _fromUndefined: Schema = Schema.create(undefined); + const _fromNothing: Schema = Schema.create(); + ``` + + ```typescript + const Schema = schemaFactory.map(schemaFactory.number); + type Schema = NodeFromSchema; + // Existing support + const _fromIterable: Schema = Schema.create([]); + const _fromObject: Schema = Schema.create({}); + + // New + const _fromUndefined: Schema = Schema.create(undefined); + const _fromNothing: Schema = Schema.create(); + ``` + +- Typing has been improved when an exact TypeScript type for a schema is not provided ([#22763](https://github.com/microsoft/FluidFramework/pull/22763)) [05197d6d3f](https://github.com/microsoft/FluidFramework/commit/05197d6d3f0189ecd61fd74ec55f6836e6797249) + + The Tree APIs are designed to be used in a strongly typed way, with the full TypeScript type for the schema always being provided. + Due to limitations of the TypeScript language, there was no practical way to prevent less descriptive types, like `TreeNodeSchema` or `ImplicitFieldSchema`, from being used where the type of a specific schema was intended. + Code which does this will encounter several issues with tree APIs, and this change fixes some of those issues. + This change mainly fixes that `NodeFromSchema` used to return `unknown` and now returns `TreeNode | TreeLeafValue`. + + This change by itself seems mostly harmless, as it just improves the precision of the typing in this one edge case. + Unfortunately, there are other typing bugs which complicate the situation, causing APIs for inserting data into the tree to also behave poorly when given non-specific types like `TreeNodeSchema`. + These APIs include cases like `TreeView.initialize`. + + This incorrectly allowed some usage like taking a type-erased schema and initial tree pair, creating a view of type `TreeView`, then initializing it. + With the typing being partly fixed, some unsafe inputs are still allowed when trying to initialize such a view, but some are now prevented. + + This use-case of modifying trees in code not that is not strongly typed by the exact schema was not intended to be supported. + Despite this, it did mostly work in some cases, and has some real use-cases (like tests looping over test data consisting of pairs of schema and initial trees). + To help mitigate the impact of this change, some experimental `@alpha` APIs have been introduced to help address these previously unsupported but somewhat working use-cases. + + Before this change: + + ```typescript + import { TinyliciousClient } from "@fluidframework/tinylicious-client"; + import { + SchemaFactory, + SharedTree, + TreeViewConfiguration, + type TreeNodeSchema, + } from "fluid-framework"; + + // Create a ITree instance + const tinyliciousClient = new TinyliciousClient(); + const { container } = await tinyliciousClient.createContainer({ initialObjects: {} }, "2"); + const tree = await container.create(SharedTree); + + const schemaFactory = new SchemaFactory("demo"); + + // Bad: This loses the schema aware type information. `: TreeNodeSchema` should be omitted to preserve strong typing. + const schema: TreeNodeSchema = schemaFactory.array(schemaFactory.number); + const config = new TreeViewConfiguration({ schema }); + + // This view is typed as `TreeView`, which does not work well since it's missing the actual schema type information. + const view = tree.viewWith(config); + // Root is typed as `unknown` allowing invalid assignment operations. + view.root = "invalid"; + view.root = {}; + // Since all assignments are allowed, valid ones still work: + view.root = []; + ``` + + After this change: + + ```typescript + // Root is now typed as `TreeNode | TreeLeafValue`, still allowing some invalid assignment operations. + // In the future this should be prevented as well, since the type of the setter in this case should be `never`. + view.root = "invalid"; + // This no longer compiles: + view.root = {}; + // This also no longer compiles despite being valid at runtime: + view.root = []; + ``` + + For code that wants to continue using an unsafe API, which can result in runtime errors if the data does not follow the schema, a new alternative has been added to address this use-case. A special type `UnsafeUnknownSchema` can now be used to opt into allowing all valid trees to be provided. + Note that this leaves ensuring the data is in schema up to the user. + For now these adjusted APIs can be accessed by casting the view to `TreeViewAlpha`. + If stabilized, this option will be added to `TreeView` directly. + + ```typescript + const viewAlpha = view as TreeViewAlpha; + viewAlpha.initialize([]); + viewAlpha.root = []; + ``` + + Additionally, this seems to have negatively impacted co-recursive schema which declare a co-recursive array as the first schema in the co-recursive cycle. + Like the TypeScript language our schema system is built on, we don't guarantee exactly which recursive type will compile, but will do our best to ensure useful recursive schema can be created easily. + In this case a slight change may be required to some recursive schema to get them to compile again: + + For example this schema used to compile: + + ```typescript + class A extends sf.arrayRecursive("A", [() => B]) {} + { + type _check = ValidateRecursiveSchema; + } + // Used to work, but breaks in this update. + class B extends sf.object("B", { x: A }) {} + ``` + + But now you must use the recursive functions like `objectRecursive` for types which are co-recursive with an array in some cases. + In our example, it can be fixed as follows: + + ```typescript + class A extends sf.arrayRecursive("A", [() => B]) {} + { + type _check = ValidateRecursiveSchema; + } + // Fixed corecursive type, using "Recursive" method variant to declare schema. + class B extends sf.objectRecursive("B", { x: A }) {} + { + type _check = ValidateRecursiveSchema; + } + ``` + + Note: while the following pattern may still compile, we recommend using the previous pattern instead since the one below may break in the future. + + ```typescript + class B extends sf.objectRecursive("B", { x: [() => A] }) {} + { + type _check = ValidateRecursiveSchema; + } + // Works, for now, but not recommended. + class A extends sf.array("A", B) {} + ``` + +- The strictness of input tree types when inexact schemas are provided has been improved ([#22874](https://github.com/microsoft/FluidFramework/pull/22874)) [645b9ed695](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + + Consider the following code where the type of the schema is not exactly specified: + + ```typescript + const schemaFactory = new SchemaFactory("com.myApp"); + class A extends schemaFactory.object("A", {}) {} + class B extends schemaFactory.array("B", schemaFactory.number) {} + + // Gives imprecise type (typeof A | typeof B)[]. The desired precise type here is [typeof A, typeof B]. + const schema = [A, B]; + + const config = new TreeViewConfiguration({ schema }); + const view = sharedTree.viewWith(config); + + // Does not compile since setter for root is typed `never` due to imprecise schema. + view.root = []; + ``` + + The assignment of `view.root` is disallowed since a schema with type `(typeof A | typeof B)[]` could be any of: + + ```typescript + const schema: (typeof A | typeof B)[] = [A]; + ``` + + ```typescript + const schema: (typeof A | typeof B)[] = [B]; + ``` + + ```typescript + const schema: (typeof A | typeof B)[] = [A, B]; + ``` + + The attempted assignment is not compatible with all of these (specifically it is incompatible with the first one) so performing this assignment could make the tree out of schema and is thus disallowed. + + To avoid this ambiguity and capture the precise type of `[typeof A, typeof B]`, use one of the following patterns: + + ```typescript + const schema = [A, B] as const; + const config = new TreeViewConfiguration({ schema }); + ``` + + ```typescript + const config = new TreeViewConfiguration({ schema: [A, B] }); + ``` + + To help update existing code which accidentally depended on this bug, an `@alpha` API `unsafeArrayToTuple` has been added. + Many usages of this API will produce incorrectly typed outputs. + However, when given `AllowedTypes` arrays which should not contain any unions, but that were accidentally flattened to a single union, it can fix them: + + ```typescript + // Gives imprecise type (typeof A | typeof B)[] + const schemaBad = [A, B]; + // Fixes the type to be [typeof A, typeof B] + const schema = unsafeArrayToTuple(schemaBad); + + const config = new TreeViewConfiguration({ schema }); + ``` + +- SharedTree branching API has been improved ([#22970](https://github.com/microsoft/FluidFramework/pull/22970)) [80ed0284f0](https://github.com/microsoft/FluidFramework/commit/80ed0284f01107d2ba8bcf2f3ebaf6175367603a) + + The alpha SharedTree branching API has been updated to be more accessible and intuitive. + The branching functions (`branch`, `merge`, `rebaseOnto`, etc.) are now directly available on the view object rather than a separate object. + In particular, `TreeViewAlpha` is now a `TreeBranch`, which exposes the methods to coordinate branches. + + The existing `TreeBranch` type has been renamed to `BranchableTree` and is now **deprecated**. + + See the `TreeBranch` interface for more details. + + The new API is used e.g. as follows: + + ```typescript + const sf = new SchemaFactory("example"); + class StringArray extends sf.array("StringArray", sf.string) {} + + function example(view: TreeViewAlpha): void { + // Create a branch + const branch = view.fork(); + // Modify the branch rather than the main view + branch.root.insertAtEnd("new string"); + // `view` does not yet contain "new string" + // ... + // Later, merge the branch into the main view + view.merge(branch); + // `view` now contains "new string" + } + ``` + + Here is the equivalent behavior with the previous API, for reference: + + ```typescript + const sf = new SchemaFactory("example"); + class StringArray extends sf.array("StringArray", sf.string) {} + + function example(view: TreeViewAlpha): void { + // Get the branch for the view + const branch = getBranch(view); + const fork = branch.branch(); + // Modify the branch rather than the main view + fork.root.insertAtEnd("new string"); + // `view` does not yet contain "new string" + // ... + // Later, merge the branch into the main view + branch.merge(fork); + // `view` now contains "new string" + } + ``` + + Additionally, there is a new API to acquire the branch from a node: + + ```typescript + // All nodes that have been inserted into the tree belong to a branch - this retrieves that branch + const branch = TreeAlpha.branch(node); + ``` + + To convert the branch object to a view with a known schema, use: + + ```typescript + if (branch.hasRootSchema(MySchema)) { + const view = branch; // `branch` is now typed as a `TreeViewAlpha` + } + ``` + + Use the following function to expose the alpha APIs on a `TreeView` that is not typed as a `TreeViewAlpha`: + + ```typescript + const viewAlpha = asTreeViewAlpha(view); + ``` + ## 2.4.0 ### Minor Changes diff --git a/packages/dds/tree/api-report/tree.alpha.api.md b/packages/dds/tree/api-report/tree.alpha.api.md index 3eee70d530e4..0b2270c0143a 100644 --- a/packages/dds/tree/api-report/tree.alpha.api.md +++ b/packages/dds/tree/api-report/tree.alpha.api.md @@ -33,6 +33,17 @@ type ApplyKindInput(view: TreeView): TreeViewAlpha; + +// @alpha @sealed +export interface BranchableTree extends ViewableTree { + branch(): TreeBranchFork; + merge(branch: TreeBranchFork): void; + merge(branch: TreeBranchFork, disposeMerged: boolean): void; + rebase(branch: TreeBranchFork): void; +} + // @public export enum CommitKind { Default = 0, @@ -49,10 +60,21 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: JsonCompatible, options: ICodecOptions, canInitialize: boolean): SchemaCompatibilityStatus; +// @alpha +export type ConciseTree = Exclude | THandle | ConciseTree[] | { + [key: string]: ConciseTree; +}; + // @public @sealed interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @alpha +export interface EncodeOptions { + readonly useStoredKeys?: boolean; + valueConverter(data: IFluidHandle): TCustom; +} + // @alpha export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TreeNode & { readonly value: TValue; @@ -136,6 +158,14 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @alpha +export enum FluidClientVersion { + v2_0 = "v2_0", + v2_1 = "v2_1", + v2_2 = "v2_2", + v2_3 = "v2_3" +} + // @alpha export interface ForestOptions { readonly forest?: ForestType; @@ -148,11 +178,11 @@ export enum ForestType { Reference = 0 } -// @alpha -export function getBranch(tree: ITree): TreeBranch; +// @alpha @deprecated +export function getBranch(tree: ITree): BranchableTree; -// @alpha -export function getBranch(view: TreeViewAlpha): TreeBranch; +// @alpha @deprecated +export function getBranch(view: TreeViewAlpha): BranchableTree; // @alpha export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema; @@ -168,6 +198,14 @@ export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; // @public export type ImplicitFieldSchema = FieldSchema | ImplicitAllowedTypes; +// @alpha +export function independentInitializedView(config: TreeViewConfiguration, options: ForestOptions & ICodecOptions, content: ViewContent): TreeViewAlpha; + +// @alpha +export function independentView(config: TreeViewConfiguration, options: ForestOptions & { + idCompressor?: IIdCompressor_2 | undefined; +}): TreeViewAlpha; + // @public type _InlineTrick = 0; @@ -243,7 +281,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -306,11 +343,11 @@ export interface JsonArrayNodeSchema extends JsonNodeSchemaBase = string | number | boolean | null | JsonCompatible[] | JsonCompatibleObject | TExtra; // @alpha -export type JsonCompatibleObject = { - [P in string]?: JsonCompatible; +export type JsonCompatibleObject = { + [P in string]?: JsonCompatible; }; // @alpha @sealed @@ -380,6 +417,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -444,11 +482,17 @@ type ObjectFromSchemaRecordUnsafe void; +// @alpha +export interface ParseOptions { + readonly useStoredKeys?: boolean; + valueConverter(data: VerboseTree): TreeLeafValue | VerboseTreeNode; +} + // @alpha export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; // @alpha -export type ReadableField = TSchema extends ImplicitFieldSchema ? TreeFieldFromImplicitField : TreeLeafValue | TreeNode; +export type ReadableField = TreeFieldFromImplicitField>; // @public @sealed export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { @@ -534,7 +578,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -550,6 +595,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -559,7 +606,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -602,6 +648,26 @@ export type TransactionConstraint = NodeInDocumentConstraint; // @public export const Tree: TreeApi; +// @alpha @sealed +export const TreeAlpha: { + branch(node: TreeNode): TreeBranch | undefined; + create(schema: UnsafeUnknownSchema extends TSchema ? ImplicitFieldSchema : TSchema & ImplicitFieldSchema, data: InsertableField): Unhydrated : TreeNode | TreeLeafValue | undefined>; + importConcise(schema: UnsafeUnknownSchema extends TSchema ? ImplicitFieldSchema : TSchema & ImplicitFieldSchema, data: ConciseTree | undefined): Unhydrated : TreeNode | TreeLeafValue | undefined>; + importVerbose(schema: TSchema, data: VerboseTree | undefined, options?: Partial>): Unhydrated>; + importVerbose(schema: TSchema, data: VerboseTree | undefined, options: ParseOptions): Unhydrated>; + exportConcise(node: TreeNode | TreeLeafValue, options?: Partial>): ConciseTree; + exportConcise(node: TreeNode | TreeLeafValue, options: EncodeOptions): ConciseTree; + exportVerbose(node: TreeNode | TreeLeafValue, options?: Partial>): VerboseTree; + exportVerbose(node: TreeNode | TreeLeafValue, options: EncodeOptions): VerboseTree; + exportCompressed(tree: TreeNode | TreeLeafValue, options: { + oldestCompatibleClient: FluidClientVersion; + idCompressor?: IIdCompressor; + }): JsonCompatible; + importCompressed(schema: TSchema, compressedData: JsonCompatible, options: { + idCompressor?: IIdCompressor; + } & ICodecOptions): Unhydrated>; +}; + // @public @sealed interface TreeApi extends TreeNodeApi { contains(node: TreeNode, other: TreeNode): boolean; @@ -609,16 +675,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -639,8 +696,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @beta @sealed @@ -650,16 +712,25 @@ export const TreeBeta: { }; // @alpha @sealed -export interface TreeBranch extends ViewableTree { - branch(): TreeBranchFork; - merge(branch: TreeBranchFork): void; - merge(branch: TreeBranchFork, disposeMerged: boolean): void; - rebase(branch: TreeBranchFork): void; +export interface TreeBranch extends IDisposable { + dispose(error?: Error): void; + readonly events: Listenable; + fork(): TreeBranch; + hasRootSchema(schema: TSchema): this is TreeViewAlpha; + merge(branch: TreeBranch, disposeMerged?: boolean): void; + rebaseOnto(branch: TreeBranch): void; } // @alpha @sealed -export interface TreeBranchFork extends TreeBranch, IDisposable { - rebaseOnto(branch: TreeBranch): void; +export interface TreeBranchEvents { + changed(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + schemaChanged(): void; +} + +// @alpha @sealed +export interface TreeBranchFork extends BranchableTree, IDisposable { + rebaseOnto(branch: BranchableTree): void; } // @public @sealed @@ -800,8 +871,12 @@ export interface TreeView extends ID upgradeSchema(): void; } -// @alpha -export interface TreeViewAlpha extends Omit>, "root" | "initialize"> { +// @alpha @sealed +export interface TreeViewAlpha extends Omit>, "root" | "initialize">, TreeBranch { + // (undocumented) + readonly events: Listenable; + // (undocumented) + fork(): ReturnType & TreeViewAlpha; // (undocumented) initialize(content: InsertableField): void; // (undocumented) @@ -864,11 +939,29 @@ export type ValidateRecursiveSchema> = true; +// @alpha +export type VerboseTree = VerboseTreeNode | Exclude | THandle; + +// @alpha +export interface VerboseTreeNode { + fields: VerboseTree[] | { + [key: string]: VerboseTree; + }; + type: string; +} + // @public @sealed export interface ViewableTree { viewWith(config: TreeViewConfiguration): TreeView; } +// @alpha +export interface ViewContent { + readonly idCompressor: IIdCompressor_2; + readonly schema: JsonCompatible; + readonly tree: JsonCompatible; +} + // @public @sealed export interface WithType { // @deprecated diff --git a/packages/dds/tree/api-report/tree.beta.api.md b/packages/dds/tree/api-report/tree.beta.api.md index 43f4a5e10d9a..fbba7607a7b1 100644 --- a/packages/dds/tree/api-report/tree.beta.api.md +++ b/packages/dds/tree/api-report/tree.beta.api.md @@ -167,7 +167,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -226,6 +225,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -366,7 +366,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -382,6 +383,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -391,7 +394,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -412,16 +414,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -442,8 +435,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @beta @sealed diff --git a/packages/dds/tree/api-report/tree.legacy.alpha.api.md b/packages/dds/tree/api-report/tree.legacy.alpha.api.md index 1f96febb4a68..22601b5c0920 100644 --- a/packages/dds/tree/api-report/tree.legacy.alpha.api.md +++ b/packages/dds/tree/api-report/tree.legacy.alpha.api.md @@ -167,7 +167,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -226,6 +225,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -361,7 +361,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -377,6 +378,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -386,7 +389,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -410,16 +412,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -440,8 +433,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/dds/tree/api-report/tree.legacy.public.api.md b/packages/dds/tree/api-report/tree.legacy.public.api.md index 4b830bf0c221..57c3885c44e6 100644 --- a/packages/dds/tree/api-report/tree.legacy.public.api.md +++ b/packages/dds/tree/api-report/tree.legacy.public.api.md @@ -167,7 +167,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -226,6 +225,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -361,7 +361,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -377,6 +378,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -386,7 +389,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -407,16 +409,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -437,8 +430,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/dds/tree/api-report/tree.public.api.md b/packages/dds/tree/api-report/tree.public.api.md index 4b830bf0c221..57c3885c44e6 100644 --- a/packages/dds/tree/api-report/tree.public.api.md +++ b/packages/dds/tree/api-report/tree.public.api.md @@ -167,7 +167,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -226,6 +225,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -361,7 +361,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -377,6 +378,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -386,7 +389,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -407,16 +409,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -437,8 +430,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/dds/tree/docs/.attachments/object-merge-semantics.drawio b/packages/dds/tree/docs/.attachments/object-merge-semantics.drawio new file mode 100644 index 000000000000..f8e7105f04c0 --- /dev/null +++ b/packages/dds/tree/docs/.attachments/object-merge-semantics.drawio @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/dds/tree/docs/user-facing/array-merge-semantics.md b/packages/dds/tree/docs/user-facing/array-merge-semantics.md new file mode 100644 index 000000000000..5e86fc3e6c82 --- /dev/null +++ b/packages/dds/tree/docs/user-facing/array-merge-semantics.md @@ -0,0 +1,344 @@ +# Merge Semantics of Edits on Array Nodes + +This document describes the semantics of edits that can be performed on array nodes. + +Target audience: `SharedTree` users and maintainers. + +> While this document is self-contained, we recommend reading about [SharedTree's approach to merge semantics](merge-semantics) first. + +Each edit's merge semantics are defined in terms of the edit's preconditions and postconditions. +A precondition defines a requirement that must be met for the edit to be valid. +A postcondition defines a guarantee that is made about the effect of the edit. +(Invalid edits are ignored along with all other edits in the same transaction, and postconditions do not hold). + +## Key Challenges and Solutions + +Before delving into the set editing operations supported by ShardTree arrays, +it's helpful to understand the key challenges that come up when users are allowed to concurrently edit the same array, +and how ShardTree arrays address these challenges. + +### Specifying the Location of Inserted Items + +#### The Problem + +Array operations that insert (or move in) array items take an integer that describes where in the array the new item(s) should be added. +For example, we can call `insertAt(1, "o")` to insert the value `"o"` in the array `["c", "a", "t"]` thus changing it to `["c", "o", "a", "t"]`. + +In a collaborative editing environment, +it's possible for the state of the array to change between the time the edit is first created and the time it is applied. +Consider what would happen if the argument that describes the destination of the insert were to be treated as a fixed integer index: + +Example 1: +* Starting state: `["c", "a", "t"]` +* Alice's edit: `insertAt(0, "r", "e", "d", " ")` with the expectation of getting `["r", "e", "d", " ", "c", "a", "t"]`. +* Bob's edit: `insertAt(1, "o")` with the expectation of getting `["c", "o", "a", "t"]`. + +If Alice and Bob's edits are concurrent, and Alice's edit is sequenced first, +then inserting `"o"` at index 1 would yield `["r", "o", "e", "d", " ", "c", "a", "t"]`. +This would not truly match the intention of Bob, +who would likely have wanted to get `["r", "e", "d", " ", "c", "o", "a", "t"]` instead. + +Example 2: +* Starting state: `["r", "e", "d", " ", "c", "a", "t"]` +* Alice's edit: `removeRange(0, 4)` with the expectation of getting `["c", "a", "t"]`. +* Bob's edit: `insertAt(5, "o")` with the expectation of getting `["r", "e", "d", " ", "c", "o", "a", "t"]`. + +If Alice and Bob's edits are concurrent, and Alice's edit is sequenced first, +then inserting `"o"` at index 5 would either crash or yield `["c", "a", "t", "o"]`. +This would not truly match the intention of Bob, +who would likely have wanted to get `["c", "o", "a", "t"]` instead. + +#### The Solution: Inserting in Gaps + +Instead of treating the destination parameter of insert and move operations as a fixed insertion index, +SharedTree's array implementation interprets that parameter as referring to a gap in the array. + +For example, in an array with two items `[A, B]` there are three gaps: +one before A, one between A and B, and one after C. +If we represented gaps with the `_` character, then would describe the array `[A, B]` as `[ _ A _ B _ ]`. +(More generally, an array with `K` items has `K+1` gaps.) + +This means that calling `insertAt(1, "o")` on an array with initial state `["c", "a", "t"]` +singles out the following gap as the location to perform the insert: `["c" _ "a" "t"]`. +This conversion from index 1 to the corresponding gap is done at the time the edit is first created. +SharedTree's array implementation then keeps track of that gap's position when reconciling this insertion against concurrent edits. +Reusing the scenario from example 1 in the previous section, +the gap's position after reconciling with the concurrent edit from Alice (`insertAt(0, "r", "e", "d", " ")`) +is as follows: `["r" "e" "d" " " "c" _ "a" "t"]`, +thus yielding the adequate result after inserting "o" `["r", "e", "d", " ", "c", "o", "a", "t"]`. + +#### Tie-Breaking + +Note that multiple edits may concurrently attempt to insert or move in items into the same gap. +When that's the case, +the items end up ordered such that the items inserted by the edit that is sequenced earlier will appear after the items inserted by the edits that is sequenced later. + +Example: +* Starting state: `[]` +* Edit 1: (concurrently to edits 2 and 3) insert items A and B (this changes the state from `[]` to `[A, B]`) +* Edit 2: (concurrently to edits 1 and 3) insert items R and S (this changes the state from `[]` to `[R, S]`) +* Edit 3: (concurrently to edits 1 and 2) insert items X and Y (this changes the state from `[]` to `[X, Y]`) + +If the edits are sequenced in increasing order (i.e., edit 1, edit 2, edit 3), +then the resulting state will be `[X, Y, R, S, A, B]`. + +#### Noteworthy Implications + +Creating an edit that inserts items before or after an existing item in an array will not necessarily insert next to that item. +For example, inserting item X at the start of array `[Y, Z]` does not guarantee that X and Y will both appear together (in that order) +if other users make concurrent edits to the array. + +Example 1: concurrent insert +* Starting state: `[Y, Z]` +* Alice's edit: insert A at the start of the array. +* Bob's edit: insert X at the start of the array. +If Alice's edit is sequenced before Bob's, +then the result will be `[X, A, Y, Z]`. + +Example 2: concurrent remove +* Starting state: `[Y, Z]` +* Alice's edit: remove Y. +* Bob's edit: insert X at the start of the array. +No matter the order in which these edits are sequenced, +the result will be `[X, Z]`. + +Example 3: concurrent move +* Starting state: `[Y, Z]` +* Alice's edit: move Y after Z. +* Bob's edit: insert X at the start of the array. +No matter the order in which these edits are sequenced, +the result will be `[X, Z, Y]` +as opposed to `[Z, X, Y]` which one might have expected if they thought of the insertion as happening "before Y". + +The takeaway from these examples is that while it's tempting to think of an insertion as occurring before or after an existing item, +that doesn't quite match the merge semantics we have implemented. +If you find yourself wishing for different merge semantics please reach out to the Fluid team. + +### Specifying the Set of (Re)Moved Items + +Each move or remove operations affects a specific set of array items. +When a single item is targeted, +the item can be specified using its index in the current state. +For example, removing the `"o"` from `["c", "o", "a", "t"]` with `removeAt(1)`. + +When targeting multiple contiguous items, +it is possible specify them as a range. +For example, `"o"` and `"a"` from `["c", "o", "a", "t"]` with `removeRange(1, 3)`. + +From the point of view of merge semantics, +calling `removeRange(1, 3)` is equivalent to individually removing each of the two middle letters in one transaction. +Using the range-based API is typically more convenient. +It is also optimized to have less overhead than making separate calls for each individual item. + +The same is true for `moveRange` compared to `moveAt`, +with the additional property that `moveRange` preserves the order that the items are in at the time the edit is created. + +Example: +* Starting state `[A, B, C]` +* Edit 1: `moveRange(0, 1, 2)` (move B in the gap before A, yielding `[B, A, C]`) +* Edit 2: `moveRange(3, 0, 2)` (move A and B in the gap after C, yielding `[C, A, B]`) + +If edit 1 is sequenced first, then, when edit 2 is finally applied, +the state will change from `[B, A, C]` to `[C, A, B]`. +If edit 2 is sequenced first, then, when edit 1 is finally applied, +the state will change from `[C, A, B]`to `[B, C, A]`. + +#### The Problem + +In a collaborative editing environment, +it's possible for the state of the array to change between the time the edit is first created and the time it is applied. +Consider what would happen if the arguments that describe the affected items were to be treated as fixed integer indexes: +* Starting state: `["c", "o", "a", "t"]` +* Alice's edit: `insertAt(0, "r", "e", "d", " ")` with the expectation of getting `["r", "e", "d", " ", "c", "o", "a", "t"]`. +* Bob's edit: `removeAt(1)` with the expectation of getting `["c", "a", "t"]`. + +If Alice and Bob's edits are concurrent, and Alice's edit is sequenced first, +then removing the item at index 1 would yield `["r", "d", " ", "c", "o", "a", "t"]`. +This would not truly match the intention of Bob, +who would likely have wanted to get `["r", "e", "d", " ", "c", "a", "t"]` instead. + +#### The Solution: Targeting Items + +Instead of treating the parameters of move and remove as a fixed index to detach items at, +SharedTree's array implementation interprets these parameter as referring to the items themselves. +In other words, `removeAt(1)` doesn't mean "remove whichever item happens to be at index 1 when the edit is applied". +Instead, it means "remove the specific item that is currently at index 1, no matter what index that item it at when the edit is applied". + +#### Noteworthy Implications + +Inserting items within a range that is concurrently being moved has no impact on the set of moved items. +For example, if one user moves items A and B to the end of array `[A, B, C]` by using a `sourceStart` of 0 and a `sourceEnd` of 2, +and some other user concurrently inserts some item X between A and B (thus changing the state to `[A, X, B, C]`), +then the move will still affect items A and B (and only these), thus yielding `[X, C, A, B]`. +This is true no matter how the concurrent edits are ordered. + +If multiple users concurrently attempt to move the same item, the conflict is resolved in a last-write-wins fashion. +For example, if one user moves item B leftward to the start of array `[A, B, C]`, +and some other user concurrently moves item B rightward to the end of the array, +then the item will be affected by each successive move in sequencing order: +* If the leftward move is sequenced before the rightward move + then A will first be moved to the start (thus yielding `[B, A, C]`) + then moved to the end (thus yielding `[A, C, B]`). +* If the rightward move is sequenced before the leftward move + then A will first be moved to the end (thus yielding `[A, C, B]`) + then moved to the start (thus yielding `[B, A, C]`). + +A removed item may be restored as a result of a concurrent move operation +and a moved item may be removed as a result of a concurrent remove operation. +For example, if one user moves item A to the end of array `[A, B, C]`, +and some other user concurrently removes item A, +then the item will be affected by each successive operation in sequencing order: +* If the move is sequenced before the remove + then A will first be moved to the end of the array (thus yielding `[C, B, A]`) + then removed (thus yielding `[C, B]`). +* If the remove is sequenced before the move + then A will first be removed (thus yielding `[C, B]`) + then moved (thus yielding `[C, B, A]`). + +## Core Editing Operations + +### `insertAt(gapIndex: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void` + +Inserts new items at the location described by `gapIndex`. + +Preconditions: +* There is no concurrent schema change edit that is sequenced before this one. +* The inserted values must have status `TreeStatus.New` or be primitives. + (This precondition will be removed soon) + +Postconditions: +* The values are inserted in the targeted [gap](#location-of-inserted-items). + +### `moveRangeToIndex(destinationGap: number, sourceStart: number, sourceEnd: number, source: TMoveFrom): void` + +Moves the specified items from the given source array to the desired location within the array. + +Preconditions: +* There is no concurrent schema change edit that is sequenced before this one. + +Postconditions: +* The [specified items](#specifying-the-set-of-removed-items) are moved to the targeted [gap](#location-of-inserted-items). + +If multiple clients concurrently move an item, +then that item will be moved to the destination indicated by the move of the client whose edit is sequenced last. + +### `removeRange(start?: number, end?: number): void` + +Removes the items between the specified indices. + +Preconditions: +* There is no concurrent schema change edit that is sequenced before this one. + +Postconditions: +* The [specified items](#specifying-the-set-of-removed-items) are removed. + +Removed items are saved internally for a time in case they need to be restored as a result of an undo operation. +Changes made to them by concurrent edits will apply despite their removed status. + +## Other Operations + +The following operations are just syntactic sugar: + +`insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void` +equates to `array.insertAt(0, ...value)`. + +`insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void` +equates to `array.insertAt(array.length, ...value)`. + +`moveRangeToIndex(destinationGap: number, sourceStart: number, sourceEnd: number): void` +equates to `array.moveRangeToIndex(destinationGap, sourceStart, sourceEnd, array)`. + +`moveRangeToStart(sourceStart: number, sourceEnd: number, source: TMoveFrom): void` +equates to `array.moveRangeToIndex(0, sourceStart, sourceEnd, source)`. + +`moveRangeToStart(sourceStart: number, sourceEnd: number): void` +equates to `array.moveRangeToIndex(0, sourceStart, sourceEnd, array)`. + +`moveToIndex(destinationGap: number, sourceIndex: number, source: TMoveFrom): void` +equates to `array.moveRangeToIndex(destinationGap, sourceIndex, sourceIndex+1, source)`. + +`moveToIndex(destinationGap: number, sourceIndex: number): void` +equates to `array.moveRangeToIndex(destinationGap, sourceIndex, sourceIndex+1, array)`. + +`moveToStart(sourceIndex: number, source: TMoveFrom): void` +equates to `array.moveRangeToIndex(0, sourceIndex, sourceIndex + 1, source)`. + +`moveToStart(sourceIndex: number): void` +equates to `array.moveRangeToIndex(0, sourceIndex, sourceIndex + 1, array)`. + +`moveRangeToEnd(sourceStart: number, sourceEnd: number, source: TMoveFrom): void` +equates to `array.moveRangeToIndex(array.length, sourceStart, sourceEnd, source)`. + +`moveRangeToEnd(sourceStart: number, sourceEnd: number): void` +equates to `array.moveRangeToIndex(array.length, sourceStart, sourceEnd, array)`. + +`moveToEnd(sourceIndex: number, source: TMoveFrom): void` +equates to `array.moveRangeToIndex(array.length, sourceIndex, sourceIndex + 1, source)`. + +`moveToEnd(sourceIndex: number): void` +equates to `array.moveRangeToIndex(array.length, sourceIndex, sourceIndex + 1, array)`. + +`removeAt(index: number): void` +equates to `array.removeRange(index, index + 1)` + +## Additional Notes + +### Operations on Removed Arrays + +All of the above operations are effective even when the targeted array has been moved or removed. + +### Removing and Re-inserting Items + +When dealing with plain JavaScript arrays, +it is possible to move items around by removing them and adding them back in. +For example, the item C can be moved to the start of the array `[A, B, C]` performing the following operations: +```typescript +const C = array.pop(); // Remove C -> [A, B] +array.unshift(C); // Insert C at the start -> [C, A, B] +``` + +As of October 2024, SharedTree arrays do not support this pattern because it would require (re)inserting item C, which has previously been inserted. +Instead, it is necessary to use the move operation: +```typescript +array.moveToStart(2); +``` +Work is underway to address this lack of flexibility. + +### Replacing Items + +When dealing with plain JavaScript arrays, it is possible to replace items. +For example, in array `[A, B, C]`, +the item B can be replaced with item X with [the `splice` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice): +```typescript +array.splice(1, 1, X); +``` +...or simply by using the `=` operator: +```typescript +array[1] = X; +``` + +As of October 2024, SharedTree arrays do not support either of these approaches. +The closest alternative is to remove and insert items in two separate calls: +```typescript +array.removeAt(1); +array.insertAt(1, X); +``` + +Note that this approach may not yield ideal merge outcomes when it comes to concurrent insertions. + +Example: +* Starting state: `["gold", "bronze"]` +* User 1: replace "gold" and "bronze" with "1st place" and "3rd place": + * `removeRange(0, 2)` + * `insertAt(0, "1st place", "3rd place")` +* User 2: insert "2nd place" between "gold" and "bronze": + * `insertAt(1, "2nd place")` +* Merge outcome: `["1st place", "3rd place", "2nd place"]` + +This outcome is not consistent with the idea of replacement +which would have yielded `["1st place", "2nd place", "3rd place"]` instead. +This is because removing and inserting does not update the original items in place. +So we effectively end up with `["1st place", "3rd place", `~~`"gold"`~~`, "2nd place", `~~`"bronze"`~~`]`. + +If in-place replacement is critical to you application's needs, +please reach out to the Fluid team. \ No newline at end of file diff --git a/packages/dds/tree/docs/user-facing/map-merge-semantics.md b/packages/dds/tree/docs/user-facing/map-merge-semantics.md new file mode 100644 index 000000000000..7601d3ab6944 --- /dev/null +++ b/packages/dds/tree/docs/user-facing/map-merge-semantics.md @@ -0,0 +1,128 @@ +# Merge Semantics of Edits on Map Nodes + +This document describes the semantics of edits that can be performed on map nodes. + +Target audience: `SharedTree` users and maintainers. + +> While this document is self-contained, we recommend reading about [SharedTree's approach to merge semantics](merge-semantics) first. + +Each edit's merge semantics are defined in terms of the edit's preconditions and postconditions. +A precondition defines a requirement that must be met for the edit to be valid. +A postcondition defines a guarantee that is made about the effect of the edit. +(Invalid edits are ignored along with all other edits in the same transaction, and postconditions do not hold). + +## `set(key: string, value: T): void` + +Updates the value associated with the given key. + +Examples: +```typescript +users.set("bob", new User({ id: "bob", email: "bob@contoso.com" }); +``` + +```typescript +partCounts.set("bolts", 42); +``` + +Preconditions: +* There is no concurrent schema change edit that is sequenced before the `set` edit. +* The value on the right side of the `=` operator must have status `TreeStatus.New` or be a primitive. + (This precondition will be removed soon) + +Postconditions: +* The given value is now associated with the given key. +* The value (if any) that was associated with the given key immediately prior to the application of the edit is removed (its status becomes `TreeStatus.Removed`). + +## `delete(key: string): void` + +Clears any value associated with the given key. + +```typescript +users.delete("bob"); +``` + +Preconditions: +* There is no concurrent schema change edit that is sequenced before the `delete` edit. + +Postconditions: +* There is no longer any value associated with the given key. + The value (if any) that was associated with the given key immediately prior to the application of the edit is removed (its status becomes `TreeStatus.Removed`). + Note: this will remove whatever value is associated with the key, + even if that value was changed by concurrent edits that were sequenced earlier. + +Removed items are saved internally for a time in case they need to be restored as a result of an undo operation. +Changes made to them will apply despite their removed status. + +## Additional Notes + +### Operations on Removed Maps + +All of the above operations are effective even when the targeted map has been moved or removed. + +### Last-Write-Wins Semantics + +If multiple edits concurrently set the same key, then the key's final value will be that of the edit that is sequenced last. +In other words, the `set` operation has last-write-wins semantics. + +Note that this means one user may overwrite a value set by another user without realizing it. +This is identical to the semantics of the `=` operator on object nodes. +Refer to its [Last-Write-Wins Semantics section](./object-merge-semantics.md#last-write-wins-semantics) for more details. + +### Delete Clears Whichever Value is Present + +If user A calls `map.set("key", 42)` and user B concurrently calls `map.delete("key", 42)` on same map node, +then the outcome depends on the arbitrary ordering that is imposed by the sequencing service: +If the `delete` call is ordered before the `set` call, then the `set` will win out, setting the new associated value for that key. +If the `set` call is ordered before the `delete` call, then the `delete` will win out, clearing the associated value for that key. + +This last point means that one user may end up deleting a value they have never seen. +Consider the following scenario: +* Starting state: `map.get("key")` is equal to `"foo"` +* Client A wants to replace the value `"foo"` with the value `"bar"` so they call `map.set("key", "bar")`. +* Client B wants to delete the value `"foo"` so they call `map.delete("key")`. + +If these two edits are made concurrently, +and if client A's edit is sequenced before client B's edit, +then client B's edit will end up removing the value `"bar"`. +This is typically acceptable, but in cases where it proves problematic, +it's possible to put the `delete` operation in a transaction with a constraint that ensures the value to be deleted is still in the tree. + +### Removing and Re-inserting Nodes + +When dealing with plain JavaScript maps, +it is possible to move items around by removing them and adding them back in. +For example, if some node N needs to be moved from `mapA` to `mapB`, +that can accomplished with the following operations: +```typescript +const N = mapA.get(key); +mapA.delete(key); // Remove node N from mapA +mapB.set(key, N); // Insert node N into mapB +``` + +Similarly, plain JavaScript maps allow moving a value from one key to another within the same map: +```typescript +const N = mapA.get("foo"); +mapA.delete("foo"); // Remove node N from mapA/key "foo" +mapA.set("bar", N); // Insert node N into mapA/key "bar" +``` + +As of October 2024, SharedTree maps do not support these patterns because it would require (re)inserting node N, which has previously been inserted. +Note that even without this restriction, the above would not perform the desired change if some other user concurrently moved N from `mapA` to some other `mapC`. +If that were the case... +* Node N may no longer be in `mapA` by the time the edit `mapA.delete(key)` is applied. + At best that edit would have no effect, and at worst it may inadvertently remove some other node. +* Node N may still be in `mapC` by the time the edit `mapB.set(key, N)` is applied. + If so, this would lead to an error since a single node cannot reside in multiple maps at the same time. + (This would violate the requirement that the document remains tree-shaped). + +Work is underway to address this lack of flexibility. + +### Clearing The Map + +As of October 2024, there is no support for clearing the whole map in one operation. +If you find yourself wishing for such an operation, please reach out to the Fluid team. + +Note that one client can, in a transaction, iterate through all existing key and use the `delete` operation on each of them. +This does not however guarantee that the map will be empty after the transaction is applied +because other users may have concurrently added entries to new keys. +This approach is also much less efficient than a `clear` operation would be since it needs to transmit the set of existing key over the network. \ No newline at end of file diff --git a/packages/dds/tree/docs/main/merge-semantics.md b/packages/dds/tree/docs/user-facing/merge-semantics.md similarity index 98% rename from packages/dds/tree/docs/main/merge-semantics.md rename to packages/dds/tree/docs/user-facing/merge-semantics.md index 8b2b689502bd..0d04a7129a26 100644 --- a/packages/dds/tree/docs/main/merge-semantics.md +++ b/packages/dds/tree/docs/user-facing/merge-semantics.md @@ -358,7 +358,7 @@ By contrast, `SharedTree`'s move semantics ensure that Bob's edit will be visibl In choosing the merge semantics of the set of edits initially supported by `SharedTree`, we have strived to keep the preconditions of those edits minimal. At this time, with the exception of [schema changes](#schema-changes), which have more preconditions, -all supported edits have the same single precondition: +most supported edits have the same single precondition: the document schema must not have been concurrently changed. As illustrated [above](#preconditions), @@ -428,7 +428,7 @@ A conditional postcondition is a postcondition of the form "If \ the (with any number of "else if" branches). For example, a remove edit that was designed not to affect concurrently moved nodes would be characterized as "If the node was concurrently moved then the node is unaffected, otherwise, the node is removed". -After an edit with such a conditional postcondition is applied, it is not certain whether the targeted no will be removed or not. +After an edit with such a conditional postcondition is applied, it is not certain whether the targeted node will be removed or not. None of `SharedTree`'s currently supported edits have conditional postconditions. This means that every edit, so long as its preconditions are met, is guaranteed to always have the same effect. @@ -526,4 +526,8 @@ The merge semantics will be improved in the future to be less conservative. ## Merge Semantics by Node Kind -TODO: add a separate document for each node kind and link to them from here. +For specifics on the merge semantics of individual edits operations for each node type, + +[Object Node](object-merge-semantics.md) +[Map Node](map-merge-semantics.md) +[Array Node](array-merge-semantics.md) diff --git a/packages/dds/tree/docs/user-facing/object-merge-semantics.md b/packages/dds/tree/docs/user-facing/object-merge-semantics.md new file mode 100644 index 000000000000..6d048c911390 --- /dev/null +++ b/packages/dds/tree/docs/user-facing/object-merge-semantics.md @@ -0,0 +1,77 @@ +# Merge Semantics of Edits on Object Nodes + +This document describes the semantics of edits that can be performed on object nodes. + +Target audience: `SharedTree` users and maintainers. + +> While this document is self-contained, we recommend reading about [SharedTree's approach to merge semantics](merge-semantics) first. + +Each edit's merge semantics are defined in terms of the edit's preconditions and postconditions. +A precondition defines a requirement that must be met for the edit to be valid. +A postcondition defines a guarantee that is made about the effect of the edit. +(Invalid edits are ignored along with all other edits in the same transaction, and postconditions do not hold). + +## Operator `=` + +Assigning a value to the property on an object node updates the value associated with that property on the object. + +Examples: + +```typescript +rectangle.topLeft = new Point({ x: 0, y: 0 }); +``` + +```typescript +babyShowerNote.author = "The Joneses"; +``` + +Optional properties can be cleared by assigning `undefined` to them. + +```typescript +proposal.text = undefined; +``` + +Preconditions: +* There is no concurrent schema change edit that is sequenced before the property assignment edit. +* The value on the right side of the `=` operator must have status `TreeStatus.New` or be a primitive. + (This precondition will be removed soon) + +Postconditions: +* The value that was on the right hand side of the `=` operator is now associated with the targeted property. +* The value (if any) that was associated with the object property immediately prior to the application of the edit is removed (its status becomes `TreeStatus.Removed`). + +Removed items are saved internally for a time in case they need to be restored as a result of an undo operation. +Changes made to them will apply despite their removed status. + +## Additional Notes + +### Operations on Removed Objects + +All of the above operations are effective even when the targeted object has been moved or removed. + +### Last-Write-Wins Semantics + +If multiple edits concurrently edit the same field, +then the field's final value will will be that of the edit that is sequenced last. +In other words, property assignment has last-write-wins semantics. + +Note that this means one user may overwrite a value set by another user without realizing it. +Consider the following scenario: +Alice and Bob are editing a document that contains sticky notes whose background color can be changed. +Alice changes the background color of one sticky note from yellow to red, +while Bob concurrently changes the background color of one sticky note from yellow to blue. +The sequencing is such that Bob's edit is sequenced after Alice's edit. + +![Bob's edit overwrites Alice's edit](https://storage.fluidframework.com/static/images/blue-over-red.png)
+_A: Bob receives Alice's edit. +Since Bob's client has yet to receive his own edit back from the sequencing service, +Bob's client can deduce that his edit is sequenced later and therefore wins out over Alice's. +This means the color property can remain blue.
+B: Alice receives Bob's edit. +Even though the edit was originally created in a context where it changed the color from yellow to blue, +the edit now changes the color red to blue._ + +Such overwriting is rare in application where users are given visual cues as to what data other users may be concurrently inspecting/editing. +It's possible to prevent such overwrites by using constraints (effectively changing the semantics to first-write-wins), +but note that this causes the the later edit to be dropped, +and the data associated with it to be lost. diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index 4e0055beaeb9..73ef5d1e08d6 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/tree", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed tree", "homepage": "https://fluidframework.com", "repository": { @@ -178,15 +178,15 @@ "@fluid-private/test-dds-utils": "workspace:~", "@fluid-private/test-drivers": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@fluidframework/test-utils": "workspace:~", - "@fluidframework/tree-previous": "npm:@fluidframework/tree@~2.4.0", + "@fluidframework/tree-previous": "npm:@fluidframework/tree@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/diff": "^3.5.1", "@types/easy-table": "^0.0.32", @@ -228,48 +228,12 @@ }, "typeValidation": { "broken": { - "TypeAlias_InsertableTreeFieldFromImplicitField": { + "TypeAlias_Input": { "backCompat": false, "forwardCompat": false }, - "Interface_TreeArrayNode": { + "Interface_InternalTypes_TreeArrayNodeBase": { "backCompat": false - }, - "TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes": { - "backCompat": false, - "forwardCompat": false - }, - "TypeAlias_NodeFromSchema": { - "backCompat": false - }, - "TypeAlias_TreeFieldFromImplicitField": { - "backCompat": false - }, - "TypeAlias_TreeNodeFromImplicitAllowedTypes": { - "backCompat": false - }, - "TypeAlias_InternalTypes_ApplyKind": { - "forwardCompat": false, - "backCompat": false - }, - "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": { - "forwardCompat": false, - "backCompat": false - }, - "TypeAlias_InsertableTreeFieldFromImplicitFieldUnsafe": { - "forwardCompat": false - }, - "TypeAlias_InternalTypes_InsertableTreeFieldFromImplicitFieldUnsafe": { - "forwardCompat": false - }, - "Interface_ITreeViewConfiguration": { - "forwardCompat": false - }, - "TypeAlias_ImplicitAllowedTypes": { - "forwardCompat": false - }, - "TypeAlias_ImplicitFieldSchema": { - "forwardCompat": false } }, "entrypoint": "public" diff --git a/packages/dds/tree/src/codec/codec.ts b/packages/dds/tree/src/codec/codec.ts index 17d2e7db2730..41414cd78806 100644 --- a/packages/dds/tree/src/codec/codec.ts +++ b/packages/dds/tree/src/codec/codec.ts @@ -328,3 +328,28 @@ export function withSchemaValidation< }, }; } + +/** + * Versions of Fluid Framework client packages. + * @remarks + * Used to express compatibility requirements by indicating the oldest version with which compatibility must be maintained. + * @privateRemarks + * This scheme assumes a single version will always be enough to communicate compatibility. + * For this to work, compatibility has to be strictly increasing. + * If this is violated (for example a subset of incompatible features from 3.x that are not in 3.0 are back ported to 2.x), + * a more complex scheme may be needed to allow safely opting into incompatible features in those cases: + * such a system can be added if/when its needed since it will be opt in and thus non-breaking. + * + * TODO: this should likely be defined higher in the stack and specified when creating the container, possibly as part of its schema. + * @alpha + */ +export enum FluidClientVersion { + /** Fluid Framework Client 2.0 and newer. */ + v2_0 = "v2_0", + /** Fluid Framework Client 2.1 and newer. */ + v2_1 = "v2_1", + /** Fluid Framework Client 2.2 and newer. */ + v2_2 = "v2_2", + /** Fluid Framework Client 2.4 and newer. */ + v2_3 = "v2_3", +} diff --git a/packages/dds/tree/src/codec/index.ts b/packages/dds/tree/src/codec/index.ts index 348cc6f204e4..8a9a81ca0ef6 100644 --- a/packages/dds/tree/src/codec/index.ts +++ b/packages/dds/tree/src/codec/index.ts @@ -18,6 +18,7 @@ export { unitCodec, withDefaultBinaryEncoding, withSchemaValidation, + FluidClientVersion, } from "./codec.js"; export { DiscriminatedUnionDispatcher, diff --git a/packages/dds/tree/src/core/forest/forest.ts b/packages/dds/tree/src/core/forest/forest.ts index a57f8325a0b0..403dceac2f00 100644 --- a/packages/dds/tree/src/core/forest/forest.ts +++ b/packages/dds/tree/src/core/forest/forest.ts @@ -58,7 +58,12 @@ export interface ForestEvents { * * When invalidating, all outstanding cursors must be freed or cleared. */ -export interface IForestSubscription extends Listenable { +export interface IForestSubscription { + /** + * Events for this forest. + */ + readonly events: Listenable; + /** * Set of anchors this forest is tracking. * diff --git a/packages/dds/tree/src/core/index.ts b/packages/dds/tree/src/core/index.ts index 2dcc3e9d38ef..948b96836e26 100644 --- a/packages/dds/tree/src/core/index.ts +++ b/packages/dds/tree/src/core/index.ts @@ -121,7 +121,7 @@ export { type TreeFieldStoredSchema, ValueSchema, TreeNodeStoredSchema, - type TreeStoredSchemaSubscription as TreeStoredSchemaSubscription, + type TreeStoredSchemaSubscription, type MutableTreeStoredSchema, type FieldKindIdentifier, type FieldKindData, diff --git a/packages/dds/tree/src/core/rebase/types.ts b/packages/dds/tree/src/core/rebase/types.ts index 6c7d3e447ff6..d185473eb469 100644 --- a/packages/dds/tree/src/core/rebase/types.ts +++ b/packages/dds/tree/src/core/rebase/types.ts @@ -161,11 +161,11 @@ export interface GraphCommit { * @public */ export enum CommitKind { - /** A commit corresponding to a change that is not the result of an undo/redo. */ + /** A commit corresponding to a change that is not the result of an undo/redo from this client. */ Default, - /** A commit that is the result of an undo. */ + /** A commit that is the result of an undo from this client. */ Undo, - /** A commit that is the result of a redo. */ + /** A commit that is the result of a redo from this client. */ Redo, } diff --git a/packages/dds/tree/src/core/revertible.ts b/packages/dds/tree/src/core/revertible.ts index 17838f8eff4f..735daaeba40e 100644 --- a/packages/dds/tree/src/core/revertible.ts +++ b/packages/dds/tree/src/core/revertible.ts @@ -50,7 +50,7 @@ export enum RevertibleStatus { /** * Factory for creating a {@link Revertible}. - * Will error if invoked outside the scope of the `commitApplied` event that provides it, or if invoked multiple times. + * Will error if invoked outside the scope of the `changed` event that provides it, or if invoked multiple times. * * @param onRevertibleDisposed - A callback that will be invoked when the `Revertible` generated by this factory is disposed. * This happens when the `Revertible` is disposed manually, or when the `TreeView` that the `Revertible` belongs to is disposed, diff --git a/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts b/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts index 2c1d3d81bfee..1081a4ec15c0 100644 --- a/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts +++ b/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts @@ -37,9 +37,12 @@ export interface SchemaEvents { /** * A collection of stored schema that fires events in response to changes. */ -export interface TreeStoredSchemaSubscription - extends Listenable, - TreeStoredSchema {} +export interface TreeStoredSchemaSubscription extends TreeStoredSchema { + /** + * Events for this schema subscription. + */ + readonly events: Listenable; +} /** * Mutable collection of stored schema. @@ -59,7 +62,8 @@ export interface MutableTreeStoredSchema extends TreeStoredSchemaSubscription { export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { protected nodeSchemaData: BTree; protected rootFieldSchemaData: TreeFieldStoredSchema; - protected readonly events = createEmitter(); + protected readonly _events = createEmitter(); + public readonly events: Listenable = this._events; /** * Copies in the provided schema. If `data` is an TreeStoredSchemaRepository, it will be cheap-cloned. @@ -92,13 +96,6 @@ export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { } } - public on( - eventName: K, - listener: SchemaEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public get nodeSchema(): ReadonlyMap { // Btree implements iterator, but not in a type-safe way return this.nodeSchemaData as unknown as ReadonlyMap< @@ -112,12 +109,12 @@ export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { } public apply(newSchema: TreeStoredSchema): void { - this.events.emit("beforeSchemaChange", newSchema); + this._events.emit("beforeSchemaChange", newSchema); const clone = new TreeStoredSchemaRepository(newSchema); // In the future, we could use btree's delta functionality to do a more efficient update this.rootFieldSchemaData = clone.rootFieldSchemaData; this.nodeSchemaData = clone.nodeSchemaData; - this.events.emit("afterSchemaChange", newSchema); + this._events.emit("afterSchemaChange", newSchema); } public clone(): TreeStoredSchemaRepository { diff --git a/packages/dds/tree/src/core/tree/anchorSet.ts b/packages/dds/tree/src/core/tree/anchorSet.ts index f255c2544676..73c3b88e9e75 100644 --- a/packages/dds/tree/src/core/tree/anchorSet.ts +++ b/packages/dds/tree/src/core/tree/anchorSet.ts @@ -206,7 +206,12 @@ export interface AnchorSetRootEvents { /** * Node in a tree of anchors. */ -export interface AnchorNode extends UpPath, Listenable { +export interface AnchorNode extends UpPath { + /** + * Events for this anchor node. + */ + readonly events: Listenable; + /** * Allows access to data stored on the Anchor in "slots". * Use {@link anchorSlot} to create slots. @@ -277,8 +282,10 @@ export function anchorSlot(): AnchorSlot { * * @sealed */ -export class AnchorSet implements Listenable, AnchorLocator { - private readonly events = createEmitter(); +export class AnchorSet implements AnchorLocator { + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; + /** * Incrementing counter to give each anchor in this set a unique index for its identifier. * "0" is reserved for the `NeverAnchor`. @@ -312,7 +319,7 @@ export class AnchorSet implements Listenable, AnchorLocator private activeVisitor?: DeltaVisitor; public constructor() { - this.on("treeChanging", () => { + this.events.on("treeChanging", () => { this.generationNumber += 1; }); } @@ -344,13 +351,6 @@ export class AnchorSet implements Listenable, AnchorLocator } } - public on( - eventName: K, - listener: AnchorSetRootEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - /** * Check if there are currently no anchors tracked. * Mainly for testing anchor cleanup. @@ -792,7 +792,7 @@ export class AnchorSet implements Listenable, AnchorLocator notifyChildrenChanging(): void { this.maybeWithNode( (p) => p.events.emit("childrenChanging", p), - () => this.anchorSet.events.emit("childrenChanging", this.anchorSet), + () => this.anchorSet.#events.emit("childrenChanging", this.anchorSet), ); }, notifyChildrenChanged(): void { @@ -1098,7 +1098,7 @@ export class AnchorSet implements Listenable, AnchorLocator this.parentField = undefined; }, }; - this.events.emit("treeChanging", this); + this.#events.emit("treeChanging", this); this.activeVisitor = visitor; return visitor; } @@ -1209,13 +1209,6 @@ class PathNode extends ReferenceCountedBase implements UpPath, AnchorN super(1); } - public on( - eventName: K, - listener: AnchorEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public child(key: FieldKey, index: number): UpPath { // Fast path: if child exists, return it. return ( diff --git a/packages/dds/tree/src/events/emitter.ts b/packages/dds/tree/src/events/emitter.ts index 7f0f7e5ac1c3..f352b374a79d 100644 --- a/packages/dds/tree/src/events/emitter.ts +++ b/packages/dds/tree/src/events/emitter.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { setInNestedMap } from "../util/index.js"; +import { UsageError } from "@fluidframework/telemetry-utils/internal"; +import { getOrCreate } from "../util/index.js"; import type { Listenable, Listeners, Off } from "./listeners.js"; /** @@ -63,7 +64,7 @@ export interface HasListeners> { /** * Provides an API for subscribing to and listening to events. * - * @remarks Classes wishing to emit events may either extend this class or compose over it. + * @remarks Classes wishing to emit events may either extend this class, compose over it, or expose it as a property of type {@link Listenable}. * * @example Extending this class * @@ -97,13 +98,27 @@ export interface HasListeners> { * } * } * ``` + * + * @example Exposing this class as a property + * + * ```typescript + * class MyExposingClass { + * private readonly _events = createEmitter(); + * public readonly events: Listenable = this._events; + * + * private load() { + * this._events.emit("loaded"); + * const results: number[] = this._events.emitAndCollect("computed"); + * } + * } + * ``` */ export class EventEmitter> implements Listenable, HasListeners { protected readonly listeners = new Map< keyof TListeners, - Map TListeners[keyof TListeners]> + Set<(...args: any[]) => TListeners[keyof TListeners]> >(); // Because this is protected and not public, calling this externally (not from a subclass) makes sending events to the constructed instance impossible. @@ -118,11 +133,10 @@ export class EventEmitter> if (listeners !== undefined) { // Current tsc (5.4.5) cannot spread `args` into `listener()`. const argArray: unknown[] = args; - // This explicitly copies listeners so that new listeners added during this call to emit will not receive this event. - for (const [off, listener] of [...listeners]) { + for (const listener of [...listeners]) { // If listener has been unsubscribed while invoking other listeners, skip it. - if (listeners.has(off)) { + if (listeners.has(listener)) { listener(...argArray); } } @@ -145,29 +159,32 @@ export class EventEmitter> return []; } - /** - * Register an event listener. - * @param eventName - the name of the event - * @param listener - the handler to run when the event is fired by the emitter - * @returns a function which will deregister the listener when run. - * This function will error if called more than once. - */ public on>( eventName: K, listener: TListeners[K], ): Off { - const off: Off = () => { - const currentListeners = this.listeners.get(eventName); - if (currentListeners?.delete(off) === true) { - if (currentListeners.size === 0) { - this.listeners.delete(eventName); - this.noListeners?.(eventName); - } - } - }; + const listeners = getOrCreate(this.listeners, eventName, () => new Set()); + if (listeners.has(listener)) { + const eventDescription = + typeof eventName === "symbol" ? eventName.description : String(eventName.toString()); - setInNestedMap(this.listeners, eventName, off, listener); - return off; + throw new UsageError( + `Attempted to register the same listener object twice for event ${eventDescription}`, + ); + } + listeners.add(listener); + return () => this.off(eventName, listener); + } + + public off>( + eventName: K, + listener: TListeners[K], + ): void { + const listeners = this.listeners.get(eventName); + if (listeners?.delete(listener) === true && listeners.size === 0) { + this.listeners.delete(eventName); + this.noListeners?.(eventName); + } } public hasListeners(eventName?: keyof TListeners): boolean { @@ -225,6 +242,10 @@ class ComposableEventEmitter> * public on(eventName: K, listener: MyEvents[K]): Off { * return this.events.on(eventName, listener); * } + * + * public off(eventName: K, listener: MyEvents[K]): void { + * return this.events.off(eventName, listener); + * } * } * ``` */ diff --git a/packages/dds/tree/src/events/listeners.ts b/packages/dds/tree/src/events/listeners.ts index 06745b1eccda..d87d6d3b6060 100644 --- a/packages/dds/tree/src/events/listeners.ts +++ b/packages/dds/tree/src/events/listeners.ts @@ -50,13 +50,25 @@ export interface Listenable { /** * Register an event listener. * @param eventName - The name of the event. - * @param listener - the handler to run when the event is fired by the emitter - * @returns a {@link Off | function} which will deregister the listener when called. - * This deregistration function is idempotent and therefore may be safely called more than once with no effect. - * @remarks Do not register the exact same `listener` object for the same event more than once. - * Doing so will result in undefined behavior, and is not guaranteed to behave the same in future versions of this library. + * @param listener - The listener function to run when the event is fired. + * @returns A {@link Off | function} which will deregister the listener when called. + * Calling the deregistration function more than once will have no effect. + * + * Listeners may also be deregistered by passing the listener to {@link Listenable.off | off()}. + * @remarks Registering the exact same `listener` object for the same event more than once will throw an error. + * If registering the same listener for the same event multiple times is desired, consider using a wrapper function for the second subscription. */ on>(eventName: K, listener: TListeners[K]): Off; + + /** + * Deregister an event listener. + * @param eventName - The name of the event. + * @param listener - The listener function to remove from the current set of event listeners. + * @remarks If `listener` is not currently registered, this method will have no effect. + * + * Listeners may also be deregistered by calling the {@link Off | deregistration function} returned when they are {@link Listenable.on | registered}. + */ + off>(eventName: K, listener: TListeners[K]): void; } /** diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/basicChunk.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/basicChunk.ts index d516434e7677..e613489d7e7f 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/basicChunk.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/basicChunk.ts @@ -30,14 +30,22 @@ export class BasicChunk extends ReferenceCountedBase implements TreeChunk { /** * Create a tree chunk with ref count 1. * - * @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). - * The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), - * and any edits to this must update child reference counts. - * @param value - the value on this node, if any. + * Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). */ public constructor( public type: TreeNodeSchemaIdentifier, + /** + * Fields of this node. + * @remarks + * This object has exclusive deep ownership of this map (which might mutate it in the future). + * Any code editing this map must update child reference counts. + * + * Like with {@link MapTree}, fields with no nodes must be removed from the map. + */ public fields: Map, + /** + * The value on this node, if any. + */ public value?: TreeValue, ) { super(); diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts index 8a201c8a092e..521eea910817 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts @@ -134,7 +134,7 @@ export class Chunker implements IChunker { if (cached !== undefined) { return cached; } - this.unregisterSchemaCallback = this.schema.on("afterSchemaChange", () => + this.unregisterSchemaCallback = this.schema.events.on("afterSchemaChange", () => this.schemaChanged(), ); return this.tryShapeFromSchema(this.schema, this.policy, schema, this.typeShapes); diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts index 3218c0ab22ba..b48d30cb729e 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts @@ -28,7 +28,7 @@ import { mapCursorField, rootFieldKey, } from "../../core/index.js"; -import { createEmitter } from "../../events/index.js"; +import { createEmitter, type Listenable } from "../../events/index.js"; import { assertValidRange, brand, fail, getOrAddEmptyToMap } from "../../util/index.js"; import { BasicChunk, BasicChunkCursor, type SiblingsOrKey } from "./basicChunk.js"; @@ -53,7 +53,8 @@ interface StackNode { export class ChunkedForest implements IEditableForest { private activeVisitor?: DeltaVisitor; - private readonly events = createEmitter(); + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; /** * @param roots - dummy node above the root under which detached fields are stored. All content of the forest is reachable from this. @@ -73,13 +74,6 @@ export class ChunkedForest implements IEditableForest { return this.roots.fields.size === 0; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public clone(schema: TreeStoredSchemaSubscription, anchors: AnchorSet): ChunkedForest { this.roots.referenceAdded(); return new ChunkedForest(this.roots, schema, this.chunker.clone(schema), anchors); @@ -119,11 +113,11 @@ export class ChunkedForest implements IEditableForest { this.forest.activeVisitor = undefined; }, destroy(detachedField: FieldKey, count: number): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); this.forest.roots.fields.delete(detachedField); }, create(content: ProtoNodes, destination: FieldKey): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const chunks: TreeChunk[] = content.map((c) => chunkTree(c, { policy: this.forest.chunker, @@ -131,7 +125,7 @@ export class ChunkedForest implements IEditableForest { }), ); this.forest.roots.fields.set(destination, chunks); - this.forest.events.emit("afterRootFieldCreated", destination); + this.forest.#events.emit("afterRootFieldCreated", destination); }, attach(source: FieldKey, count: number, destination: PlaceIndex): void { this.attachEdit(source, count, destination); @@ -146,7 +140,7 @@ export class ChunkedForest implements IEditableForest { * @param destination - The index in the current field at which to attach the content. */ attachEdit(source: FieldKey, count: number, destination: PlaceIndex): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const sourceField = this.forest.roots.fields.get(source) ?? []; this.forest.roots.fields.delete(source); if (sourceField.length === 0) { @@ -166,7 +160,7 @@ export class ChunkedForest implements IEditableForest { * If not specified, the detached range is destroyed. */ detachEdit(source: Range, destination: FieldKey | undefined): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const parent = this.getParent(); const sourceField = parent.mutableChunk.fields.get(parent.key) ?? []; @@ -201,6 +195,11 @@ export class ChunkedForest implements IEditableForest { newContentSource !== oldContentDestination, 0x7b0 /* Replace detached source field and detached destination field must be different */, ); + // TODO: optimize this to: perform in-place replace in uniform chunks when possible. + // This should result in 3 cases: + // 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks. + // 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy. + // 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed). this.detachEdit(range, oldContentDestination); this.attachEdit(newContentSource, range.end - range.start, range.start); }, diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/context.ts b/packages/dds/tree/src/feature-libraries/flex-tree/context.ts index 8ec7112c79aa..7a2f57f87f9f 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/context.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/context.ts @@ -49,7 +49,8 @@ export interface FlexTreeContext { * A common context of a "forest" of FlexTrees. * It handles group operations like transforming cursors into anchors for edits. */ -export interface FlexTreeHydratedContext extends FlexTreeContext, Listenable { +export interface FlexTreeHydratedContext extends FlexTreeContext { + readonly events: Listenable; /** * Gets the root field of the tree. */ @@ -92,7 +93,7 @@ export class Context implements FlexTreeHydratedContext, IDisposable { public readonly nodeKeyManager: NodeKeyManager, ) { this.eventUnregister = [ - this.checkout.forest.on("beforeChange", () => { + this.checkout.forest.events.on("beforeChange", () => { this.prepareForEdit(); }), ]; @@ -160,11 +161,8 @@ export class Context implements FlexTreeHydratedContext, IDisposable { return field; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.checkout.forest.on(eventName, listener); + public get events(): Listenable { + return this.checkout.forest.events; } } diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/flexTreeTypes.ts b/packages/dds/tree/src/feature-libraries/flex-tree/flexTreeTypes.ts index 5a14255d545a..1d22bf5bdba6 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/flexTreeTypes.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/flexTreeTypes.ts @@ -9,6 +9,7 @@ import { type FieldKey, type FieldKindIdentifier, type FieldUpPath, + type ITreeCursorSynchronous, type TreeNodeSchemaIdentifier, type TreeValue, anchorSlot, @@ -184,6 +185,14 @@ export interface FlexTreeNode extends FlexTreeEntity { * If well-formed, it must follow this schema. */ readonly schema: TreeNodeSchemaIdentifier; + + /** + * Get a cursor for the underlying data. + * @remarks + * This cursor might be one the node uses in its implementation, and thus must be returned to its original location before using any other APIs to interact with the tree. + * Must not be held onto across edits or any other tree API use. + */ + borrowCursor(): ITreeCursorSynchronous; } /** diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts index d3c734662835..548496de74a8 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts @@ -151,7 +151,7 @@ export abstract class LazyField extends LazyEntity implements FlexT const anchorNode = context.checkout.forest.anchors.locate(fieldAnchor.parent) ?? fail("parent anchor node should always exist since field is under a node"); - this.offAfterDestroy = anchorNode.on("afterDestroy", () => { + this.offAfterDestroy = anchorNode.events.on("afterDestroy", () => { this[disposeSymbol](); }); } diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts index 5f54cececf3b..ee6ff1962214 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts @@ -10,6 +10,7 @@ import { type AnchorNode, CursorLocationType, type FieldKey, + type ITreeCursorSynchronous, type FieldKindIdentifier, type ITreeSubscriptionCursor, type TreeNavigationResult, @@ -88,7 +89,11 @@ export class LazyTreeNode extends LazyEntity implements FlexTreeNode { this.storedSchema = context.schema.nodeSchema.get(this.schema) ?? fail("missing schema"); assert(cursor.mode === CursorLocationType.Nodes, 0x783 /* must be in nodes mode */); anchorNode.slots.set(flexTreeSlot, this); - this.#removeDeleteCallback = anchorNode.on("afterDestroy", cleanupTree); + this.#removeDeleteCallback = anchorNode.events.on("afterDestroy", cleanupTree); + } + + public borrowCursor(): ITreeCursorSynchronous { + return this[cursorSymbol] as ITreeCursorSynchronous; } protected override [tryMoveCursorToAnchorSymbol]( diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/utilities.ts b/packages/dds/tree/src/feature-libraries/flex-tree/utilities.ts index 0571aa936de4..c70ded34a481 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/utilities.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/utilities.ts @@ -99,7 +99,7 @@ export function getSchemaAndPolicy(nodeOrField: FlexTreeEntity): SchemaAndPolicy */ export function indexForAt(index: number, length: number): number | undefined { let finalIndex = Math.trunc(+index); - if (isNaN(finalIndex)) { + if (Number.isNaN(finalIndex)) { finalIndex = 0; } if (finalIndex < -length || finalIndex >= length) { diff --git a/packages/dds/tree/src/feature-libraries/index.ts b/packages/dds/tree/src/feature-libraries/index.ts index 1d6b4ff96922..f88ae9d1e249 100644 --- a/packages/dds/tree/src/feature-libraries/index.ts +++ b/packages/dds/tree/src/feature-libraries/index.ts @@ -74,7 +74,7 @@ export { type FieldChangeEncodingContext, type FieldKindConfiguration, type FieldKindConfigurationEntry, - getAllowedContentIncompatibilities, + getAllowedContentDiscrepancies, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/discrepancies.ts b/packages/dds/tree/src/feature-libraries/modular-schema/discrepancies.ts index 613a94cf730a..b3c8a8f26644 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/discrepancies.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/discrepancies.ts @@ -27,45 +27,45 @@ import { brand } from "../../util/index.js"; /** * @remarks * - * 1. FieldIncompatibility + * 1. FieldDiscrepancy * - * `FieldIncompatibility` represents the differences between two `TreeFieldStoredSchema` objects. It consists of + * `FieldDiscrepancy` represents the differences between two `TreeFieldStoredSchema` objects. It consists of * three types of incompatibilities: * - * - FieldKindIncompatibility: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` + * - FieldKindDiscrepancy: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` * objects (e.g., optional, required, sequence, etc.). - * - AllowedTypesIncompatibility: Indicates the differences in the allowed child types between the two schemas. - * - ValueSchemaIncompatibility: Specifically indicates the differences in the `ValueSchema` of two + * - AllowedTypesDiscrepancy: Indicates the differences in the allowed child types between the two schemas. + * - ValueSchemaDiscrepancy: Specifically indicates the differences in the `ValueSchema` of two * `LeafNodeStoredSchema` objects. * - * 2. NodeIncompatibility + * 2. NodeDiscrepancy * - * `NodeIncompatibility` represents the differences between two `TreeNodeStoredSchema` objects and includes: + * `NodeDiscrepancy` represents the differences between two `TreeNodeStoredSchema` objects and includes: * - * - NodeKindIncompatibility: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports + * - NodeKindDiscrepancy: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports * `ObjectNodeStoredSchema`, `MapNodeStoredSchema`, and `LeafNodeStoredSchema`). - * - NodeFieldsIncompatibility: Indicates the `FieldIncompatibility` of `TreeFieldStoredSchema` within two - * `TreeNodeStoredSchema`. It includes an array of `FieldIncompatibility` instances in the `differences` field. + * - NodeFieldsDiscrepancy: Indicates the `FieldDiscrepancy` of `TreeFieldStoredSchema` within two + * `TreeNodeStoredSchema`. It includes an array of `FieldDiscrepancy` instances in the `differences` field. * * When comparing two nodes for compatibility, it only makes sense to compare their fields if the nodes are of * the same kind (map, object, leaf). * - * 3. Incompatibility + * 3. Discrepancy * - * Incompatibility consists of both `NodeIncompatibility` and `FieldIncompatibility`, representing any kind of - * schema differences. See {@link getAllowedContentIncompatibilities} for more details about how we process it + * Discrepancy consists of both `NodeDiscrepancy` and `FieldDiscrepancy`, representing any kind of + * schema differences. See {@link getAllowedContentDiscrepancies} for more details about how we process it * and the ordering. */ -export type Incompatibility = FieldIncompatibility | NodeIncompatibility; +export type Discrepancy = FieldDiscrepancy | NodeDiscrepancy; -export type NodeIncompatibility = NodeKindIncompatibility | NodeFieldsIncompatibility; +export type NodeDiscrepancy = NodeKindDiscrepancy | NodeFieldsDiscrepancy; -export type FieldIncompatibility = - | AllowedTypeIncompatibility - | FieldKindIncompatibility - | ValueSchemaIncompatibility; +export type FieldDiscrepancy = + | AllowedTypeDiscrepancy + | FieldKindDiscrepancy + | ValueSchemaDiscrepancy; -export interface AllowedTypeIncompatibility { +export interface AllowedTypeDiscrepancy { identifier: string | undefined; // undefined indicates root field schema mismatch: "allowedTypes"; /** @@ -78,62 +78,60 @@ export interface AllowedTypeIncompatibility { stored: string[]; } -export interface FieldKindIncompatibility { +export interface FieldKindDiscrepancy { identifier: string | undefined; // undefined indicates root field schema mismatch: "fieldKind"; view: FieldKindIdentifier; stored: FieldKindIdentifier; } -export interface ValueSchemaIncompatibility { +export interface ValueSchemaDiscrepancy { identifier: string; mismatch: "valueSchema"; view: ValueSchema | undefined; stored: ValueSchema | undefined; } -export interface NodeKindIncompatibility { +export interface NodeKindDiscrepancy { identifier: string; mismatch: "nodeKind"; view: SchemaFactoryNodeKind | undefined; stored: SchemaFactoryNodeKind | undefined; } -export interface NodeFieldsIncompatibility { +export interface NodeFieldsDiscrepancy { identifier: string; mismatch: "fields"; - differences: FieldIncompatibility[]; + differences: FieldDiscrepancy[]; } type SchemaFactoryNodeKind = "object" | "leaf" | "map"; /** - * @remarks + * Finds and reports discrepancies between a view schema and a stored schema. * * The workflow for finding schema incompatibilities: - * 1. Compare the two root schemas to identify any `FieldIncompatibility`. + * 1. Compare the two root schemas to identify any `FieldDiscrepancy`. * * 2. For each node schema in the `view`: * - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are - * consistent. Otherwise this difference is treated as `NodeKindIncompatibility` + * consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` * - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` - * are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. + * are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. * * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already * addressed in the previous step. * * @returns the discrepancies between two TreeStoredSchema objects */ -export function getAllowedContentIncompatibilities( +export function getAllowedContentDiscrepancies( view: TreeStoredSchema, stored: TreeStoredSchema, -): Incompatibility[] { - const incompatibilities: Incompatibility[] = []; +): Discrepancy[] { + const discrepancies: Discrepancy[] = []; // check root schema discrepancies - incompatibilities.push( - ...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema), - ); + discrepancies.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); // Verify the existence and type of a node schema given its identifier (key), then determine if // an exhaustive search is necessary. @@ -143,7 +141,7 @@ export function getAllowedContentIncompatibilities( if (viewNodeSchema instanceof ObjectNodeStoredSchema) { if (!stored.nodeSchema.has(key)) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "object", @@ -156,27 +154,27 @@ export function getAllowedContentIncompatibilities( 0x9be /* The storedNodeSchema in stored.nodeSchema should not be undefined */, ); if (storedNodeSchema instanceof MapNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "object", stored: "map", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof LeafNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "object", stored: "leaf", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof ObjectNodeStoredSchema) { const differences = trackObjectNodeDiscrepancies(viewNodeSchema, storedNodeSchema); if (differences.length > 0) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "fields", differences, - } satisfies NodeFieldsIncompatibility); + } satisfies NodeFieldsDiscrepancy); } } else { throwUnsupportedNodeType(storedNodeSchema.constructor.name); @@ -184,12 +182,12 @@ export function getAllowedContentIncompatibilities( } } else if (viewNodeSchema instanceof MapNodeStoredSchema) { if (!stored.nodeSchema.has(key)) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "map", stored: undefined, - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else { const storedNodeSchema = stored.nodeSchema.get(key); assert( @@ -197,21 +195,21 @@ export function getAllowedContentIncompatibilities( 0x9bf /* The storedNodeSchema in stored.nodeSchema should not be undefined */, ); if (storedNodeSchema instanceof ObjectNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "map", stored: "object", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof LeafNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "map", stored: "leaf", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof MapNodeStoredSchema) { - incompatibilities.push( + discrepancies.push( ...trackFieldDiscrepancies( viewNodeSchema.mapFields, storedNodeSchema.mapFields, @@ -224,7 +222,7 @@ export function getAllowedContentIncompatibilities( } } else if (viewNodeSchema instanceof LeafNodeStoredSchema) { if (!stored.nodeSchema.has(key)) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "leaf", @@ -237,27 +235,27 @@ export function getAllowedContentIncompatibilities( 0x9c0 /* The storedNodeSchema in stored.nodeSchema should not be undefined */, ); if (storedNodeSchema instanceof MapNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "leaf", stored: "map", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof ObjectNodeStoredSchema) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: "leaf", stored: "object", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } else if (storedNodeSchema instanceof LeafNodeStoredSchema) { if (viewNodeSchema.leafValue !== storedNodeSchema.leafValue) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "valueSchema", view: viewNodeSchema.leafValue, stored: storedNodeSchema.leafValue, - } satisfies ValueSchemaIncompatibility); + } satisfies ValueSchemaDiscrepancy); } } else { throwUnsupportedNodeType(storedNodeSchema.constructor.name); @@ -270,7 +268,7 @@ export function getAllowedContentIncompatibilities( for (const [key, storedNodeSchema] of stored.nodeSchema) { if (!viewNodeKeys.has(key)) { - incompatibilities.push({ + discrepancies.push({ identifier: key, mismatch: "nodeKind", view: undefined, @@ -280,11 +278,11 @@ export function getAllowedContentIncompatibilities( : storedNodeSchema instanceof ObjectNodeStoredSchema ? "object" : "leaf", - } satisfies NodeKindIncompatibility); + } satisfies NodeKindDiscrepancy); } } - return incompatibilities; + return discrepancies; } /** @@ -296,8 +294,8 @@ function trackFieldDiscrepancies( view: TreeFieldStoredSchema, stored: TreeFieldStoredSchema, keyOrRoot?: string, -): FieldIncompatibility[] { - const differences: FieldIncompatibility[] = []; +): FieldDiscrepancy[] { + const differences: FieldDiscrepancy[] = []; // Only track the symmetric differences of two sets. const findSetDiscrepancies = ( @@ -316,7 +314,7 @@ function trackFieldDiscrepancies( mismatch: "allowedTypes", view: allowedTypesDiscrepancies[0], stored: allowedTypesDiscrepancies[1], - } satisfies AllowedTypeIncompatibility); + } satisfies AllowedTypeDiscrepancy); } if (view.kind !== stored.kind) { @@ -325,7 +323,7 @@ function trackFieldDiscrepancies( mismatch: "fieldKind", view: view.kind, stored: stored.kind, - } satisfies FieldKindIncompatibility); + } satisfies FieldKindDiscrepancy); } return differences; @@ -334,8 +332,8 @@ function trackFieldDiscrepancies( function trackObjectNodeDiscrepancies( view: ObjectNodeStoredSchema, stored: ObjectNodeStoredSchema, -): FieldIncompatibility[] { - const differences: FieldIncompatibility[] = []; +): FieldDiscrepancy[] { + const differences: FieldDiscrepancy[] = []; const viewFieldKeys = new Set(); /** * Similar to the logic used for tracking discrepancies between two node schemas, we will identify @@ -359,7 +357,7 @@ function trackObjectNodeDiscrepancies( mismatch: "fieldKind", view: fieldStoredSchema.kind, stored: storedEmptyFieldSchema.kind, - } satisfies FieldKindIncompatibility); + } satisfies FieldKindDiscrepancy); } else { differences.push( ...trackFieldDiscrepancies( @@ -382,7 +380,7 @@ function trackObjectNodeDiscrepancies( mismatch: "fieldKind", view: storedEmptyFieldSchema.kind, stored: fieldStoredSchema.kind, - } satisfies FieldKindIncompatibility); + } satisfies FieldKindDiscrepancy); } } @@ -407,12 +405,12 @@ function trackObjectNodeDiscrepancies( * validating internal fields. */ export function isRepoSuperset(view: TreeStoredSchema, stored: TreeStoredSchema): boolean { - const incompatibilities = getAllowedContentIncompatibilities(view, stored); + const discrepancies = getAllowedContentDiscrepancies(view, stored); - for (const incompatibility of incompatibilities) { - switch (incompatibility.mismatch) { + for (const discrepancy of discrepancies) { + switch (discrepancy.mismatch) { case "nodeKind": { - if (incompatibility.stored !== undefined) { + if (discrepancy.stored !== undefined) { // It's fine for the view schema to know of a node type that the stored schema doesn't know about. return false; } @@ -421,14 +419,14 @@ export function isRepoSuperset(view: TreeStoredSchema, stored: TreeStoredSchema) case "valueSchema": case "allowedTypes": case "fieldKind": { - if (!validateFieldIncompatibility(incompatibility)) { + if (!validateFieldIncompatibility(discrepancy)) { return false; } break; } case "fields": { if ( - incompatibility.differences.some( + discrepancy.differences.some( (difference) => !validateFieldIncompatibility(difference), ) ) { @@ -442,16 +440,16 @@ export function isRepoSuperset(view: TreeStoredSchema, stored: TreeStoredSchema) return true; } -function validateFieldIncompatibility(incompatibility: FieldIncompatibility): boolean { - switch (incompatibility.mismatch) { +function validateFieldIncompatibility(discrepancy: FieldDiscrepancy): boolean { + switch (discrepancy.mismatch) { case "allowedTypes": { // Since we only track the symmetric difference between the allowed types in the view and // stored schemas, it's sufficient to check if any extra allowed types still exist in the // stored schema. - return incompatibility.stored.length === 0; + return discrepancy.stored.length === 0; } case "fieldKind": { - return posetLte(incompatibility.stored, incompatibility.view, fieldRealizer); + return posetLte(discrepancy.stored, discrepancy.view, fieldRealizer); } case "valueSchema": { return false; diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/genericFieldKind.ts b/packages/dds/tree/src/feature-libraries/modular-schema/genericFieldKind.ts index fae018821220..473a97943789 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/genericFieldKind.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/genericFieldKind.ts @@ -103,8 +103,8 @@ function rebaseGenericChange( break; } - const newIndex = newEntry?.[0] ?? Infinity; - const baseIndex = baseEntry?.[0] ?? Infinity; + const newIndex = newEntry?.[0] ?? Number.POSITIVE_INFINITY; + const baseIndex = baseEntry?.[0] ?? Number.POSITIVE_INFINITY; let newNodeChange: NodeId | undefined; let baseNodeChange: NodeId | undefined; let index: number; diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/index.ts b/packages/dds/tree/src/feature-libraries/modular-schema/index.ts index e39a97f82850..8378915b1208 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/index.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/index.ts @@ -74,4 +74,7 @@ export type { FieldKindConfiguration, FieldKindConfigurationEntry, } from "./fieldKindConfiguration.js"; -export { getAllowedContentIncompatibilities, isRepoSuperset } from "./discrepancies.js"; +export { + getAllowedContentDiscrepancies, + isRepoSuperset, +} from "./discrepancies.js"; diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts index a5c5f6b1ad68..da90aed60095 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts @@ -3016,7 +3016,7 @@ function getFirstIntersectingCrossFieldEntry( table: CrossFieldKeyTable, [target, revision, id, count]: CrossFieldKeyRange, ): [CrossFieldKeyRange, FieldId] | undefined { - const entry = table.nextLowerPair([target, revision, id, Infinity]); + const entry = table.nextLowerPair([target, revision, id, Number.POSITIVE_INFINITY]); if (entry === undefined) { return undefined; } diff --git a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts index 61f5d1f19db6..ffacb467ef3c 100644 --- a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts +++ b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts @@ -33,7 +33,7 @@ import { aboveRootPlaceholder, deepCopyMapTree, } from "../../core/index.js"; -import { createEmitter } from "../../events/index.js"; +import { createEmitter, type Listenable } from "../../events/index.js"; import { assertNonNegativeSafeInteger, assertValidIndex, @@ -73,7 +73,8 @@ export class ObjectForest implements IEditableForest { // All cursors that are in the "Current" state. Must be empty when editing. public readonly currentCursors: Set = new Set(); - private readonly events = createEmitter(); + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; readonly #roots: MutableMapTree; public get roots(): MapTree { @@ -98,13 +99,6 @@ export class ObjectForest implements IEditableForest { return this.roots.fields.size === 0; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public clone(_: TreeStoredSchemaSubscription, anchors: AnchorSet): ObjectForest { return new ObjectForest(anchors, this.additionalAsserts, this.roots); } @@ -133,7 +127,7 @@ export class ObjectForest implements IEditableForest { * This is required for each change since there may be app facing change event handlers which create cursors. */ const preEdit = (): void => { - this.events.emit("beforeChange"); + this.#events.emit("beforeChange"); assert( this.currentCursors.has(cursor), 0x995 /* missing visitor cursor while editing */, @@ -168,7 +162,7 @@ export class ObjectForest implements IEditableForest { public create(content: ProtoNodes, destination: FieldKey): void { preEdit(); this.forest.add(content, destination); - this.forest.events.emit("afterRootFieldCreated", destination); + this.forest.#events.emit("afterRootFieldCreated", destination); } public attach(source: FieldKey, count: number, destination: PlaceIndex): void { preEdit(); diff --git a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts index b0f92028a0ea..13960fd2a229 100644 --- a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts @@ -52,7 +52,7 @@ export class SchemaSummarizer implements Summarizable { collabWindow: CollabWindow, ) { this.codec = makeSchemaCodec(options); - this.schema.on("afterSchemaChange", () => { + this.schema.events.on("afterSchemaChange", () => { // Invalidate the cache, as we need to regenerate the blob if the schema changes // We are assuming that schema changes from remote ops are valid, as we are in a summarization context. this.schemaIndexLastChangedSeq = collabWindow.getCurrentSeq(); diff --git a/packages/dds/tree/src/feature-libraries/sequence-field/compose.ts b/packages/dds/tree/src/feature-libraries/sequence-field/compose.ts index 8f56e7efe08d..48804affe916 100644 --- a/packages/dds/tree/src/feature-libraries/sequence-field/compose.ts +++ b/packages/dds/tree/src/feature-libraries/sequence-field/compose.ts @@ -582,7 +582,7 @@ export class ComposeQueue { } } - private dequeueBase(length: number = Infinity): ComposeMarks { + private dequeueBase(length: number = Number.POSITIVE_INFINITY): ComposeMarks { const baseMark = this.baseMarks.dequeueUpTo(length); const movedChanges = getMovedChangesFromMark(this.moveEffects, baseMark); if (movedChanges !== undefined) { @@ -593,7 +593,7 @@ export class ComposeQueue { return { baseMark, newMark }; } - private dequeueNew(length: number = Infinity): ComposeMarks { + private dequeueNew(length: number = Number.POSITIVE_INFINITY): ComposeMarks { const newMark = this.newMarks.dequeueUpTo(length); const baseMark = createNoopMark(newMark.count, undefined, getInputCellId(newMark)); diff --git a/packages/dds/tree/src/feature-libraries/sequence-field/markListFactory.ts b/packages/dds/tree/src/feature-libraries/sequence-field/markListFactory.ts index 91d0b5eb6855..1d01004ee089 100644 --- a/packages/dds/tree/src/feature-libraries/sequence-field/markListFactory.ts +++ b/packages/dds/tree/src/feature-libraries/sequence-field/markListFactory.ts @@ -42,7 +42,7 @@ export class MarkListFactory { if (prev !== undefined && prev.type === mark.type) { const merged = tryMergeMarks(prev, mark); if (merged !== undefined) { - this.list.splice(this.list.length - 1, 1, merged); + this.list.splice(-1, 1, merged); return; } } diff --git a/packages/dds/tree/src/index.ts b/packages/dds/tree/src/index.ts index 57d698104fea..5f123fb5a167 100644 --- a/packages/dds/tree/src/index.ts +++ b/packages/dds/tree/src/index.ts @@ -58,8 +58,12 @@ export { rollback, type ForestOptions, getBranch, - type TreeBranch, + type BranchableTree, type TreeBranchFork, + independentInitializedView, + type ViewContent, + TreeAlpha, + independentView, } from "./shared-tree/index.js"; export { @@ -142,8 +146,13 @@ export { // Beta APIs TreeBeta, type TreeChangeEventsBeta, + type VerboseTreeNode, + type EncodeOptions, + type ParseOptions, + type VerboseTree, extractPersistedSchema, comparePersistedSchema, + type ConciseTree, // Back to normal types type JsonTreeSchema, type JsonSchemaId, @@ -161,19 +170,38 @@ export { getJsonSchema, type LazyItem, type Unenforced, + type SimpleNodeSchemaBase, + type SimpleTreeSchema, + type SimpleNodeSchema, + type SimpleFieldSchema, + type SimpleLeafNodeSchema, + type SimpleMapNodeSchema, + type SimpleArrayNodeSchema, + type SimpleObjectNodeSchema, + normalizeAllowedTypes, + getSimpleSchema, + numberSchema, + stringSchema, + booleanSchema, + handleSchema, + nullSchema, type ReadonlyArrayNode, type InsertableTreeNodeFromAllowedTypes, type Input, + type TreeBranch, + type TreeBranchEvents, + asTreeViewAlpha, } from "./simple-tree/index.js"; export { SharedTree, configuredSharedTree, } from "./treeFactory.js"; -export type { - ICodecOptions, - JsonValidator, - SchemaValidationFunction, +export { + type ICodecOptions, + type JsonValidator, + type SchemaValidationFunction, + FluidClientVersion, } from "./codec/index.js"; export { noopValidator } from "./codec/index.js"; export { typeboxValidator } from "./external-utilities/index.js"; diff --git a/packages/dds/tree/src/internalTypes.ts b/packages/dds/tree/src/internalTypes.ts index 08f873edcf20..f6ef03b4580b 100644 --- a/packages/dds/tree/src/internalTypes.ts +++ b/packages/dds/tree/src/internalTypes.ts @@ -13,7 +13,6 @@ export type { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, diff --git a/packages/dds/tree/src/packageVersion.ts b/packages/dds/tree/src/packageVersion.ts index 69ac03908d83..c2b2b9a6e68f 100644 --- a/packages/dds/tree/src/packageVersion.ts +++ b/packages/dds/tree/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/tree"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/dds/tree/src/shared-tree-core/branch.ts b/packages/dds/tree/src/shared-tree-core/branch.ts index f7d53664543f..aaac692a3a8f 100644 --- a/packages/dds/tree/src/shared-tree-core/branch.ts +++ b/packages/dds/tree/src/shared-tree-core/branch.ts @@ -21,7 +21,7 @@ import { tagRollbackInverse, type RebaseStatsWithDuration, } from "../core/index.js"; -import { EventEmitter, type Listenable } from "../events/index.js"; +import { createEmitter, type Listenable } from "../events/index.js"; import { TransactionStack } from "./transactionStack.js"; import { fail } from "../util/index.js"; @@ -175,10 +175,9 @@ export interface BranchTrimmingEvents { /** * A branch of changes that can be applied to a SharedTree. */ -export class SharedTreeBranch< - TEditor extends ChangeFamilyEditor, - TChange, -> extends EventEmitter> { +export class SharedTreeBranch { + readonly #events = createEmitter>(); + public readonly events: Listenable> = this.#events; public readonly editor: TEditor; private readonly transactions = new TransactionStack(); /** @@ -222,12 +221,11 @@ export class SharedTreeBranch< keyof RebaseStatsWithDuration >, ) { - super(); this.editor = this.changeFamily.buildEditor(mintRevisionTag, (change) => this.apply(change), ); this.unsubscribeBranchTrimmer = branchTrimmer?.on("ancestryTrimmed", (commit) => { - this.emit("ancestryTrimmed", commit); + this.#events.emit("ancestryTrimmed", commit); }); } @@ -267,9 +265,9 @@ export class SharedTreeBranch< newCommits: [newHead], } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [taggedChange.change, newHead]; } @@ -290,7 +288,7 @@ export class SharedTreeBranch< const onDisposeUnSubscribes: (() => void)[] = []; const onForkUnSubscribe = onForkTransitive(this, (fork) => { forks.add(fork); - onDisposeUnSubscribes.push(fork.on("dispose", () => forks.delete(fork))); + onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork))); }); this.transactions.push(this.head.revision, () => { forks.forEach((fork) => fork.dispose()); @@ -298,7 +296,7 @@ export class SharedTreeBranch< onForkUnSubscribe(); }); this.editor.enterTransaction(); - this.emit("transactionStarted", this.transactions.size === 1); + this.#events.emit("transactionStarted", this.transactions.size === 1); } /** @@ -315,7 +313,7 @@ export class SharedTreeBranch< const [startCommit, commits] = this.popTransaction(); this.editor.exitTransaction(); - this.emit("transactionCommitted", this.transactions.size === 0); + this.#events.emit("transactionCommitted", this.transactions.size === 0); if (commits.length === 0) { return undefined; } @@ -336,9 +334,9 @@ export class SharedTreeBranch< newCommits: [newHead], } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [commits, newHead]; } @@ -356,9 +354,9 @@ export class SharedTreeBranch< const [startCommit, commits] = this.popTransaction(); this.editor.exitTransaction(); - this.emit("transactionAborted", this.transactions.size === 0); + this.#events.emit("transactionAborted", this.transactions.size === 0); if (commits.length === 0) { - this.emit("transactionRolledBack", this.transactions.size === 0); + this.#events.emit("transactionRolledBack", this.transactions.size === 0); return [undefined, []]; } @@ -384,10 +382,10 @@ export class SharedTreeBranch< removedCommits: commits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = startCommit; - this.emit("afterChange", changeEvent); - this.emit("transactionRolledBack", this.transactions.size === 0); + this.#events.emit("afterChange", changeEvent); + this.#events.emit("transactionRolledBack", this.transactions.size === 0); return [change, commits]; } @@ -435,7 +433,7 @@ export class SharedTreeBranch< this.mintRevisionTag, this.branchTrimmer, ); - this.emit("fork", fork); + this.#events.emit("fork", fork); return fork; } @@ -483,9 +481,9 @@ export class SharedTreeBranch< newCommits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newSourceHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return rebaseResult; } @@ -529,9 +527,9 @@ export class SharedTreeBranch< newCommits: sourceCommits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = rebaseResult.newSourceHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [change, sourceCommits]; } @@ -585,7 +583,7 @@ export class SharedTreeBranch< this.unsubscribeBranchTrimmer?.(); this.disposed = true; - this.emit("dispose"); + this.#events.emit("dispose"); } private assertNotDisposed(): void { @@ -594,20 +592,22 @@ export class SharedTreeBranch< } /** - * Registers an event listener that fires when the given forkable object forks. + * Registers an event listener that fires when the given branch forks. * The listener will also fire when any of those forks fork, and when those forks of forks fork, and so on. - * @param forkable - an object that emits an event when it is forked + * @param branch - the branch that will be listened to for forks * @param onFork - the fork event listener * @returns a function which when called will deregister all registrations (including transitive) created by this function. * The deregister function has undefined behavior if called more than once. */ -export function onForkTransitive void }>>( - forkable: T, +// Branches are invariant over TChange +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function onForkTransitive>( + branch: T, onFork: (fork: T) => void, ): () => void { const offs: (() => void)[] = []; offs.push( - forkable.on("fork", (fork) => { + branch.events.on("fork", (fork: T) => { offs.push(onForkTransitive(fork, onFork)); onFork(fork); }), diff --git a/packages/dds/tree/src/shared-tree-core/editManager.ts b/packages/dds/tree/src/shared-tree-core/editManager.ts index c089855969f1..e4cd45958064 100644 --- a/packages/dds/tree/src/shared-tree-core/editManager.ts +++ b/packages/dds/tree/src/shared-tree-core/editManager.ts @@ -191,7 +191,7 @@ export class EditManager< this.telemetryEventBatcher, ); - this.localBranch.on("afterChange", (event) => { + this.localBranch.events.on("afterChange", (event) => { if (event.type === "append") { for (const commit of event.newCommits) { this.localCommits.push(commit); @@ -223,19 +223,19 @@ export class EditManager< private registerBranch(branch: SharedTreeBranch): void { this.trackBranch(branch); // Whenever the branch is rebased, update our record of its base trunk commit - const offBeforeRebase = branch.on("beforeChange", (args) => { + const offBeforeRebase = branch.events.on("beforeChange", (args) => { if (args.type === "replace" && getChangeReplaceType(args) === "rebase") { this.untrackBranch(branch); } }); - const offAfterRebase = branch.on("afterChange", (args) => { + const offAfterRebase = branch.events.on("afterChange", (args) => { if (args.type === "replace" && getChangeReplaceType(args) === "rebase") { this.trackBranch(branch); this.trimTrunk(); } }); // When the branch is disposed, update our branch set and trim the trunk - const offDispose = branch.on("dispose", () => { + const offDispose = branch.events.on("dispose", () => { this.untrackBranch(branch); this.trimTrunk(); offBeforeRebase(); @@ -424,15 +424,24 @@ export class EditManager< } else { Reflect.defineProperty(commit, "change", { get: () => - assert(false, "Should not access 'change' property of an evicted commit"), + assert( + false, + 0xa5e /* Should not access 'change' property of an evicted commit */, + ), }); Reflect.defineProperty(commit, "revision", { get: () => - assert(false, "Should not access 'revision' property of an evicted commit"), + assert( + false, + 0xa5f /* Should not access 'revision' property of an evicted commit */, + ), }); Reflect.defineProperty(commit, "parent", { get: () => - assert(false, "Should not access 'parent' property of an evicted commit"), + assert( + false, + 0xa60 /* Should not access 'parent' property of an evicted commit */, + ), }); return { delete: true }; } @@ -491,7 +500,10 @@ export class EditManager< const trunk = getPathFromBase(this.trunk.getHead(), oldestCommitInCollabWindow).map( (c) => { - assert(c !== this.trunkBase, "Serialized trunk should not include the trunk base"); + assert( + c !== this.trunkBase, + 0xa61 /* Serialized trunk should not include the trunk base */, + ); const metadata = this.trunkMetadata.get(c.revision) ?? fail("Expected metadata for trunk commit"); const commit: SequencedCommit = { @@ -522,7 +534,7 @@ export class EditManager< commits: branchPath.map((c) => { assert( c !== this.trunkBase, - "Serialized branch should not include the trunk base", + 0xa62 /* Serialized branch should not include the trunk base */, ); const commit: Commit = { change: c.change, @@ -593,7 +605,7 @@ export class EditManager< if (id === undefined) { assert( trunkCommitOrTrunkBase === this.trunkBase, - "Commit must be either be on the trunk or be the trunk base", + 0xa63 /* Commit must be either be on the trunk or be the trunk base */, ); return minimumPossibleSequenceId; } @@ -652,7 +664,7 @@ export class EditManager< assert( sequenceNumber >= // This is ">=", not ">" because changes in the same batch will have the same sequence number (this.sequenceMap.maxKey()?.sequenceNumber ?? minimumPossibleSequenceNumber), - "Attempted to sequence change with an outdated sequence number", + 0xa64 /* Attempted to sequence change with an outdated sequence number */, ); const commitsSequenceNumber = this.getBatch(sequenceNumber); diff --git a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts index 29c787798db9..8ce343b4c837 100644 --- a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts +++ b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts @@ -183,16 +183,16 @@ export class SharedTreeCore this.mintRevisionTag, rebaseLogger, ); - this.editManager.localBranch.on("transactionStarted", () => { + this.editManager.localBranch.events.on("transactionStarted", () => { this.commitEnricher.startNewTransaction(); }); - this.editManager.localBranch.on("transactionAborted", () => { + this.editManager.localBranch.events.on("transactionAborted", () => { this.commitEnricher.abortCurrentTransaction(); }); - this.editManager.localBranch.on("transactionCommitted", () => { + this.editManager.localBranch.events.on("transactionCommitted", () => { this.commitEnricher.commitCurrentTransaction(); }); - this.editManager.localBranch.on("beforeChange", (change) => { + this.editManager.localBranch.events.on("beforeChange", (change) => { // Ensure that any previously prepared commits that have not been sent are purged. this.commitEnricher.purgePreparedCommits(); if (this.detachedRevision !== undefined) { @@ -219,7 +219,7 @@ export class SharedTreeCore this.commitEnricher.prepareCommit(change.newCommits[0] ?? oob(), true); } }); - this.editManager.localBranch.on("afterChange", (change) => { + this.editManager.localBranch.events.on("afterChange", (change) => { if (this.getLocalBranch().isTransacting()) { // We do not submit ops for changes that are part of a transaction. return; diff --git a/packages/dds/tree/src/shared-tree/independentView.ts b/packages/dds/tree/src/shared-tree/independentView.ts new file mode 100644 index 000000000000..0ef5c6fef873 --- /dev/null +++ b/packages/dds/tree/src/shared-tree/independentView.ts @@ -0,0 +1,176 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { IFluidHandle } from "@fluidframework/core-interfaces"; +import { assert } from "@fluidframework/core-utils/internal"; +import { + type IIdCompressor, + createIdCompressor, +} from "@fluidframework/id-compressor/internal"; +import type { ICodecOptions } from "../codec/index.js"; +import { + type RevisionTag, + RevisionTagCodec, + TreeStoredSchemaRepository, + initializeForest, + type ITreeCursorSynchronous, + mapCursorField, +} from "../core/index.js"; +import { + createNodeKeyManager, + makeFieldBatchCodec, + makeSchemaCodec, + type FieldBatchEncodingContext, + defaultSchemaPolicy, + chunkTree, + defaultChunkPolicy, + TreeCompressionStrategy, +} from "../feature-libraries/index.js"; +// eslint-disable-next-line import/no-internal-modules +import type { Format } from "../feature-libraries/schema-index/format.js"; +import type { + TreeViewConfiguration, + ImplicitFieldSchema, + TreeViewAlpha, +} from "../simple-tree/index.js"; +import type { JsonCompatibleReadOnly, JsonCompatible } from "../util/index.js"; +import { + buildConfiguredForest, + defaultSharedTreeOptions, + type ForestOptions, +} from "./sharedTree.js"; +import { createTreeCheckout } from "./treeCheckout.js"; +import { SchematizingSimpleTreeView } from "./schematizingTreeView.js"; + +/** + * Create an uninitialized {@link TreeView} that is not tied to any {@link ITree} instance. + * + * @remarks + * Such a view can never experience collaboration or be persisted to to a Fluid Container. + * + * This can be useful for testing, as well as use-cases like working on local files instead of documents stored in some Fluid service. + * @alpha + */ +export function independentView( + config: TreeViewConfiguration, + options: ForestOptions & { idCompressor?: IIdCompressor | undefined }, +): TreeViewAlpha { + const idCompressor: IIdCompressor = options.idCompressor ?? createIdCompressor(); + const mintRevisionTag = (): RevisionTag => idCompressor.generateCompressedId(); + const revisionTagCodec = new RevisionTagCodec(idCompressor); + const schema = new TreeStoredSchemaRepository(); + const forest = buildConfiguredForest( + options.forest ?? defaultSharedTreeOptions.forest, + schema, + idCompressor, + ); + const checkout = createTreeCheckout(idCompressor, mintRevisionTag, revisionTagCodec, { + forest, + schema, + }); + const out: TreeViewAlpha = new SchematizingSimpleTreeView( + checkout, + config, + createNodeKeyManager(idCompressor), + ); + return out; +} +/** + * Create an initialized {@link TreeView} that is not tied to any {@link ITree} instance. + * + * @remarks + * Such a view can never experience collaboration or be persisted to to a Fluid Container. + * + * This can be useful for testing, as well as use-cases like working on local files instead of documents stored in some Fluid service. + * @alpha + */ +export function independentInitializedView( + config: TreeViewConfiguration, + options: ForestOptions & ICodecOptions, + content: ViewContent, +): TreeViewAlpha { + const idCompressor: IIdCompressor = content.idCompressor; + const mintRevisionTag = (): RevisionTag => idCompressor.generateCompressedId(); + const revisionTagCodec = new RevisionTagCodec(idCompressor); + + const fieldBatchCodec = makeFieldBatchCodec(options, 1); + const schemaCodec = makeSchemaCodec(options); + + const schema = new TreeStoredSchemaRepository(schemaCodec.decode(content.schema as Format)); + const forest = buildConfiguredForest( + options.forest ?? defaultSharedTreeOptions.forest, + schema, + idCompressor, + ); + + const context: FieldBatchEncodingContext = { + encodeType: TreeCompressionStrategy.Compressed, + idCompressor, + originatorId: idCompressor.localSessionId, // Is this right? If so, why is is needed? + schema: { schema, policy: defaultSchemaPolicy }, + }; + + const fieldCursors = fieldBatchCodec.decode(content.tree as JsonCompatibleReadOnly, context); + assert(fieldCursors.length === 1, 0xa5b /* must have exactly 1 field in batch */); + // Checked above. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const cursors = fieldCursorToNodesCursors(fieldCursors[0]!); + + initializeForest(forest, cursors, revisionTagCodec, idCompressor, false); + + const checkout = createTreeCheckout(idCompressor, mintRevisionTag, revisionTagCodec, { + forest, + schema, + }); + const out: TreeViewAlpha = new SchematizingSimpleTreeView( + checkout, + config, + createNodeKeyManager(idCompressor), + ); + return out; +} + +function fieldCursorToNodesCursors( + fieldCursor: ITreeCursorSynchronous, +): ITreeCursorSynchronous[] { + return mapCursorField(fieldCursor, copyNodeCursor); +} + +/** + * TODO: avoid needing this, or optimize it. + */ +function copyNodeCursor(cursor: ITreeCursorSynchronous): ITreeCursorSynchronous { + const copy = chunkTree(cursor, { + policy: defaultChunkPolicy, + idCompressor: undefined, + }).cursor(); + copy.enterNode(0); + return copy; +} + +/** + * The portion of SharedTree data typically persisted by the container. + * Usable with {@link independentInitializedView} to create a {@link TreeView} + * without loading a container. + * @alpha + */ +export interface ViewContent { + /** + * Compressed tree from {@link TreeAlpha.exportCompressed}. + * @remarks + * This is an owning reference: + * consumers of this content might modify this data in place (for example when applying edits) to avoid copying. + */ + readonly tree: JsonCompatible; + /** + * Persisted schema from {@link extractPersistedSchema}. + */ + readonly schema: JsonCompatible; + /** + * IIdCompressor which will be used to decompress any compressed identifiers in `tree` + * as well as for any other identifiers added to the view. + */ + readonly idCompressor: IIdCompressor; +} diff --git a/packages/dds/tree/src/shared-tree/index.ts b/packages/dds/tree/src/shared-tree/index.ts index be2f40a90ab1..40ae259e06d8 100644 --- a/packages/dds/tree/src/shared-tree/index.ts +++ b/packages/dds/tree/src/shared-tree/index.ts @@ -26,7 +26,7 @@ export { type CheckoutEvents, type ITransaction, type ITreeCheckoutFork, - type TreeBranch, + type BranchableTree, type TreeBranchFork, } from "./treeCheckout.js"; @@ -46,3 +46,11 @@ export { type RunTransaction, rollback, } from "./treeApi.js"; + +export { TreeAlpha } from "./treeApiAlpha.js"; + +export { + independentInitializedView, + type ViewContent, + independentView, +} from "./independentView.js"; diff --git a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts index c602b42ac012..bcf1d14dc851 100644 --- a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts +++ b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts @@ -46,13 +46,19 @@ import { type ReadableField, type ReadSchema, type UnsafeUnknownSchema, + type TreeBranch, + type TreeBranchEvents, } from "../simple-tree/index.js"; import { Breakable, breakingClass, disposeSymbol, type WithBreakable } from "../util/index.js"; import { canInitialize, ensureSchema, initialize } from "./schematizeTree.js"; import type { ITreeCheckout, TreeCheckout } from "./treeCheckout.js"; import { CheckoutFlexTreeView } from "./checkoutFlexTreeView.js"; -import { HydratedContext, SimpleContextSlot } from "../simple-tree/index.js"; +import { + HydratedContext, + SimpleContextSlot, + areImplicitFieldSchemaEqual, +} from "../simple-tree/index.js"; /** * Creating multiple tree views from the same checkout is not supported. This slot is used to detect if one already * exists and error if creating a second. @@ -65,7 +71,7 @@ export const ViewSlot = anchorSlot>(); @breakingClass export class SchematizingSimpleTreeView< in out TRootSchema extends ImplicitFieldSchema | UnsafeUnknownSchema, -> implements TreeViewAlpha, WithBreakable +> implements TreeBranch, TreeViewAlpha, WithBreakable { /** * The view is set to undefined when this object is disposed or the view schema does not support viewing the document's stored schema. @@ -79,9 +85,9 @@ export class SchematizingSimpleTreeView< */ private currentCompatibility: SchemaCompatibilityStatus | undefined; private readonly schemaPolicy: SchemaPolicy; - public readonly events: Listenable & - IEmitter & - HasListeners = createEmitter(); + public readonly events: Listenable & + IEmitter & + HasListeners = createEmitter(); private readonly viewSchema: ViewSchema; @@ -128,12 +134,19 @@ export class SchematizingSimpleTreeView< this.update(); this.unregisterCallbacks.add( - this.checkout.events.on("commitApplied", (data, getRevertible) => - this.events.emit("commitApplied", data, getRevertible), - ), + this.checkout.events.on("changed", (data, getRevertible) => { + this.events.emit("changed", data, getRevertible); + this.events.emit("commitApplied", data, getRevertible); + }), ); } + public hasRootSchema( + schema: TSchema, + ): this is TreeViewAlpha { + return areImplicitFieldSchemaEqual(this.rootFieldSchema, schema); + } + public get schema(): ReadSchema { return this.config.schema; } @@ -284,7 +297,7 @@ export class SchematizingSimpleTreeView< new HydratedContext(this.rootFieldSchema.allowedTypeSet, view.context), ); - const unregister = this.checkout.storedSchema.on("afterSchemaChange", () => { + const unregister = this.checkout.storedSchema.events.on("afterSchemaChange", () => { unregister(); this.unregisterCallbacks.delete(unregister); view[disposeSymbol](); @@ -294,7 +307,7 @@ export class SchematizingSimpleTreeView< this.view = undefined; this.checkout.forest.anchors.slots.delete(SimpleContextSlot); - const unregister = this.checkout.storedSchema.on("afterSchemaChange", () => { + const unregister = this.checkout.storedSchema.events.on("afterSchemaChange", () => { unregister(); this.unregisterCallbacks.delete(unregister); this.update(); @@ -342,6 +355,10 @@ export class SchematizingSimpleTreeView< this.checkout.forest.anchors.slots.delete(ViewSlot); this.currentCompatibility = undefined; this.onDispose?.(); + if (this.checkout.isBranch && !this.checkout.disposed) { + // All (non-main) branches are 1:1 with views, so if a user manually disposes a view, we should also dispose the checkout/branch. + this.checkout.dispose(); + } } public get root(): ReadableField { @@ -369,6 +386,34 @@ export class SchematizingSimpleTreeView< newRoot as InsertableContent | undefined, ); } + + // #region Branching + + public fork(): ReturnType & TreeViewAlpha { + return this.checkout.branch().viewWith(this.config); + } + + public merge(context: TreeBranch, disposeMerged = true): void { + this.checkout.merge(getCheckout(context), disposeMerged); + } + + public rebaseOnto(context: TreeBranch): void { + getCheckout(context).rebase(this.checkout); + } + + // #endregion Branching +} + +/** + * Get the {@link TreeCheckout} associated with a given {@link TreeBranch}. + * @remarks Currently, all contexts are also {@link SchematizingSimpleTreeView}s. + * Other checkout implementations (e.g. not associated with a view) may be supported in the future. + */ +function getCheckout(context: TreeBranch): TreeCheckout { + if (context instanceof SchematizingSimpleTreeView) { + return context.checkout; + } + throw new UsageError("Unsupported context implementation"); } /** diff --git a/packages/dds/tree/src/shared-tree/sharedTree.ts b/packages/dds/tree/src/shared-tree/sharedTree.ts index 962f969b4e16..ad7bf615973a 100644 --- a/packages/dds/tree/src/shared-tree/sharedTree.ts +++ b/packages/dds/tree/src/shared-tree/sharedTree.ts @@ -68,7 +68,7 @@ import type { SharedTreeEditBuilder } from "./sharedTreeEditBuilder.js"; import { type CheckoutEvents, type TreeCheckout, - type TreeBranch, + type BranchableTree, createTreeCheckout, } from "./treeCheckout.js"; import { breakingClass, throwIfBroken } from "../util/index.js"; @@ -352,34 +352,36 @@ export class SharedTree } /** - * Get a {@link TreeBranch} from a {@link ITree}. + * Get a {@link BranchableTree} from a {@link ITree}. * @remarks The branch can be used for "version control"-style coordination of edits on the tree. * @privateRemarks This function will be removed if/when the branching API becomes public, * but it (or something like it) is necessary in the meantime to prevent the alpha types from being exposed as public. * @alpha + * @deprecated This API is superseded by {@link TreeBranch}, which should be used instead. */ -export function getBranch(tree: ITree): TreeBranch; +export function getBranch(tree: ITree): BranchableTree; /** - * Get a {@link TreeBranch} from a {@link TreeView}. + * Get a {@link BranchableTree} from a {@link TreeView}. * @remarks The branch can be used for "version control"-style coordination of edits on the tree. * Branches are currently an unstable "alpha" API and are subject to change in the future. * @privateRemarks This function will be removed if/when the branching API becomes public, * but it (or something like it) is necessary in the meantime to prevent the alpha types from being exposed as public. * @alpha + * @deprecated This API is superseded by {@link TreeBranch}, which should be used instead. */ export function getBranch( view: TreeViewAlpha, -): TreeBranch; +): BranchableTree; export function getBranch( treeOrView: ITree | TreeViewAlpha, -): TreeBranch { +): BranchableTree { assert( treeOrView instanceof SharedTree || treeOrView instanceof SchematizingSimpleTreeView, 0xa48 /* Unsupported implementation */, ); const checkout: TreeCheckout = treeOrView.checkout; // This cast is safe so long as TreeCheckout supports all the operations on the branch interface. - return checkout as unknown as TreeBranch; + return checkout as unknown as BranchableTree; } /** diff --git a/packages/dds/tree/src/shared-tree/treeApi.ts b/packages/dds/tree/src/shared-tree/treeApi.ts index b711de05ebbc..81de4c7a62dd 100644 --- a/packages/dds/tree/src/shared-tree/treeApi.ts +++ b/packages/dds/tree/src/shared-tree/treeApi.ts @@ -478,10 +478,10 @@ function runTransactionInCheckout( let result: ReturnType; try { result = transaction(); - } catch (e) { + } catch (error) { // If the transaction has an unhandled error, abort and rollback the transaction but continue to propagate the error. checkout.transaction.abort(); - throw e; + throw error; } if (result === rollback) { diff --git a/packages/dds/tree/src/shared-tree/treeApiAlpha.ts b/packages/dds/tree/src/shared-tree/treeApiAlpha.ts new file mode 100644 index 000000000000..fb08f9474cbe --- /dev/null +++ b/packages/dds/tree/src/shared-tree/treeApiAlpha.ts @@ -0,0 +1,401 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { assert } from "@fluidframework/core-utils/internal"; +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +import { UsageError } from "@fluidframework/telemetry-utils/internal"; +import type { IFluidHandle } from "@fluidframework/core-interfaces"; +import type { IIdCompressor } from "@fluidframework/id-compressor"; + +import { + getKernel, + type TreeNode, + type Unhydrated, + TreeBeta, + tryGetSchema, + createFromCursor, + createFromInsertable, + cursorFromInsertable, + FieldKind, + normalizeFieldSchema, + type ImplicitFieldSchema, + type InsertableField, + type TreeFieldFromImplicitField, + type TreeLeafValue, + type UnsafeUnknownSchema, + conciseFromCursor, + type ConciseTree, + applySchemaToParserOptions, + cursorFromVerbose, + verboseFromCursor, + type ParseOptions, + type VerboseTree, + type VerboseTreeNode, + toStoredSchema, + type EncodeOptions, + extractPersistedSchema, + TreeViewConfiguration, + type TreeBranch, +} from "../simple-tree/index.js"; +import { fail, type JsonCompatible } from "../util/index.js"; +import { noopValidator, type FluidClientVersion, type ICodecOptions } from "../codec/index.js"; +import type { ITreeCursorSynchronous } from "../core/index.js"; +import { + cursorForMapTreeField, + defaultSchemaPolicy, + isTreeValue, + makeFieldBatchCodec, + mapTreeFromCursor, + TreeCompressionStrategy, + type FieldBatch, + type FieldBatchEncodingContext, +} from "../feature-libraries/index.js"; +import { independentInitializedView, type ViewContent } from "./independentView.js"; +import { SchematizingSimpleTreeView, ViewSlot } from "./schematizingTreeView.js"; + +/** + * Extensions to {@link Tree} and {@link TreeBeta} which are not yet stable. + * @sealed @alpha + */ +export const TreeAlpha: { + /** + * Retrieve the {@link TreeBranch | branch}, if any, for the given node. + * @param node - The node to query + * @remarks If the node has already been inserted into the tree, this will return the branch associated with that node's {@link TreeView | view}. + * Otherwise, it will return `undefined` (because the node has not yet been inserted and is therefore not part of a branch or view). + * + * This does not fork a new branch, but rather retrieves the _existing_ branch for the node. + * To create a new branch, use e.g. {@link TreeBranch.fork | `myBranch.fork()`}. + */ + branch(node: TreeNode): TreeBranch | undefined; + + /** + * Construct tree content that is compatible with the field defined by the provided `schema`. + * @param schema - The schema for what to construct. As this is an {@link ImplicitFieldSchema}, a {@link FieldSchema}, {@link TreeNodeSchema} or {@link AllowedTypes} array can be provided. + * @param data - The data used to construct the field content. + * @remarks + * When providing a {@link TreeNodeSchemaClass}, this is the same as invoking its constructor except that an unhydrated node can also be provided. + * This function exists as a generalization that can be used in other cases as well, + * such as when `undefined` might be allowed (for an optional field), or when the type should be inferred from the data when more than one type is possible. + * + * Like with {@link TreeNodeSchemaClass}'s constructor, it's an error to provide an existing node to this API. + * For that case, use {@link TreeBeta.clone}. + * @privateRemarks + * There should be a way to provide a source for defaulted identifiers, wither via this API or some way to add them to its output later. + */ + create( + schema: UnsafeUnknownSchema extends TSchema + ? ImplicitFieldSchema + : TSchema & ImplicitFieldSchema, + data: InsertableField, + ): Unhydrated< + TSchema extends ImplicitFieldSchema + ? TreeFieldFromImplicitField + : TreeNode | TreeLeafValue | undefined + >; + + /** + * Less type safe version of {@link TreeAlpha.create}, suitable for importing data. + * @remarks + * Due to {@link ConciseTree} relying on type inference from the data, its use is somewhat limited. + * This does not support {@link ConciseTree|ConciseTrees} with customized handle encodings or using persisted keys. + * Use "compressed" or "verbose" formats for more flexibility. + * + * When using this function, + * it is recommend to ensure your schema is unambiguous with {@link ITreeConfigurationOptions.preventAmbiguity}. + * If the schema is ambiguous, consider using {@link TreeAlpha.create} and {@link Unhydrated} nodes where needed, + * or using {@link TreeAlpha.(importVerbose:1)} and specify all types. + * + * Documented (and thus recoverable) error handling/reporting for this is not yet implemented, + * but for now most invalid inputs will throw a recoverable error. + */ + importConcise( + schema: UnsafeUnknownSchema extends TSchema + ? ImplicitFieldSchema + : TSchema & ImplicitFieldSchema, + data: ConciseTree | undefined, + ): Unhydrated< + TSchema extends ImplicitFieldSchema + ? TreeFieldFromImplicitField + : TreeNode | TreeLeafValue | undefined + >; + + /** + * Construct tree content compatible with a field defined by the provided `schema`. + * @param schema - The schema for what to construct. As this is an {@link ImplicitFieldSchema}, a {@link FieldSchema}, {@link TreeNodeSchema} or {@link AllowedTypes} array can be provided. + * @param data - The data used to construct the field content. See {@link TreeAlpha.(exportVerbose:1)}. + * @remarks + * This overload requires that any {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles} are encoded as actual {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles} in the input. + */ + importVerbose( + schema: TSchema, + data: VerboseTree | undefined, + options?: Partial>, + ): Unhydrated>; + + /** + * Construct tree content compatible with a field defined by the provided `schema`. + * @param schema - The schema for what to construct. As this is an {@link ImplicitFieldSchema}, a {@link FieldSchema}, {@link TreeNodeSchema} or {@link AllowedTypes} array can be provided. + * @param data - The data used to construct the field content. See {@link TreeAlpha.(exportVerbose:2)}. + * + * @typeparam THandle - How {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles} in the input `data` are encoded. + * A converter from this encoding to {@link @fluidframework/core-interfaces#IFluidHandle} is required in `options`. + */ + importVerbose( + schema: TSchema, + data: VerboseTree | undefined, + options: ParseOptions, + ): Unhydrated>; + + /** + * Same as {@link TreeAlpha.(exportConcise:2)}, except leaves handles as is. + */ + exportConcise( + node: TreeNode | TreeLeafValue, + options?: Partial>, + ): ConciseTree; + + /** + * Copy a snapshot of the current version of a TreeNode into a {@link ConciseTree}. + * + * @typeparam THandle - How {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles} in the output should be encoded. + * A converter from from {@link @fluidframework/core-interfaces#IFluidHandle} to this format is required in `options`. + */ + exportConcise( + node: TreeNode | TreeLeafValue, + options: EncodeOptions, + ): ConciseTree; + + /** + * Same {@link TreeAlpha.(exportVerbose:2)} except leaves handles as is. + */ + exportVerbose( + node: TreeNode | TreeLeafValue, + options?: Partial>, + ): VerboseTree; + + /** + * Copy a snapshot of the current version of a TreeNode into a JSON compatible plain old JavaScript Object. + * Verbose tree format, with explicit type on every node. + * + * @typeparam THandle - How {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles} in the output should be encoded. + * A converter from from {@link @fluidframework/core-interfaces#IFluidHandle} to this format is required in `options`. + * + * @remarks + * There are several cases this may be preferred to {@link TreeAlpha.(exportConcise:2)}: + * + * 1. When not using {@link ITreeConfigurationOptions.preventAmbiguity} (or when using `useStableFieldKeys`), `exportConcise` can produce ambiguous data (the type may be unclear on some nodes). + * `exportVerbose` will always be unambiguous and thus lossless. + * + * 2. When the data might be interpreted without access to the exact same view schema. In such cases, the types may be unknowable if not included. + * + * 3. When easy access to the type is desired. + */ + exportVerbose(node: TreeNode | TreeLeafValue, options: EncodeOptions): VerboseTree; + + /** + * Export the content of the provided `tree` in a compressed JSON compatible format. + * @remarks + * If an `idCompressor` is provided, it will be used to compress identifiers and thus will be needed to decompress the data. + * + * Always uses "stored" keys. + * See {@link EncodeOptions.useStoredKeys} for details. + * @privateRemarks + * TODO: It is currently not clear how to work with the idCompressors correctly in the package API. + * Better APIs should probably be provided as there is currently no way to associate an un-hydrated tree with an idCompressor, + * Nor get the correct idCompressor from a subtree to use when exporting it. + * Additionally using `createIdCompressor` to make an idCompressor is `@legacy` and thus not intended for use in this API surface. + * It would probably make more sense if we provided a way to get an idCompressor from the context of a node, + * which could be optional (and settable if missing) for un0hydrated nodes and required for hydrated ones. + * Add in a stable public APi for creating idCompressors, and a way to get them from a tree (without view schema), and that should address the anticipated use-cases. + */ + exportCompressed( + tree: TreeNode | TreeLeafValue, + options: { oldestCompatibleClient: FluidClientVersion; idCompressor?: IIdCompressor }, + ): JsonCompatible; + + /** + * Import data encoded by {@link TreeAlpha.exportCompressed}. + * + * @param schema - Schema with which the data must be compatible. This compatibility is not verified and must be ensured by the caller. + * @param compressedData - Data compressed by {@link TreeAlpha.exportCompressed}. + * @param options - If {@link TreeAlpha.exportCompressed} was given an `idCompressor`, it must be provided here. + * + * @remarks + * If the data could have been encoded with a different schema, consider encoding the schema along side it using {@link extractPersistedSchema} and loading the data using {@link independentView}. + * + * @privateRemarks + * This API could be improved: + * + * 1. It could validate that the schema is compatible, and return or throw an error in the invalid case (maybe add a "try" version). + * 2. A "try" version of this could return an error if the data isn't in a supported format (as determined by version and/or JasonValidator). + * 3. Requiring the caller provide a JsonValidator isn't the most friendly API. It might be practical to provide a default. + */ + importCompressed( + schema: TSchema, + compressedData: JsonCompatible, + options: { idCompressor?: IIdCompressor } & ICodecOptions, + ): Unhydrated>; +} = { + branch(node: TreeNode): TreeBranch | undefined { + const kernel = getKernel(node); + if (!kernel.isHydrated()) { + return undefined; + } + const view = kernel.anchorNode.anchorSet.slots.get(ViewSlot); + assert( + view instanceof SchematizingSimpleTreeView, + 0xa5c /* Unexpected view implementation */, + ); + return view; + }, + + create: createFromInsertable, + + importConcise( + schema: UnsafeUnknownSchema extends TSchema + ? ImplicitFieldSchema + : TSchema & ImplicitFieldSchema, + data: ConciseTree | undefined, + ): Unhydrated< + TSchema extends ImplicitFieldSchema + ? TreeFieldFromImplicitField + : TreeNode | TreeLeafValue | undefined + > { + return createFromInsertable( + schema, + data as InsertableField, + ) as Unhydrated< + TSchema extends ImplicitFieldSchema + ? TreeFieldFromImplicitField + : TreeNode | TreeLeafValue | undefined + >; + }, + + importVerbose( + schema: TSchema, + data: VerboseTree | undefined, + options?: Partial>, + ): Unhydrated> { + const config: ParseOptions = { + valueConverter: (input: VerboseTree) => { + return input as TreeLeafValue | VerboseTreeNode; + }, + ...options, + }; + // Create a config which is standalone, and thus can be used without having to refer back to the schema. + const schemalessConfig = applySchemaToParserOptions(schema, config); + if (data === undefined) { + const field = normalizeFieldSchema(schema); + if (field.kind !== FieldKind.Optional) { + throw new UsageError("undefined provided for non-optional field."); + } + return undefined as Unhydrated>; + } + const cursor = cursorFromVerbose(data, schemalessConfig); + return createFromCursor(schema, cursor); + }, + + exportConcise( + node: TreeNode | TreeLeafValue, + options?: Partial>, + ): ConciseTree { + const config: EncodeOptions = { + valueConverter(handle: IFluidHandle): T { + return handle as T; + }, + ...options, + }; + + const cursor = borrowCursorFromTreeNodeOrValue(node); + return conciseFromCursor(cursor, tryGetSchema(node) ?? fail("invalid input"), config); + }, + + exportVerbose( + node: TreeNode | TreeLeafValue, + options?: Partial>, + ): VerboseTree { + const config: EncodeOptions = { + valueConverter(handle: IFluidHandle): T { + return handle as T; + }, + ...options, + }; + + const cursor = borrowCursorFromTreeNodeOrValue(node); + return verboseFromCursor(cursor, tryGetSchema(node) ?? fail("invalid input"), config); + }, + + exportCompressed( + node: TreeNode | TreeLeafValue, + options: { + oldestCompatibleClient: FluidClientVersion; + idCompressor?: IIdCompressor; + }, + ): JsonCompatible { + const schema = tryGetSchema(node) ?? fail("invalid input"); + const format = versionToFormat[options.oldestCompatibleClient]; + const codec = makeFieldBatchCodec({ jsonValidator: noopValidator }, format); + const cursor = borrowFieldCursorFromTreeNodeOrValue(node); + const batch: FieldBatch = [cursor]; + // If none provided, create a compressor which will not compress anything. + const idCompressor = options.idCompressor ?? createIdCompressor(); + const context: FieldBatchEncodingContext = { + encodeType: TreeCompressionStrategy.Compressed, + idCompressor, + originatorId: idCompressor.localSessionId, // TODO: Why is this needed? + schema: { schema: toStoredSchema(schema), policy: defaultSchemaPolicy }, + }; + const result = codec.encode(batch, context); + return result; + }, + + importCompressed( + schema: TSchema, + compressedData: JsonCompatible, + options: { + idCompressor?: IIdCompressor; + } & ICodecOptions, + ): Unhydrated> { + const content: ViewContent = { + schema: extractPersistedSchema(schema), + tree: compressedData, + idCompressor: options.idCompressor ?? createIdCompressor(), + }; + const config = new TreeViewConfiguration({ schema }); + const view = independentInitializedView(config, options, content); + return TreeBeta.clone(view.root); + }, +}; + +function borrowCursorFromTreeNodeOrValue( + node: TreeNode | TreeLeafValue, +): ITreeCursorSynchronous { + if (isTreeValue(node)) { + return cursorFromInsertable( + tryGetSchema(node) ?? fail("missing schema"), + node, + ); + } + const kernel = getKernel(node); + const cursor = kernel.getOrCreateInnerNode().borrowCursor(); + return cursor; +} + +function borrowFieldCursorFromTreeNodeOrValue( + node: TreeNode | TreeLeafValue, +): ITreeCursorSynchronous { + const cursor = borrowCursorFromTreeNodeOrValue(node); + // TODO: avoid copy + const mapTree = mapTreeFromCursor(cursor); + return cursorForMapTreeField([mapTree]); +} + +const versionToFormat = { + v2_0: 1, + v2_1: 1, + v2_2: 1, + v2_3: 1, +}; diff --git a/packages/dds/tree/src/shared-tree/treeCheckout.ts b/packages/dds/tree/src/shared-tree/treeCheckout.ts index d8d4225220ae..54feae4ee834 100644 --- a/packages/dds/tree/src/shared-tree/treeCheckout.ts +++ b/packages/dds/tree/src/shared-tree/treeCheckout.ts @@ -85,18 +85,14 @@ export interface CheckoutEvents { afterBatch(): void; /** - * Fired when a revertible change has been made to this view. + * Fired when a change is made to the branch. Includes data about the change that is made which listeners + * can use to filter on changes they care about e.g. local vs remote changes. * - * Applications which subscribe to this event are expected to revert or discard revertibles they acquire (failure to do so will leak memory). - * The provided revertible is inherently bound to the view that raised the event, calling `revert` won't apply to forked views. - * - * @param revertible - The revertible that can be used to revert the change. - */ - - /** - * {@inheritdoc TreeViewEvents.commitApplied} + * @param data - information about the change + * @param getRevertible - a function provided that allows users to get a revertible for the change. If not provided, + * this change is not revertible. */ - commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + changed(data: CommitMetadata, getRevertible?: RevertibleFactory): void; } /** @@ -106,7 +102,7 @@ export interface CheckoutEvents { * Changes may be synchronized across branches via merge and rebase operations provided on the branch object. * @alpha @sealed */ -export interface TreeBranch extends ViewableTree { +export interface BranchableTree extends ViewableTree { /** * Spawn a new branch which is based off of the current state of this branch. * Any mutations of the new branch will not apply to this branch until the new branch is merged back into this branch via `merge()`. @@ -118,7 +114,7 @@ export interface TreeBranch extends ViewableTree { * @param view - a branch which was created by a call to `branch()`. * It is automatically disposed after the merge completes. * @remarks All ongoing transactions (if any) in `branch` will be committed before the merge. - * A "commitApplied" event and a corresponding {@link Revertible} will be emitted on this branch for each new change merged from 'branch'. + * A "changed" event and a corresponding {@link Revertible} will be emitted on this branch for each new change merged from 'branch'. */ merge(branch: TreeBranchFork): void; @@ -138,16 +134,16 @@ export interface TreeBranch extends ViewableTree { } /** - * A {@link TreeBranch | branch} of a SharedTree that has merged from another branch. + * A {@link BranchableTree | branch} of a SharedTree that has merged from another branch. * @remarks This branch should be disposed when it is no longer needed in order to free resources. * @alpha @sealed */ -export interface TreeBranchFork extends TreeBranch, IDisposable { +export interface TreeBranchFork extends BranchableTree, IDisposable { /** * Rebase the changes that have been applied to this branch over all the new changes in the given branch. * @param branch - Either the root branch or a branch that was created by a call to `branch()`. It is not modified by this operation. */ - rebaseOnto(branch: TreeBranch): void; + rebaseOnto(branch: BranchableTree): void; } /** @@ -285,6 +281,7 @@ export function createTreeCheckout( return new TreeCheckout( transaction, branch, + false, changeFamily, schema, forest, @@ -426,6 +423,8 @@ export class TreeCheckout implements ITreeCheckoutFork { public constructor( public readonly transaction: ITransaction, private readonly _branch: SharedTreeBranch, + /** True if and only if this checkout is for a forked branch and not the "main branch" of the tree. */ + public readonly isBranch: boolean, private readonly changeFamily: ChangeFamily, public readonly storedSchema: TreeStoredSchemaRepository, public readonly forest: IEditableForest, @@ -445,15 +444,15 @@ export class TreeCheckout implements ITreeCheckoutFork { private readonly breaker: Breakable = new Breakable("TreeCheckout"), ) { // when a transaction is started, take a snapshot of the current state of removed roots - _branch.on("transactionStarted", () => { + _branch.events.on("transactionStarted", () => { this.removedRootsSnapshots.push(this.removedRoots.clone()); }); // when a transaction is committed, the latest snapshot of removed roots can be discarded - _branch.on("transactionCommitted", () => { + _branch.events.on("transactionCommitted", () => { this.removedRootsSnapshots.pop(); }); // after a transaction is rolled back, revert removed roots back to the latest snapshot - _branch.on("transactionRolledBack", () => { + _branch.events.on("transactionRolledBack", () => { const snapshot = this.removedRootsSnapshots.pop(); assert(snapshot !== undefined, 0x9ae /* a snapshot for removed roots does not exist */); this.removedRoots = snapshot; @@ -463,7 +462,7 @@ export class TreeCheckout implements ITreeCheckoutFork { // For example, a bug in the editor might produce a malformed change object and thus applying the change to the forest will throw an error. // In such a case we will crash here, preventing the change from being added to the commit graph, and preventing `afterChange` from firing. // One important consequence of this is that we will not submit the op containing the invalid change, since op submissions happens in response to `afterChange`. - _branch.on("beforeChange", (event) => { + _branch.events.on("beforeChange", (event) => { if (event.change !== undefined) { const revision = event.type === "replace" @@ -510,7 +509,7 @@ export class TreeCheckout implements ITreeCheckoutFork { } } }); - _branch.on("afterChange", (event) => { + _branch.events.on("afterChange", (event) => { // The following logic allows revertibles to be generated for the change. // Currently only appends (including merges) and transaction commits are supported. if (!_branch.isTransacting()) { @@ -529,12 +528,12 @@ export class TreeCheckout implements ITreeCheckoutFork { : (onRevertibleDisposed?: (revertible: Revertible) => void) => { if (!withinEventContext) { throw new UsageError( - "Cannot get a revertible outside of the context of a commitApplied event.", + "Cannot get a revertible outside of the context of a changed event.", ); } if (this.revertibleCommitBranches.get(revision) !== undefined) { throw new UsageError( - "Cannot generate the same revertible more than once. Note that this can happen when multiple commitApplied event listeners are registered.", + "Cannot generate the same revertible more than once. Note that this can happen when multiple changed event listeners are registered.", ); } const revertibleCommits = this.revertibleCommitBranches; @@ -579,16 +578,19 @@ export class TreeCheckout implements ITreeCheckoutFork { }; let withinEventContext = true; - this.events.emit("commitApplied", { isLocal: true, kind }, getRevertible); + this.events.emit("changed", { isLocal: true, kind }, getRevertible); withinEventContext = false; } + } else if (event.type === "replace") { + // TODO: figure out how to plumb through commit kind info for remote changes + this.events.emit("changed", { isLocal: false, kind: CommitKind.Default }); } } }); // When the branch is trimmed, we can garbage collect any repair data whose latest relevant revision is one of the // trimmed revisions. - _branch.on("ancestryTrimmed", (revisions) => { + _branch.events.on("ancestryTrimmed", (revisions) => { this.withCombinedVisitor((visitor) => { revisions.forEach((revision) => { // get all the roots last created or used by the revision @@ -651,7 +653,7 @@ export class TreeCheckout implements ITreeCheckoutFork { } public get rootEvents(): Listenable { - return this.forest.anchors; + return this.forest.anchors.events; } public get editor(): ISharedTreeEditor { @@ -676,6 +678,7 @@ export class TreeCheckout implements ITreeCheckoutFork { return new TreeCheckout( transaction, branch, + true, this.changeFamily, storedSchema, forest, @@ -700,6 +703,10 @@ export class TreeCheckout implements ITreeCheckoutFork { !checkout.transaction.inProgress(), 0x9af /* A view cannot be rebased while it has a pending transaction */, ); + assert( + checkout.isBranch, + 0xa5d /* The main branch cannot be rebased onto another branch. */, + ); checkout._branch.rebaseOnto(this._branch); } @@ -727,7 +734,8 @@ export class TreeCheckout implements ITreeCheckoutFork { checkout.transaction.commit(); } this._branch.merge(checkout._branch); - if (disposeMerged) { + if (disposeMerged && checkout.isBranch) { + // Dispose the merged checkout unless it is the main branch. checkout[disposeSymbol](); } } diff --git a/packages/dds/tree/src/simple-tree/api/conciseTree.ts b/packages/dds/tree/src/simple-tree/api/conciseTree.ts index 679690bf35db..78b91643f8f3 100644 --- a/packages/dds/tree/src/simple-tree/api/conciseTree.ts +++ b/packages/dds/tree/src/simple-tree/api/conciseTree.ts @@ -23,6 +23,7 @@ import { getUnhydratedContext } from "../createContext.js"; * @privateRemarks * This can store all possible simple trees, * but it can not store all possible trees representable by our internal representations like FlexTree and JsonableTree. + * @alpha */ export type ConciseTree = | Exclude diff --git a/packages/dds/tree/src/simple-tree/api/create.ts b/packages/dds/tree/src/simple-tree/api/create.ts index 78657e20bb39..d2e962024b3f 100644 --- a/packages/dds/tree/src/simple-tree/api/create.ts +++ b/packages/dds/tree/src/simple-tree/api/create.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. */ -import type { IFluidHandle } from "@fluidframework/core-interfaces"; import { assert } from "@fluidframework/core-utils/internal"; import type { ITreeCursorSynchronous, SchemaAndPolicy } from "../../core/index.js"; import type { - TreeLeafValue, ImplicitFieldSchema, TreeFieldFromImplicitField, FieldSchema, FieldKind, UnsafeUnknownSchema, InsertableField, + TreeLeafValue, } from "../schemaTypes.js"; import { getOrCreateNodeFromInnerNode, @@ -32,13 +31,6 @@ import { import { isFieldInSchema } from "../../feature-libraries/index.js"; import { toStoredSchema } from "../toStoredSchema.js"; import { inSchemaOrThrow, mapTreeFromNodeData } from "../toMapTree.js"; -import { - applySchemaToParserOptions, - cursorFromVerbose, - type ParseOptions, - type VerboseTree, - type VerboseTreeNode, -} from "./verboseTree.js"; import { getUnhydratedContext } from "../createContext.js"; /** @@ -118,46 +110,6 @@ export function cursorFromInsertable< return cursorForMapTreeNode(mapTree); } -/** - * Construct tree content compatible with a field defined by the provided `schema`. - * @param schema - The schema for what to construct. As this is an {@link ImplicitFieldSchema}, a {@link FieldSchema}, {@link TreeNodeSchema} or {@link AllowedTypes} array can be provided. - * @param data - The data used to construct the field content. See `Tree.cloneToJSONVerbose`. - * @privateRemarks - * This could be exposed as a public `Tree.createFromVerbose` function. - */ -export function createFromVerbose( - schema: TSchema, - data: VerboseTreeNode | undefined, - options: ParseOptions, -): Unhydrated>; - -/** - * Construct tree content compatible with a field defined by the provided `schema`. - * @param schema - The schema for what to construct. As this is an {@link ImplicitFieldSchema}, a {@link FieldSchema}, {@link TreeNodeSchema} or {@link AllowedTypes} array can be provided. - * @param data - The data used to construct the field content. See `Tree.cloneToJSONVerbose`. - */ -export function createFromVerbose( - schema: TSchema, - data: VerboseTreeNode | undefined, - options?: Partial>, -): Unhydrated>; - -export function createFromVerbose( - schema: TSchema, - data: VerboseTreeNode | undefined, - options?: Partial>, -): Unhydrated> { - const config: ParseOptions = { - valueConverter: (input: VerboseTree) => { - return input as TreeLeafValue | VerboseTreeNode; - }, - ...options, - }; - const schemalessConfig = applySchemaToParserOptions(schema, config); - const cursor = cursorFromVerbose(data, schemalessConfig); - return createFromCursor(schema, cursor); -} - /** * Creates an unhydrated simple-tree field from a cursor in nodes mode. */ diff --git a/packages/dds/tree/src/simple-tree/api/customTree.ts b/packages/dds/tree/src/simple-tree/api/customTree.ts index a5a28026c90e..50c0c31f22f3 100644 --- a/packages/dds/tree/src/simple-tree/api/customTree.ts +++ b/packages/dds/tree/src/simple-tree/api/customTree.ts @@ -28,6 +28,7 @@ import { isObjectNodeSchema } from "../objectNodeTypes.js"; /** * Options for how to encode a tree. + * @alpha */ export interface EncodeOptions { /** diff --git a/packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts b/packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts index 75bfec1ace08..ca3f8e38b758 100644 --- a/packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts @@ -63,6 +63,8 @@ const simpleSchemaCache = new WeakMap(); * * @privateRemarks In the future, we may wish to move this to a more discoverable API location. * For now, while still an experimental API, it is surfaced as a free function. + * + * @internal */ export function getSimpleSchema(schema: ImplicitFieldSchema): SimpleTreeSchema { return getOrCreate(simpleSchemaCache, schema, () => toSimpleTreeSchema(schema)); diff --git a/packages/dds/tree/src/simple-tree/api/index.ts b/packages/dds/tree/src/simple-tree/api/index.ts index 8eb0ce304cad..f4d5c2737afd 100644 --- a/packages/dds/tree/src/simple-tree/api/index.ts +++ b/packages/dds/tree/src/simple-tree/api/index.ts @@ -13,6 +13,9 @@ export { type SchemaCompatibilityStatus, type ITreeConfigurationOptions, type TreeViewAlpha, + type TreeBranch, + type TreeBranchEvents, + asTreeViewAlpha, } from "./tree.js"; export { SchemaFactory, type ScopedSchemaName } from "./schemaFactory.js"; export type { @@ -24,9 +27,18 @@ export { enumFromStrings, singletonSchema, } from "./schemaCreationUtilities.js"; -export { treeNodeApi, type TreeNodeApi } from "./treeNodeApi.js"; -export { createFromInsertable, cursorFromInsertable } from "./create.js"; -export type { SimpleTreeSchema } from "./simpleSchema.js"; +export { treeNodeApi, type TreeNodeApi, tryGetSchema } from "./treeNodeApi.js"; +export { createFromInsertable, cursorFromInsertable, createFromCursor } from "./create.js"; +export type { + SimpleTreeSchema, + SimpleNodeSchema, + SimpleFieldSchema, + SimpleLeafNodeSchema, + SimpleMapNodeSchema, + SimpleArrayNodeSchema, + SimpleObjectNodeSchema, + SimpleNodeSchemaBase, +} from "./simpleSchema.js"; export { type JsonSchemaId, type JsonSchemaType, @@ -69,15 +81,18 @@ export type { InsertableTreeNodeFromAllowedTypesUnsafe, } from "./typesUnsafe.js"; -export type { - VerboseTreeNode, - ParseOptions, - VerboseTree, +export { + type VerboseTreeNode, + type ParseOptions, + type VerboseTree, + applySchemaToParserOptions, + cursorFromVerbose, + verboseFromCursor, } from "./verboseTree.js"; export type { EncodeOptions } from "./customTree.js"; -export type { ConciseTree } from "./conciseTree.js"; +export { type ConciseTree, conciseFromCursor } from "./conciseTree.js"; export { TreeBeta, type NodeChangedData, type TreeChangeEventsBeta } from "./treeApiBeta.js"; diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts index 1564f0fa95c4..8a8efaf86628 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactory.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactory.ts @@ -216,19 +216,45 @@ export class SchemaFactory< private readonly structuralTypes: Map = new Map(); /** - * Construct a SchemaFactory with a given scope. + * Construct a SchemaFactory with a given {@link SchemaFactory.scope|scope}. * @remarks - * There are no restrictions on mixing schema from different schema factories: - * this is encouraged when a single schema references schema from different libraries. - * If each library exporting schema picks its own globally unique scope for its SchemaFactory, - * then all schema an application might depend on, directly or transitively, - * will end up with a unique fully qualified name which is required to refer to it in persisted data and errors. - * - * @param scope - Prefix appended to the identifiers of all {@link TreeNodeSchema} produced by this builder. - * Use of [Reverse domain name notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) or a UUIDv4 is recommended to avoid collisions. - * You may opt out of using a scope by passing `undefined`, but note that this increases the risk of collisions. + * There are no restrictions on mixing schema from different schema factories. + * Typically each library will create one or more SchemaFactories and use them to define its schema. */ - public constructor(public readonly scope: TScope) {} + public constructor( + /** + * Prefix appended to the identifiers of all {@link TreeNodeSchema} produced by this builder. + * + * @remarks + * Generally each independently developed library + * (possibly a package, but could also be part of a package or multiple packages developed together) + * should get its own unique `scope`. + * Then each schema in the library get a name which is unique within the library. + * The scope and name are joined (with a period) to form the {@link TreeNodeSchemaCore.identifier|schema identifier}. + * Following this pattern allows a single application to depend on multiple libraries which define their own schema, and use them together in a single tree without risk of collisions. + * If a library logically contains sub-libraries with their own schema, they can be given a scope nested inside the parent scope, such as "ParentScope.ChildScope". + * + * To avoid collisions between the scopes of libraries + * it is recommended that the libraries use {@link https://en.wikipedia.org/wiki/Reverse_domain_name_notation | Reverse domain name notation} or a UUIDv4 for their scope. + * If this pattern is followed, application can safely use third party libraries without risk of the schema in them colliding. + * + * You may opt out of using a scope by passing `undefined`, but note that this increases the risk of collisions. + * + * @example + * Fluid Framework follows this pattern, placing the schema for the built in leaf types in the `com.fluidframework.leaf` scope. + * If Fluid Framework publishes more schema in the future, they would be under some other `com.fluidframework` scope. + * This ensures that any schema defined by any other library will not conflict with Fluid Framework's schema + * as long as the library uses the recommended patterns for how to scope its schema.. + * + * @example + * A library could generate a random UUIDv4, like `242c4397-49ed-47e6-8dd0-d5c3bc31778b` and use that as the scope. + * Note: do not use this UUID: a new one must be randomly generated when needed to ensure collision resistance. + * ```typescript + * const factory = new SchemaFactory("242c4397-49ed-47e6-8dd0-d5c3bc31778b"); + * ``` + */ + public readonly scope: TScope, + ) {} private scoped(name: Name): ScopedSchemaName { return ( @@ -778,6 +804,8 @@ export class SchemaFactory< name, allowedTypes as T & ImplicitAllowedTypes, true, + // Setting this (implicitlyConstructable) to true seems to work ok currently, but not for other node kinds. + // Supporting this could be fragile and might break other future changes, so it's being kept as false for now. false, ); @@ -785,24 +813,29 @@ export class SchemaFactory< ScopedSchemaName, NodeKind.Map, TreeMapNodeUnsafe & WithType, NodeKind.Map>, - { - /** - * Iterator for the iterable of content for this node. - * @privateRemarks - * Wrapping the constructor parameter for recursive arrays and maps in an inlined object type avoids (for unknown reasons) - * the following compile error when declaring the recursive schema: - * `Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.` - * To benefit from this without impacting the API, the definition of `Iterable` has been inlined as such an object. - * - * If this workaround is kept, ideally this comment would be deduplicated with the other instance of it. - * Unfortunately attempts to do this failed to avoid the compile error this was introduced to solve. - */ - [Symbol.iterator](): Iterator< - [string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe] - >; - }, - // Ideally this would be included, but doing so breaks recursive types. - // | RestrictiveStringRecord>, + | { + /** + * Iterator for the iterable of content for this node. + * @privateRemarks + * Wrapping the constructor parameter for recursive arrays and maps in an inlined object type avoids (for unknown reasons) + * the following compile error when declaring the recursive schema: + * `Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.` + * To benefit from this without impacting the API, the definition of `Iterable` has been inlined as such an object. + * + * If this workaround is kept, ideally this comment would be deduplicated with the other instance of it. + * Unfortunately attempts to do this failed to avoid the compile error this was introduced to solve. + */ + [Symbol.iterator](): Iterator< + [string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe] + >; + } + // Ideally this would be + // RestrictiveStringRecord>, + // but doing so breaks recursive types. + // Instead we do a less nice version: + | { + readonly [P in string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; + }, false, T, undefined diff --git a/packages/dds/tree/src/simple-tree/api/schemaFactoryRecursive.ts b/packages/dds/tree/src/simple-tree/api/schemaFactoryRecursive.ts index 204d1b2452de..cd0b095e0ed3 100644 --- a/packages/dds/tree/src/simple-tree/api/schemaFactoryRecursive.ts +++ b/packages/dds/tree/src/simple-tree/api/schemaFactoryRecursive.ts @@ -140,7 +140,7 @@ export type ValidateRecursiveSchema< ? Iterable<[string, InsertableTreeNodeFromImplicitAllowedTypes]> : unknown; }[T["kind"]], - // ImplicitlyConstructable: recursive types are not implicitly constructable. + // ImplicitlyConstructable: recursive types are currently not implicitly constructable. false, // Info: What's passed to the method to create the schema. Constraining these here should be about as effective as if the actual constraints existed on the actual method itself. { diff --git a/packages/dds/tree/src/simple-tree/api/simpleSchema.ts b/packages/dds/tree/src/simple-tree/api/simpleSchema.ts index 19fe582b6742..e13e1e3c372f 100644 --- a/packages/dds/tree/src/simple-tree/api/simpleSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/simpleSchema.ts @@ -5,11 +5,12 @@ import type { ValueSchema } from "../../core/index.js"; import type { NodeKind } from "../core/index.js"; -import type { FieldKind } from "../schemaTypes.js"; +import type { FieldKind, FieldSchemaMetadata } from "../schemaTypes.js"; /** * Base interface for all {@link SimpleNodeSchema} implementations. * + * @internal * @sealed */ export interface SimpleNodeSchemaBase { @@ -24,6 +25,7 @@ export interface SimpleNodeSchemaBase { /** * A {@link SimpleNodeSchema} for an object node. * + * @internal * @sealed */ export interface SimpleObjectNodeSchema extends SimpleNodeSchemaBase { @@ -36,6 +38,7 @@ export interface SimpleObjectNodeSchema extends SimpleNodeSchemaBase { @@ -51,6 +54,7 @@ export interface SimpleArrayNodeSchema extends SimpleNodeSchemaBase { @@ -66,6 +70,7 @@ export interface SimpleMapNodeSchema extends SimpleNodeSchemaBase /** * A {@link SimpleNodeSchema} for a leaf node. * + * @internal * @sealed */ export interface SimpleLeafNodeSchema extends SimpleNodeSchemaBase { @@ -81,6 +86,8 @@ export interface SimpleLeafNodeSchema extends SimpleNodeSchemaBase; /** - * {@inheritDoc FieldSchemaMetadata.description} + * {@inheritDoc FieldSchemaMetadata} */ - readonly description?: string | undefined; + readonly metadata?: FieldSchemaMetadata | undefined; } /** @@ -123,6 +131,7 @@ export interface SimpleFieldSchema { * @remarks Contains the complete set of schema {@link SimpleTreeSchema.definitions} required to resolve references, * which are represented inline with identifiers. * + * @internal * @sealed */ export interface SimpleTreeSchema extends SimpleFieldSchema { diff --git a/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts b/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts index b2656df80717..0e81be8260d1 100644 --- a/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/simpleSchemaToJsonSchema.ts @@ -150,8 +150,8 @@ function convertObjectNodeSchema(schema: SimpleObjectNodeSchema): JsonObjectNode }; // Don't include "description" property at all if it's not present in the input. - if (value.description !== undefined) { - output.description = value.description; + if (value.metadata?.description !== undefined) { + output.description = value.metadata.description; } properties[key] = output; diff --git a/packages/dds/tree/src/simple-tree/api/tree.ts b/packages/dds/tree/src/simple-tree/api/tree.ts index 6503816e3122..c693afb90f7f 100644 --- a/packages/dds/tree/src/simple-tree/api/tree.ts +++ b/packages/dds/tree/src/simple-tree/api/tree.ts @@ -30,6 +30,14 @@ import type { MakeNominal } from "../../util/index.js"; import { walkFieldSchema } from "../walkFieldSchema.js"; /** * A tree from which a {@link TreeView} can be created. + * + * @privateRemarks + * TODO: + * Add stored key versions of {@link TreeAlpha.(exportVerbose:2)}, {@link TreeAlpha.(exportConcise:2)} and {@link TreeAlpha.exportCompressed} here so tree content can be accessed without a view schema. + * Add exportSimpleSchema and exportJsonSchema methods (which should exactly match the concise format, and match the free functions for exporting view schema). + * Maybe rename "exportJsonSchema" to align on "concise" terminology. + * Ensure schema exporting APIs here align and reference APIs for exporting view schema to the same formats (which should include stored vs property key choice). + * Make sure users of independentView can use these export APIs (maybe provide a reference back to the ViewableTree from the TreeView to accomplish that). * @system @sealed @public */ export interface ViewableTree { @@ -49,7 +57,7 @@ export interface ViewableTree { * Only one schematized view may exist for a given ITree at a time. * If creating a second, the first must be disposed before calling `viewWith` again. * - * @privateRemarks + * * TODO: Provide a way to make a generic view schema for any document. * TODO: Support adapters for handling out-of-schema data. * @@ -243,7 +251,7 @@ export class TreeViewConfiguration< if (ambiguityErrors.length !== 0) { // Duplicate errors are common since when two types conflict, both orders error: const deduplicated = new Set(ambiguityErrors); - throw new UsageError(`Ambigious schema found:\n${[...deduplicated].join("\n")}`); + throw new UsageError(`Ambiguous schema found:\n${[...deduplicated].join("\n")}`); } // Eagerly perform this conversion to surface errors sooner. @@ -351,6 +359,84 @@ export function checkUnion(union: Iterable, errors: string[]): v } } +/** + * A collection of functionality associated with a (version-control-style) branch of a SharedTree. + * @remarks A `TreeBranch` allows for the {@link TreeBranch.fork | creation of branches} and for those branches to later be {@link TreeBranch.merge | merged}. + * + * The `TreeBranch` for a specific {@link TreeNode} may be acquired by calling `TreeAlpha.branch`. + * + * A branch does not necessarily know the schema of its SharedTree - to convert a branch to a {@link TreeViewAlpha | view with a schema}, use {@link TreeBranch.hasRootSchema | hasRootSchema()}. + * + * The branch associated directly with the {@link ITree | SharedTree} is the "main" branch, and all other branches fork (directly or transitively) from that main branch. + * @sealed @alpha + */ +export interface TreeBranch extends IDisposable { + /** + * Events for the branch + */ + readonly events: Listenable; + + /** + * Returns true if this branch has the given schema as its root schema. + * @remarks This is a type guard which allows this branch to become strongly typed as a {@link TreeViewAlpha | view} of the given schema. + * + * To succeed, the given schema must be invariant to the schema of the view - it must include exactly the same allowed types. + * For example, a schema of `Foo | Bar` will not match a view schema of `Foo`, and likewise a schema of `Foo` will not match a view schema of `Foo | Bar`. + * @example + * ```typescript + * if (branch.hasRootSchema(MySchema)) { + * const { root } = branch; // `branch` is now a TreeViewAlpha + * // ... + * } + * ``` + */ + hasRootSchema( + schema: TSchema, + ): this is TreeViewAlpha; + + /** + * Fork a new branch off of this branch which is based off of this branch's current state. + * @remarks Any changes to the tree on the new branch will not apply to this branch until the new branch is e.g. {@link TreeBranch.merge | merged} back into this branch. + * The branch should be disposed when no longer needed, either {@link TreeBranch.dispose | explicitly} or {@link TreeBranch.merge | implicitly when merging} into another branch. + */ + fork(): TreeBranch; + + /** + * Apply all the new changes on the given branch to this branch. + * @param branch - a branch which was created by a call to `branch()`. + * @param disposeMerged - whether or not to dispose `branch` after the merge completes. + * Defaults to true. + * The {@link TreeBranch | main branch} cannot be disposed - attempting to do so will have no effect. + * @remarks All ongoing transactions (if any) in `branch` will be committed before the merge. + */ + merge(branch: TreeBranch, disposeMerged?: boolean): void; + + /** + * Advance this branch forward such that all new changes on the target branch become part of this branch. + * @param branch - The branch to rebase onto. + * @remarks After rebasing, this branch will be "ahead" of the target branch, that is, its unique changes will have been recreated as if they happened after all changes on the target branch. + * This method may only be called on branches produced via {@link TreeBranch.fork | branch} - attempting to rebase the main branch will throw. + * + * Rebasing long-lived branches is important to avoid consuming memory unnecessarily. + * In particular, the SharedTree retains all sequenced changes made to the tree since the "most-behind" branch was created or last rebased. + * + * The {@link TreeBranch | main branch} cannot be rebased onto another branch - attempting to do so will throw an error. + */ + rebaseOnto(branch: TreeBranch): void; + + /** + * Dispose of this branch, cleaning up any resources associated with it. + * @param error - Optional error indicating the reason for the disposal, if the object was disposed as the result of an error. + * @remarks Branches can also be automatically disposed when {@link TreeBranch.merge | they are merged} into another branch. + * + * Disposing branches is important to avoid consuming memory unnecessarily. + * In particular, the SharedTree retains all sequenced changes made to the tree since the "most-behind" branch was created or last {@link TreeBranch.rebaseOnto | rebased}. + * + * The {@link TreeBranch | main branch} cannot be disposed - attempting to do so will have no effect. + */ + dispose(error?: Error): void; +} + /** * An editable view of a (version control style) branch of a shared tree based on some schema. * @@ -431,16 +517,22 @@ export interface TreeView extends ID /** * {@link TreeView} with proposed changes to the schema aware typing to allow use with `UnsafeUnknownSchema`. - * @alpha + * @sealed @alpha */ export interface TreeViewAlpha< in out TSchema extends ImplicitFieldSchema | UnsafeUnknownSchema, -> extends Omit>, "root" | "initialize"> { +> extends Omit>, "root" | "initialize">, + TreeBranch { get root(): ReadableField; set root(newRoot: InsertableField); + readonly events: Listenable; + initialize(content: InsertableField): void; + + // Override the base branch method to return a typed view rather than merely a branch. + fork(): ReturnType & TreeViewAlpha; } /** @@ -510,7 +602,7 @@ export interface SchemaCompatibilityStatus { * * @remarks * It's not necessary to check this field before calling {@link TreeView.initialize} in most scenarios; application authors typically know from - * context that they're in a flow which creates a new `SharedTree` and would like to initialize it. + * branch that they're in a flow which creates a new `SharedTree` and would like to initialize it. */ readonly canInitialize: boolean; @@ -519,6 +611,42 @@ export interface SchemaCompatibilityStatus { // - details about the differences between the stored and view schema sufficient for implementing "safe mismatch" policies } +/** + * Events for {@link TreeBranch}. + * @sealed @alpha + */ +export interface TreeBranchEvents { + /** + * The stored schema for the document has changed. + */ + schemaChanged(): void; + + /** + * Fired when a change is made to the branch. Includes data about the change that is made which listeners + * can use to filter on changes they care about (e.g. local vs. remote changes). + * + * @param data - information about the change + * @param getRevertible - a function that allows users to get a revertible for the change. If not provided, + * this change is not revertible. + */ + changed(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + + /** + * Fired when: + * - a local commit is applied outside of a transaction + * - a local transaction is committed + * + * The event is not fired when: + * - a local commit is applied within a transaction + * - a remote commit is applied + * + * @param data - information about the commit that was applied + * @param getRevertible - a function provided that allows users to get a revertible for the commit that was applied. If not provided, + * this commit is not revertible. + */ + commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void; +} + /** * Events for {@link TreeView}. * @sealed @public @@ -558,3 +686,13 @@ export interface TreeViewEvents { */ commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void; } + +/** + * Retrieve the {@link TreeViewAlpha | alpha API} for a {@link TreeView}. + * @alpha + */ +export function asTreeViewAlpha( + view: TreeView, +): TreeViewAlpha { + return view as TreeViewAlpha; +} diff --git a/packages/dds/tree/src/simple-tree/api/treeApiBeta.ts b/packages/dds/tree/src/simple-tree/api/treeApiBeta.ts index c4cec14f6f6c..34b2521b6eb2 100644 --- a/packages/dds/tree/src/simple-tree/api/treeApiBeta.ts +++ b/packages/dds/tree/src/simple-tree/api/treeApiBeta.ts @@ -12,14 +12,9 @@ import { type Unhydrated, type WithType, } from "../core/index.js"; -import type { - ImplicitFieldSchema, - TreeFieldFromImplicitField, - UnsafeUnknownSchema, -} from "../schemaTypes.js"; import { treeNodeApi } from "./treeNodeApi.js"; -import { createFromCursor, cursorFromInsertable } from "./create.js"; -import type { ITreeCursorSynchronous } from "../../core/index.js"; +import { createFromCursor } from "./create.js"; +import type { ImplicitFieldSchema, TreeFieldFromImplicitField } from "../schemaTypes.js"; /** * Data included for {@link TreeChangeEventsBeta.nodeChanged}. @@ -128,6 +123,25 @@ export const TreeBeta: { clone( node: TreeFieldFromImplicitField, ): TreeFieldFromImplicitField; + + // TODO: support more clone options + // /** + // * Like {@link TreeBeta.create}, except deeply clones existing nodes. + // * @remarks + // * This only clones the persisted data associated with a node. + // * Local state, such as properties added to customized schema classes, will not be cloned: + // * they will be initialized however they end up after running the constructor, just like if a remote client had inserted the same nodes. + // */ + // clone( + // original: TreeFieldFromImplicitField, + // options?: { + // /** + // * If set, all identifier's in the cloned tree (See {@link SchemaFactory.identifier}) will be replaced with new ones allocated using the default identifier allocation schema. + // * Otherwise any identifiers will be preserved as is. + // */ + // replaceIdentifiers?: true; + // }, + // ): TreeFieldFromImplicitField; } = { on, TNode extends TreeNode>( node: TNode, @@ -145,26 +159,9 @@ export const TreeBeta: { } const kernel = getKernel(node); - /* - * For unhydrated nodes, we can create a cursor by calling `cursorFromInsertable` because the node - * hasn't been inserted yet. We can then create a new node from the cursor. - */ - if (!kernel.isHydrated()) { - return createFromCursor( - kernel.schema, - cursorFromInsertable(kernel.schema, node), - ) as Unhydrated>; - } - - // For hydrated nodes, create a new cursor in the forest and then create a new node from the cursor. - const forest = kernel.context.flexContext.checkout.forest; - const cursor = forest.allocateCursor("tree.clone"); - forest.moveCursorToPath(kernel.anchorNode, cursor); - const clonedNode = createFromCursor( - kernel.schema, - cursor as ITreeCursorSynchronous, - ) as Unhydrated>; - cursor.free(); - return clonedNode; + const cursor = kernel.getOrCreateInnerNode().borrowCursor(); + return createFromCursor(kernel.schema, cursor) as Unhydrated< + TreeFieldFromImplicitField + >; }, }; diff --git a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts index 3154ed67d8e0..2211d21212d5 100644 --- a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts +++ b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts @@ -168,7 +168,7 @@ export const treeNodeApi: TreeNodeApi = { case "nodeChanged": { const nodeSchema = kernel.schema; if (isObjectNodeSchema(nodeSchema)) { - return kernel.on("childrenChangedAfterBatch", ({ changedFields }) => { + return kernel.events.on("childrenChangedAfterBatch", ({ changedFields }) => { const changedProperties = new Set( Array.from( changedFields, @@ -180,17 +180,17 @@ export const treeNodeApi: TreeNodeApi = { listener({ changedProperties }); }); } else if (nodeSchema.kind === NodeKind.Array) { - return kernel.on("childrenChangedAfterBatch", () => { + return kernel.events.on("childrenChangedAfterBatch", () => { listener({ changedProperties: undefined }); }); } else { - return kernel.on("childrenChangedAfterBatch", ({ changedFields }) => { + return kernel.events.on("childrenChangedAfterBatch", ({ changedFields }) => { listener({ changedProperties: changedFields }); }); } } case "treeChanged": { - return kernel.on("subtreeChangedAfterBatch", () => listener({})); + return kernel.events.on("subtreeChangedAfterBatch", () => listener({})); } default: throw new UsageError(`No event named ${JSON.stringify(eventName)}.`); diff --git a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts index eb758f4b63c8..da2f39f46651 100644 --- a/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts +++ b/packages/dds/tree/src/simple-tree/api/typesUnsafe.ts @@ -23,7 +23,7 @@ import type { TreeNodeSchema, TreeNodeSchemaCore, } from "../core/index.js"; -import type { TreeArrayNodeBase } from "../arrayNode.js"; +import type { TreeArrayNode } from "../arrayNode.js"; import type { FlexListToUnion, LazyItem } from "../flexList.js"; /* @@ -238,7 +238,8 @@ export type NodeBuilderDataUnsafe> = * @system @sealed @public */ export interface TreeArrayNodeUnsafe> - extends TreeArrayNodeBase< + extends TreeArrayNode< + TAllowedTypes, TreeNodeFromImplicitAllowedTypesUnsafe, InsertableTreeNodeFromImplicitAllowedTypesUnsafe > {} diff --git a/packages/dds/tree/src/simple-tree/api/verboseTree.ts b/packages/dds/tree/src/simple-tree/api/verboseTree.ts index 39fb1be5a30c..e86cef6aa945 100644 --- a/packages/dds/tree/src/simple-tree/api/verboseTree.ts +++ b/packages/dds/tree/src/simple-tree/api/verboseTree.ts @@ -53,6 +53,7 @@ import { getUnhydratedContext } from "../createContext.js"; * @privateRemarks * This can store all possible simple trees, * but it can not store all possible trees representable by our internal representations like FlexTree and JsonableTree. + * @alpha */ export type VerboseTree = | VerboseTreeNode @@ -82,6 +83,7 @@ export type VerboseTree = * Unlike `JsonableTree`, leaf nodes are not boxed into node objects, and instead have their schema inferred from the value. * Additionally, sequence fields can only occur on a node that has a single sequence field (with the empty key) * replicating the behavior of simple-tree ArrayNodes. + * @alpha */ export interface VerboseTreeNode { /** @@ -109,6 +111,7 @@ export interface VerboseTreeNode { /** * Options for how to interpret a `VerboseTree` when schema information is available. + * @alpha */ export interface ParseOptions { /** diff --git a/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts b/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts index 891bdc0f960e..f356e3d530f7 100644 --- a/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts +++ b/packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts @@ -42,8 +42,8 @@ export function toSimpleTreeSchema(schema: ImplicitFieldSchema): SimpleTreeSchem }; // Include the "description" property only if it's present on the input. - if (normalizedSchema.metadata?.description !== undefined) { - output.description = normalizedSchema.metadata.description; + if (normalizedSchema.metadata !== undefined) { + output.metadata = normalizedSchema.metadata; } return output; @@ -140,9 +140,9 @@ function fieldSchemaToSimpleSchema(schema: FieldSchema): SimpleFieldSchema { allowedTypes, }; - // Don't include "description" property at all if it's not present. - if (schema.metadata?.description !== undefined) { - result.description = schema.metadata.description; + // Don't include "metadata" property at all if it's not present. + if (schema.metadata !== undefined) { + result.metadata = schema.metadata; } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/dds/tree/src/simple-tree/arrayNode.ts b/packages/dds/tree/src/simple-tree/arrayNode.ts index 259403df25e9..01b9e1108732 100644 --- a/packages/dds/tree/src/simple-tree/arrayNode.ts +++ b/packages/dds/tree/src/simple-tree/arrayNode.ts @@ -46,6 +46,7 @@ import { } from "./core/index.js"; import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js"; import { getUnhydratedContext } from "./createContext.js"; +import type { Unenforced } from "./api/index.js"; /** * A covariant base type for {@link (TreeArrayNode:interface)}. @@ -61,15 +62,25 @@ export interface ReadonlyArrayNode Awaited> {} /** - * A generic array type, used to defined types like {@link (TreeArrayNode:interface)}. + * A {@link TreeNode} which implements 'readonly T[]' and the array mutation APIs. * - * @privateRemarks - * Inlining this into TreeArrayNode causes recursive array use to stop compiling. + * @typeParam TAllowedTypes - Schema for types which are allowed as members of this array. + * @typeParam T - Use Default: Do not specify. Type of values to read from the array. + * @typeParam TNew - Use Default: Do not specify. Type of values to write into the array. + * @typeParam TMoveFrom - Use Default: Do not specify. Type of node from which children can be moved into this array. * - * @system @sealed @public + * @sealed @public */ -export interface TreeArrayNodeBase - extends ReadonlyArrayNode { +export interface TreeArrayNode< + TAllowedTypes extends Unenforced = ImplicitAllowedTypes, + out T = [TAllowedTypes] extends [ImplicitAllowedTypes] + ? TreeNodeFromImplicitAllowedTypes + : TreeNodeFromImplicitAllowedTypes, + in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] + ? InsertableTreeNodeFromImplicitAllowedTypes + : InsertableTreeNodeFromImplicitAllowedTypes, + in TMoveFrom = ReadonlyArrayNode, +> extends ReadonlyArrayNode { /** * Inserts new item(s) at a specified location. * @param index - The index at which to insert `value`. @@ -369,20 +380,6 @@ export interface TreeArrayNodeBase; } -/** - * A {@link TreeNode} which implements 'readonly T[]' and the array mutation APIs. - * - * @typeParam TAllowedTypes - Schema for types which are allowed as members of this array. - * - * @sealed @public - */ -export interface TreeArrayNode< - TAllowedTypes extends ImplicitAllowedTypes = ImplicitAllowedTypes, -> extends TreeArrayNodeBase< - TreeNodeFromImplicitAllowedTypes, - InsertableTreeNodeFromImplicitAllowedTypes - > {} - /** * A {@link TreeNode} which implements 'readonly T[]' and the array mutation APIs. * @public @@ -1099,7 +1096,7 @@ export function arraySchema< // Since proxy reports this as a "non-configurable" property, it must exist on the underlying object used as the proxy target, not as an inherited property. // This should not get used as the proxy should intercept all use. Object.defineProperty(instance, "length", { - value: NaN, + value: Number.NaN, writable: true, enumerable: false, configurable: false, diff --git a/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts b/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts index 1a8331d2b1e9..74c6482758a4 100644 --- a/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts +++ b/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts @@ -17,7 +17,6 @@ import { assertFlexTreeEntityNotFreed, ContextSlot, flexTreeSlot, - isFlexTreeNode, isFreedSymbol, LazyEntity, TreeStatus, @@ -71,10 +70,15 @@ export function tryGetTreeNodeSchema(value: unknown): undefined | TreeNodeSchema } /** The {@link HydrationState} of a {@link TreeNodeKernel} before the kernel is hydrated */ -type UnhydratedState = Off; +interface UnhydratedState { + off: Off; + innerNode: UnhydratedFlexTreeNode; +} /** The {@link HydrationState} of a {@link TreeNodeKernel} after the kernel is hydrated */ interface HydratedState { + /** The flex node for this kernel (lazy - undefined if it has not yet been demanded) */ + innerNode?: FlexTreeNode; /** The {@link AnchorNode} that this node is associated with. */ anchorNode: AnchorNode; /** All {@link Off | event deregistration functions} that should be run when the kernel is disposed. */ @@ -86,16 +90,15 @@ type HydrationState = UnhydratedState | HydratedState; /** True if and only if the given {@link HydrationState} is post-hydration */ function isHydrated(state: HydrationState): state is HydratedState { - return typeof state === "object"; + return (state as Partial).anchorNode !== undefined; } /** * Contains state and an internal API for managing {@link TreeNode}s. * @remarks All {@link TreeNode}s have an associated kernel object. * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. - * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. */ -export class TreeNodeKernel implements Listenable { +export class TreeNodeKernel { private disposed = false; /** @@ -110,7 +113,7 @@ export class TreeNodeKernel implements Listenable { */ public generationNumber: number = 0; - #hydrationState: HydrationState = () => {}; + #hydrationState: HydrationState; /** * Events registered before hydration. @@ -133,7 +136,7 @@ export class TreeNodeKernel implements Listenable { public constructor( public readonly node: TreeNode, public readonly schema: TreeNodeSchema, - private innerNode: InnerNode, + innerNode: InnerNode, private readonly initialContext: Context, ) { assert(!treeNodeToKernel.has(node), 0xa1a /* only one kernel per node can be made */); @@ -144,9 +147,9 @@ export class TreeNodeKernel implements Listenable { mapTreeNodeToProxy.set(innerNode, node); // Register for change events from the unhydrated flex node. // These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node. - this.#hydrationState = innerNode.events.on( - "childrenChangedAfterBatch", - ({ changedFields }) => { + this.#hydrationState = { + innerNode, + off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { this.#unhydratedEvents.value.emit("childrenChangedAfterBatch", { changedFields, }); @@ -161,15 +164,16 @@ export class TreeNodeKernel implements Listenable { // This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node. n = n.parentField.parent.parent as UnhydratedFlexTreeNode | undefined; } - }, - ); + }), + }; } else { // Hydrated case + const { anchorNode } = innerNode; assert( - !innerNode.anchorNode.slots.has(proxySlot), + !anchorNode.slots.has(proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */, ); - this.hydrate(innerNode.anchorNode); + this.#hydrationState = this.createHydratedState(anchorNode); } } @@ -191,27 +195,11 @@ export class TreeNodeKernel implements Listenable { * Happens at most once for any given node. * Cleans up mappings to {@link UnhydratedFlexTreeNode} - it is assumed that they are no longer needed once the proxy has an anchor node. */ - public hydrate(anchorNode: AnchorNode): void { + private hydrate(anchorNode: AnchorNode): void { assert(!this.disposed, 0xa2a /* cannot hydrate a disposed node */); assert(!isHydrated(this.#hydrationState), 0xa2b /* hydration should only happen once */); - - // If the this node is raw and thus has a MapTreeNode, forget it: - if (this.innerNode instanceof UnhydratedFlexTreeNode) { - mapTreeNodeToProxy.delete(this.innerNode); - } - - // However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced. - anchorNode.slots.set(proxySlot, this.node); - this.#hydrationState = { - anchorNode, - offAnchorNode: new Set([ - anchorNode.on("afterDestroy", () => this.dispose()), - // TODO: this should be triggered on change even for unhydrated nodes. - anchorNode.on("childrenChanging", () => { - this.generationNumber += 1; - }), - ]), - }; + mapTreeNodeToProxy.delete(this.#hydrationState.innerNode); + this.#hydrationState = this.createHydratedState(anchorNode); // If needed, register forwarding emitters for events from before hydration if (this.#unhydratedEvents.evaluated) { @@ -221,13 +209,27 @@ export class TreeNodeKernel implements Listenable { this.#hydrationState.offAnchorNode.add( // Argument is forwarded between matching events, so the type should be correct. // eslint-disable-next-line @typescript-eslint/no-explicit-any - anchorNode.on(eventName, (arg: any) => events.emit(eventName, arg)), + anchorNode.events.on(eventName, (arg: any) => events.emit(eventName, arg)), ); } } } } + private createHydratedState(anchorNode: AnchorNode): HydratedState { + anchorNode.slots.set(proxySlot, this.node); + return { + anchorNode, + offAnchorNode: new Set([ + anchorNode.events.on("afterDestroy", () => this.dispose()), + // TODO: this should be triggered on change even for unhydrated nodes. + anchorNode.events.on("childrenChanging", () => { + this.generationNumber += 1; + }), + ]), + }; + } + public getStatus(): TreeStatus { if (this.disposed) { return TreeStatus.Deleted; @@ -248,13 +250,11 @@ export class TreeNodeKernel implements Listenable { return treeStatusFromAnchorCache(this.#hydrationState.anchorNode); } - public on(eventName: K, listener: KernelEvents[K]): Off { + public get events(): Listenable { // Retrieve the correct events object based on whether this node is pre or post hydration. - const events: Listenable = isHydrated(this.#hydrationState) - ? this.#hydrationState.anchorNode + return isHydrated(this.#hydrationState) + ? this.#hydrationState.anchorNode.events : this.#unhydratedEvents.value; - - return events.on(eventName, listener); } public dispose(): void { @@ -284,13 +284,12 @@ export class TreeNodeKernel implements Listenable { * Note that for "marinated" nodes, this FlexTreeNode exists and returns it: it does not return the MapTreeNode which is the current InnerNode. */ public getOrCreateInnerNode(allowFreed = false): InnerNode { - if (!(this.innerNode instanceof UnhydratedFlexTreeNode)) { - // Cooked case - return this.innerNode; + if (!isHydrated(this.#hydrationState)) { + return this.#hydrationState.innerNode; // Unhydrated case } - if (!isHydrated(this.#hydrationState)) { - return this.innerNode; + if (this.#hydrationState.innerNode !== undefined) { + return this.#hydrationState.innerNode; // Cooked case } // Marinated case -> cooked @@ -298,21 +297,23 @@ export class TreeNodeKernel implements Listenable { // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet const flexNode = anchorNode.slots.get(flexTreeSlot); if (flexNode !== undefined) { - this.innerNode = flexNode; - return flexNode; // If it does have a flex node, return it... - } // ...otherwise, the flex node must be created - const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); - const cursor = context.checkout.forest.allocateCursor("getFlexNode"); - context.checkout.forest.moveCursorToPath(anchorNode, cursor); - const newFlexNode = makeTree(context, cursor); - cursor.free(); - this.innerNode = newFlexNode; - // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode - anchorForgetters?.get(this.node)?.(); - if (!allowFreed) { - assertFlexTreeEntityNotFreed(newFlexNode); + // If the flex node already exists, use it... + this.#hydrationState.innerNode = flexNode; + } else { + // ...otherwise, the flex node must be created + const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); + const cursor = context.checkout.forest.allocateCursor("getFlexNode"); + context.checkout.forest.moveCursorToPath(anchorNode, cursor); + this.#hydrationState.innerNode = makeTree(context, cursor); + cursor.free(); + // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode + anchorForgetters?.get(this.node)?.(); + if (!allowFreed) { + assertFlexTreeEntityNotFreed(this.#hydrationState.innerNode); + } } - return newFlexNode; + + return this.#hydrationState.innerNode; } /** @@ -339,31 +340,26 @@ export class TreeNodeKernel implements Listenable { off(); }; anchorForgetters.set(this.node, forget); - const off = anchorNode.on("afterDestroy", forget); + const off = anchorNode.events.on("afterDestroy", forget); return anchorNode; } /** * Retrieves the InnerNode associated with the given target via {@link setInnerNode}, if any. * @remarks - * If `target` is a unhydrated node, returns its MapTreeNode. + * If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. * If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. - * If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. + * If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. */ public tryGetInnerNode(): InnerNode | undefined { - if (isFlexTreeNode(this.innerNode)) { - // Cooked case - return this.innerNode; - } - - if (!isHydrated(this.#hydrationState)) { - return this.innerNode; + if (isHydrated(this.#hydrationState)) { + return ( + this.#hydrationState.innerNode ?? + this.#hydrationState.anchorNode.slots.get(flexTreeSlot) + ); } - // Marinated case -> cooked - const anchorNode = this.#hydrationState.anchorNode; - // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet - return anchorNode.slots.get(flexTreeSlot); + return this.#hydrationState.innerNode; } } diff --git a/packages/dds/tree/src/simple-tree/core/unhydratedFlexTree.ts b/packages/dds/tree/src/simple-tree/core/unhydratedFlexTree.ts index 8f2b4845a1ae..7938be81b9e6 100644 --- a/packages/dds/tree/src/simple-tree/core/unhydratedFlexTree.ts +++ b/packages/dds/tree/src/simple-tree/core/unhydratedFlexTree.ts @@ -15,6 +15,7 @@ import { type FieldKindIdentifier, type FieldUpPath, forbiddenFieldKindIdentifier, + type ITreeCursorSynchronous, type MapTree, type SchemaPolicy, type TreeNodeSchemaIdentifier, @@ -39,6 +40,7 @@ import { type FlexFieldKind, FieldKinds, type SequenceFieldEditBuilder, + cursorForMapTreeNode, } from "../../feature-libraries/index.js"; import type { Context } from "./context.js"; import { createEmitter, type Listenable } from "../../events/index.js"; @@ -168,6 +170,10 @@ export class UnhydratedFlexTreeNode implements UnhydratedFlexTreeNode { return this.location; } + public borrowCursor(): ITreeCursorSynchronous { + return cursorForMapTreeNode(this.mapTree); + } + public tryGetField(key: FieldKey): UnhydratedFlexTreeField | undefined { const field = this.mapTree.fields.get(key); // Only return the field if it is not empty, in order to fulfill the contract of `tryGetField`. diff --git a/packages/dds/tree/src/simple-tree/index.ts b/packages/dds/tree/src/simple-tree/index.ts index 6c532b3ba3d2..a9d0c9bd609a 100644 --- a/packages/dds/tree/src/simple-tree/index.ts +++ b/packages/dds/tree/src/simple-tree/index.ts @@ -23,6 +23,7 @@ export { HydratedContext, SimpleContextSlot, getOrCreateInnerNode, + getKernel, } from "./core/index.js"; export { type ITree, @@ -50,7 +51,14 @@ export { type NodeChangedData, TreeBeta, type TreeChangeEventsBeta, + type SimpleNodeSchemaBase, type SimpleTreeSchema, + type SimpleNodeSchema, + type SimpleFieldSchema, + type SimpleLeafNodeSchema, + type SimpleMapNodeSchema, + type SimpleArrayNodeSchema, + type SimpleObjectNodeSchema, type JsonSchemaId, type JsonSchemaType, type JsonObjectNodeSchema, @@ -97,6 +105,15 @@ export { type TreeNodeSchemaNonClassUnsafe, type InsertableTreeNodeFromAllowedTypesUnsafe, type TreeViewAlpha, + type TreeBranch, + type TreeBranchEvents, + tryGetSchema, + applySchemaToParserOptions, + cursorFromVerbose, + verboseFromCursor, + conciseFromCursor, + createFromCursor, + asTreeViewAlpha, } from "./api/index.js"; export { type NodeFromSchema, @@ -116,11 +133,14 @@ export { type DefaultProvider, type FieldProps, normalizeFieldSchema, + areFieldSchemaEqual, + areImplicitFieldSchemaEqual, type ApplyKind, type FieldSchemaMetadata, type InsertableField, type Insertable, type UnsafeUnknownSchema, + normalizeAllowedTypes, type ApplyKindInput, type InsertableTreeNodeFromAllowedTypes, type Input, @@ -134,7 +154,6 @@ export { export { TreeArrayNode, IterableTreeArrayContent, - type TreeArrayNodeBase, type ReadonlyArrayNode, } from "./arrayNode.js"; export { diff --git a/packages/dds/tree/src/simple-tree/leafNodeSchema.ts b/packages/dds/tree/src/simple-tree/leafNodeSchema.ts index 5612de18c0f6..bc7e1f532a64 100644 --- a/packages/dds/tree/src/simple-tree/leafNodeSchema.ts +++ b/packages/dds/tree/src/simple-tree/leafNodeSchema.ts @@ -68,8 +68,27 @@ function makeLeaf( } // Leaf schema shared between all SchemaFactory instances. +/** + * @internal + */ export const stringSchema = makeLeaf("string", ValueSchema.String); + +/** + * @internal + */ export const numberSchema = makeLeaf("number", ValueSchema.Number); + +/** + * @internal + */ export const booleanSchema = makeLeaf("boolean", ValueSchema.Boolean); + +/** + * @internal + */ export const nullSchema = makeLeaf("null", ValueSchema.Null); + +/** + * @internal + */ export const handleSchema = makeLeaf("handle", ValueSchema.FluidHandle); diff --git a/packages/dds/tree/src/simple-tree/proxies.ts b/packages/dds/tree/src/simple-tree/proxies.ts index ef3bdd751c4e..67fc9aa546d1 100644 --- a/packages/dds/tree/src/simple-tree/proxies.ts +++ b/packages/dds/tree/src/simple-tree/proxies.ts @@ -172,7 +172,7 @@ function bindProxies(proxies: RootedProxyPaths[], forest: IForestSubscription): if (proxies.length > 0) { // Creating a new array emits one event per element in the array, so listen to the event once for each element let i = 0; - const off = forest.on("afterRootFieldCreated", (fieldKey) => { + const off = forest.events.on("afterRootFieldCreated", (fieldKey) => { // Non null asserting here because of the length check above // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (proxies[i]!.rootPath as Mutable).parentField = fieldKey; diff --git a/packages/dds/tree/src/simple-tree/schemaTypes.ts b/packages/dds/tree/src/simple-tree/schemaTypes.ts index be41b7122d4c..83979f815c25 100644 --- a/packages/dds/tree/src/simple-tree/schemaTypes.ts +++ b/packages/dds/tree/src/simple-tree/schemaTypes.ts @@ -13,6 +13,9 @@ import { brand, isReadonlyArray, type UnionToIntersection, + compareSets, + type requireTrue, + type areOnlyKeys, } from "../util/index.js"; import type { Unhydrated, @@ -28,6 +31,7 @@ import { isLazy, type FlexListToUnion, type LazyItem } from "./flexList.js"; /** * Returns true if the given schema is a {@link TreeNodeSchemaClass}, or otherwise false if it is a {@link TreeNodeSchemaNonClass}. + * @internal */ export function isTreeNodeSchemaClass< Name extends string, @@ -363,6 +367,8 @@ export function normalizeFieldSchema(schema: ImplicitFieldSchema): FieldSchema { * * @remarks Note: this must only be called after all required schemas have been declared, otherwise evaluation of * recursive schemas may fail. + * + * @internal */ export function normalizeAllowedTypes( types: ImplicitAllowedTypes, @@ -378,6 +384,87 @@ export function normalizeAllowedTypes( return normalized; } +/** + * Returns true if the given {@link ImplicitFieldSchema} are equivalent, otherwise false. + * @remarks Two ImplicitFieldSchema are considered equivalent if all of the following are true: + * 1. They have the same {@link FieldKind | kinds}. + * 2. They have {@link areFieldPropsEqual | equivalent FieldProps}. + * 3. They have the same exact set of allowed types. The allowed types must be (respectively) reference equal. + * + * For example, comparing an ImplicitFieldSchema that is a {@link TreeNodeSchema} to an ImplicitFieldSchema that is a {@link FieldSchema} + * will return true if they are the same kind, the FieldSchema has exactly one allowed type (the TreeNodeSchema), and they have equivalent FieldProps. + */ +export function areImplicitFieldSchemaEqual( + a: ImplicitFieldSchema, + b: ImplicitFieldSchema, +): boolean { + return areFieldSchemaEqual(normalizeFieldSchema(a), normalizeFieldSchema(b)); +} + +/** + * Returns true if the given {@link FieldSchema} are equivalent, otherwise false. + * @remarks Two FieldSchema are considered equivalent if all of the following are true: + * 1. They have the same {@link FieldKind | kinds}. + * 2. They have {@link areFieldPropsEqual | equivalent FieldProps}. + * 3. They have the same exact set of allowed types. The allowed types must be reference equal. + */ +export function areFieldSchemaEqual(a: FieldSchema, b: FieldSchema): boolean { + if (a === b) { + return true; + } + + if (a.kind !== b.kind) { + return false; + } + + if (!areFieldPropsEqual(a.props, b.props)) { + return false; + } + + return compareSets({ a: a.allowedTypeSet, b: b.allowedTypeSet }); +} + +/** + * Returns true if the given {@link FieldProps} are equivalent, otherwise false. + * @remarks FieldProps are considered equivalent if their keys and default providers are reference equal, and their metadata are {@link areMetadataEqual | equivalent}. + */ +function areFieldPropsEqual(a: FieldProps | undefined, b: FieldProps | undefined): boolean { + // If any new fields are added to FieldProps, this check will stop compiling as a reminder that this function needs to be updated. + type _keys = requireTrue>; + + if (a === b) { + return true; + } + + if (a?.key !== b?.key || a?.defaultProvider !== b?.defaultProvider) { + return false; + } + + if (!areMetadataEqual(a?.metadata, b?.metadata)) { + return false; + } + + return true; +} + +/** + * Returns true if the given {@link FieldSchemaMetadata} are equivalent, otherwise false. + * @remarks FieldSchemaMetadata are considered equivalent if their custom data and descriptions are (respectively) reference equal. + */ +function areMetadataEqual( + a: FieldSchemaMetadata | undefined, + b: FieldSchemaMetadata | undefined, +): boolean { + // If any new fields are added to FieldSchemaMetadata, this check will stop compiling as a reminder that this function needs to be updated. + type _keys = requireTrue>; + + if (a === b) { + return true; + } + + return a?.custom === b?.custom && a?.description === b?.description; +} + function evaluateLazySchema(value: LazyItem): TreeNodeSchema { const evaluatedSchema = isLazy(value) ? value() : value; if (evaluatedSchema === undefined) { @@ -389,9 +476,34 @@ function evaluateLazySchema(value: LazyItem): TreeNodeSchema { } /** - * Types allowed in a field. + * Types of {@link TreeNode|TreeNodes} or {@link TreeLeafValue|TreeLeafValues} allowed at a location in a tree. * @remarks + * Used by {@link TreeViewConfiguration} for the root and various kinds of {@link TreeNodeSchema} to specify their allowed child types. + * + * Use {@link SchemaFactory} to access leaf schema or declare new composite schema. + * * Implicitly treats a single type as an array of one type. + * + * Arrays of schema can be used to specify multiple types are allowed, which result in unions of those types in the Tree APIs. + * + * When saved into variables, avoid type-erasing the details, as doing so loses the compile time schema awareness of APIs derived from the types. + * + * When referring to types that are declared after the definition of the `ImplicitAllowedTypes`, the schema can be wrapped in a lambda to allow the forward reference. + * See {@link ValidateRecursiveSchema} for details on how to structure the `ImplicitAllowedTypes` instances when constructing recursive schema. + * + * @example Explicit use with strong typing + * ```typescript + * const sf = new SchemaFactory("myScope"); + * const childTypes = [sf.number, sf.string] as const satisfies ImplicitAllowedTypes; + * const config = new TreeViewConfiguration({ schema: childTypes }); + * ``` + * + * @example Forward reference + * ```typescript + * const sf = new SchemaFactory("myScope"); + * class A extends sf.array("example", [() => B]) {} + * class B extends sf.array("Inner", sf.number) {} + * ``` * @public */ export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; @@ -502,9 +614,7 @@ export type InsertableField = - TSchema extends ImplicitFieldSchema - ? TreeFieldFromImplicitField - : TreeLeafValue | TreeNode; + TreeFieldFromImplicitField>; /** * Adapter to remove {@link (UnsafeUnknownSchema:type)} from a schema type so it can be used with types for generating APIs for reading data. diff --git a/packages/dds/tree/src/simple-tree/treeNodeValid.ts b/packages/dds/tree/src/simple-tree/treeNodeValid.ts index 3d3b52b2e9ea..22b9ff86fecf 100644 --- a/packages/dds/tree/src/simple-tree/treeNodeValid.ts +++ b/packages/dds/tree/src/simple-tree/treeNodeValid.ts @@ -103,7 +103,7 @@ export abstract class TreeNodeValid extends TreeNode { // would not see the stored `constructorCached`, and the validation above against multiple derived classes would not work. // This is not just an alias of `this`, but a reference to the item in the prototype chain being walked, which happens to start at `this`. - // eslint-disable-next-line @typescript-eslint/no-this-alias + // eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment let schemaBase: typeof TreeNodeValid = this; while (!Object.prototype.hasOwnProperty.call(schemaBase, "constructorCached")) { schemaBase = Reflect.getPrototypeOf(schemaBase) as typeof TreeNodeValid; @@ -300,7 +300,7 @@ function formattedReference( object: unknown, config?: DevtoolsFormatter.ObjectConfig, ): DevtoolsFormatter.Item { - if (typeof object === "undefined") { + if (object === undefined) { return ["span", "undefined"]; } else if (object === "null") { return ["span", "null"]; diff --git a/packages/dds/tree/src/test/changesetWrapper.ts b/packages/dds/tree/src/test/changesetWrapper.ts index 0e900e5d4303..0d46959f52b9 100644 --- a/packages/dds/tree/src/test/changesetWrapper.ts +++ b/packages/dds/tree/src/test/changesetWrapper.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict } from "assert"; +import { strict } from "node:assert"; import { assert } from "@fluidframework/core-utils/internal"; import { type ChangeAtomIdMap, diff --git a/packages/dds/tree/src/test/codec/codec.spec.ts b/packages/dds/tree/src/test/codec/codec.spec.ts index fb790a73cd19..19c1660233ee 100644 --- a/packages/dds/tree/src/test/codec/codec.spec.ts +++ b/packages/dds/tree/src/test/codec/codec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { Type } from "@sinclair/typebox"; diff --git a/packages/dds/tree/src/test/cursorTestSuite.ts b/packages/dds/tree/src/test/cursorTestSuite.ts index d08c961a64b3..1cf41aae0ce0 100644 --- a/packages/dds/tree/src/test/cursorTestSuite.ts +++ b/packages/dds/tree/src/test/cursorTestSuite.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { CursorLocationType, diff --git a/packages/dds/tree/src/test/domains/json/citm.ts b/packages/dds/tree/src/test/domains/json/citm.ts index b0b9f2c25602..12448f617136 100644 --- a/packages/dds/tree/src/test/domains/json/citm.ts +++ b/packages/dds/tree/src/test/domains/json/citm.ts @@ -93,7 +93,7 @@ function increaseKeyspace( ) { const newKeyspace = [...keySpace]; if (multiplier <= 0) { - throw Error("multiplier must be greater than 0"); + throw new Error("multiplier must be greater than 0"); } const adjustedLength = Math.max(1, Math.floor(keySpace.length * multiplier)); const difference = adjustedLength - keySpace.length; diff --git a/packages/dds/tree/src/test/domains/json/jsDirectObject.bench.ts b/packages/dds/tree/src/test/domains/json/jsDirectObject.bench.ts index 47c71168ff7c..9a1af9ea02f5 100644 --- a/packages/dds/tree/src/test/domains/json/jsDirectObject.bench.ts +++ b/packages/dds/tree/src/test/domains/json/jsDirectObject.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { BenchmarkType, benchmark, isInPerformanceTestingMode } from "@fluid-tools/benchmark"; diff --git a/packages/dds/tree/src/test/domains/json/jsonCursor.bench.ts b/packages/dds/tree/src/test/domains/json/jsonCursor.bench.ts index 39ea8da72c94..13756ae2666a 100644 --- a/packages/dds/tree/src/test/domains/json/jsonCursor.bench.ts +++ b/packages/dds/tree/src/test/domains/json/jsonCursor.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { BenchmarkType, benchmark, isInPerformanceTestingMode } from "@fluid-tools/benchmark"; diff --git a/packages/dds/tree/src/test/editMinter.ts b/packages/dds/tree/src/test/editMinter.ts index 3103d0517919..907152db0428 100644 --- a/packages/dds/tree/src/test/editMinter.ts +++ b/packages/dds/tree/src/test/editMinter.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { DefaultChangeFamily, diff --git a/packages/dds/tree/src/test/events/events.spec.ts b/packages/dds/tree/src/test/events/events.spec.ts index 13bef28befad..ca1b31bc3a08 100644 --- a/packages/dds/tree/src/test/events/events.spec.ts +++ b/packages/dds/tree/src/test/events/events.spec.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; + +import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; import { EventEmitter, @@ -71,27 +73,31 @@ describe("EventEmitter", () => { assert(closed); }); - it("deregisters events", () => { + it("deregisters events via callback", () => { const emitter = createEmitter(); let error = false; - const deregister = emitter.on("close", (e: boolean) => { - error = e; - }); + const deregister = emitter.on("close", (e: boolean) => (error = e)); deregister(); emitter.emit("close", true); assert.strictEqual(error, false); }); - it("deregisters multiple events", () => { + it("deregisters events via off", () => { + const emitter = createEmitter(); + let error = false; + const listener = (e: boolean) => (error = e); + emitter.on("close", listener); + emitter.off("close", listener); + emitter.emit("close", true); + assert.strictEqual(error, false); + }); + + it("deregisters multiple events via callback", () => { const emitter = createEmitter(); let opened = false; let closed = false; - const deregisterOpen = emitter.on("open", () => { - opened = true; - }); - const deregisterClosed = emitter.on("close", () => { - closed = true; - }); + const deregisterOpen = emitter.on("open", () => (opened = true)); + const deregisterClosed = emitter.on("close", () => (closed = true)); deregisterOpen(); deregisterClosed(); emitter.emit("open"); @@ -102,6 +108,24 @@ describe("EventEmitter", () => { assert(!closed); }); + it("deregisters multiple events via off", () => { + const emitter = createEmitter(); + let opened = false; + let closed = false; + const listenerOpen = () => (opened = true); + const listenerClosed = () => (closed = true); + emitter.on("open", listenerOpen); + emitter.on("close", listenerClosed); + emitter.off("open", listenerOpen); + emitter.off("close", listenerClosed); + emitter.emit("open"); + assert(!opened); + assert(!closed); + emitter.emit("close", false); + assert(!opened); + assert(!closed); + }); + it("correctly handles multiple registrations for the same event", () => { const emitter = createEmitter(); let count: number; @@ -124,56 +148,127 @@ describe("EventEmitter", () => { assert.strictEqual(count, 0); }); - // Note: This behavior is not contractually required (see docs for `Listenable.on()`), - // but is tested here to check for changes or regressions. - it("correctly handles multiple registrations of the same listener", () => { + it("errors on multiple registrations of the same listener", () => { const emitter = createEmitter(); - let count: number; + let count = 0; const listener = () => (count += 1); - const off1 = emitter.on("open", listener); - const off2 = emitter.on("open", listener); - - count = 0; - emitter.emit("open"); - assert.strictEqual(count, 2); // Listener should be fired twice - - count = 0; - off1(); + emitter.on("open", listener); + assert.throws( + () => emitter.on("open", listener), + (e: Error) => validateAssertionError(e, /register.*twice.*open/), + ); + // If error is caught, the listener should still fire once for the first registration emitter.emit("open"); assert.strictEqual(count, 1); + }); - count = 0; - off2(); - emitter.emit("open"); - assert.strictEqual(count, 0); + it("includes symbol description in the error message on multiple registrations of the same listener", () => { + // This test ensures that symbol types are registered, error on double registration, and include the description of the symbol in the error message. + const eventSymbol = Symbol("TestEvent"); + const emitter = createEmitter<{ [eventSymbol]: () => void }>(); + const listener = () => {}; + emitter.on(eventSymbol, listener); + emitter.emit(eventSymbol); + assert.throws( + () => emitter.on(eventSymbol, listener), + (e: Error) => validateAssertionError(e, /register.*twice.*TestEvent/), + ); }); it("allows repeat deregistrations", () => { const emitter = createEmitter(); const deregister = emitter.on("open", () => {}); - const deregisterB = emitter.on("open", () => {}); + const listenerB = () => {}; + emitter.on("open", listenerB); deregister(); deregister(); - deregisterB(); - deregisterB(); + emitter.off("open", listenerB); + emitter.off("open", listenerB); }); - it("skips events adding during event", () => { + it("skips events added during event", () => { const emitter = createEmitter(); const log: string[] = []; - const unsubscribe = emitter.on("open", () => { + const off = emitter.on("open", () => { log.push("A"); emitter.on("open", () => { log.push("B"); }); }); emitter.emit("open"); - unsubscribe(); + off(); assert.deepEqual(log, ["A"]); emitter.emit("open"); assert.deepEqual(log, ["A", "B"]); }); + it("skips events removed during event", () => { + function test(remove: boolean, expected: string[]): void { + const log: string[] = []; + const emitter = createEmitter(); + emitter.on("open", () => { + log.push("A"); + if (remove) { + offB(); + } + }); + const offB = emitter.on("open", () => { + log.push("B"); + }); + emitter.emit("open"); + assert.deepEqual(log, expected); + } + + // Because event ordering is not guaranteed, we first test the control case to ensure that the second event fires after the first... + test(false, ["A", "B"]); + // ... and then test the same scenario but with the second event removed before it can fire. + test(true, ["A"]); + // If event ordering ever changes, this test will need to be updated to account for that. + }); + + it("fires the noListeners callback when the last listener is removed", () => { + let noListenersFired = false; + const emitter = createEmitter(() => (noListenersFired = true)); + const offA = emitter.on("open", () => {}); + const offB = emitter.on("open", () => {}); + assert.equal(noListenersFired, false); + offA(); + assert.equal(noListenersFired, false); + offB(); + assert.equal(noListenersFired, true); + }); + + it("reports whether or not it has listeners", () => { + const emitter = createEmitter(); + assert.equal(emitter.hasListeners(), false); + const offA = emitter.on("open", () => {}); + assert.equal(emitter.hasListeners(), true); + const offB = emitter.on("open", () => {}); + assert.equal(emitter.hasListeners(), true); + offB(); + assert.equal(emitter.hasListeners(), true); + offA(); + assert.equal(emitter.hasListeners(), false); + }); + + it("reports whether or not it has listeners for a given event", () => { + const emitter = createEmitter(); + assert.equal(emitter.hasListeners("open"), false); + assert.equal(emitter.hasListeners("close"), false); + const offA = emitter.on("open", () => {}); + assert.equal(emitter.hasListeners("open"), true); + assert.equal(emitter.hasListeners("close"), false); + const offB = emitter.on("close", () => {}); + assert.equal(emitter.hasListeners("open"), true); + assert.equal(emitter.hasListeners("close"), true); + offA(); + assert.equal(emitter.hasListeners("open"), false); + assert.equal(emitter.hasListeners("close"), true); + offB(); + assert.equal(emitter.hasListeners("open"), false); + assert.equal(emitter.hasListeners("close"), false); + }); + it("reentrant events", () => { const emitter = createEmitter(); const log: string[] = []; @@ -216,4 +311,19 @@ class MyCompositionClass implements Listenable { public on(eventName: K, listener: MyEvents[K]): () => void { return this.events.on(eventName, listener); } + + public off(eventName: K, listener: MyEvents[K]): void { + return this.events.off(eventName, listener); + } +} + +class MyExposingClass { + private readonly _events = createEmitter(); + + public readonly events: Listenable = this._events; + + private load() { + this._events.emit("loaded"); + const results: number[] = this._events.emitAndCollect("computed"); + } } diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/basicChunk.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/basicChunk.spec.ts index 36e9cd89f204..ff7026f38efc 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/basicChunk.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/basicChunk.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { EmptyKey, diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts index ea7fbbd6892c..cf559afe6acf 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; import { createIdCompressor } from "@fluidframework/id-compressor/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts index f806b51c9793..83c704eda246 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { CursorLocationType, diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/checkEncode.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/checkEncode.ts index 406ef67956ce..976994365523 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/checkEncode.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/checkEncode.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import type { JsonableTree } from "../../../../core/index.js"; // eslint-disable-next-line import/no-internal-modules diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkCodecUtilities.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkCodecUtilities.spec.ts index 394c81786d70..08e072d5d559 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkCodecUtilities.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkCodecUtilities.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { DiscriminatedUnionDispatcher } from "../../../../codec/index.js"; import { diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecoding.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecoding.spec.ts index f8b353f48255..05cdd543e191 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecoding.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecoding.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { compareArrays } from "@fluidframework/core-utils/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecodingGeneric.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecodingGeneric.spec.ts index 84bde9423f21..f87916ab8790 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecodingGeneric.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkDecodingGeneric.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type Static, Type } from "@sinclair/typebox"; diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkEncodingGeneric.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkEncodingGeneric.spec.ts index b296648ebac7..766b27ab4460 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkEncodingGeneric.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/chunkEncodingGeneric.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type Static, Type } from "@sinclair/typebox"; diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/compressedEncode.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/compressedEncode.spec.ts index c0f87fa5857f..f3309e038c66 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/compressedEncode.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/compressedEncode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { compareArrays } from "@fluidframework/core-utils/internal"; import { MockHandle } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/nodeShape.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/nodeShape.spec.ts index f0d94d37c522..6435b1b9e2b5 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/nodeShape.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/nodeShape.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import type { JsonableTree } from "../../../../core/index.js"; import { diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/schemaBasedEncoding.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/schemaBasedEncoding.spec.ts index e3d3430d01d3..bd53af920580 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/schemaBasedEncoding.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/schemaBasedEncoding.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import type { TreeFieldStoredSchema, diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/uncompressedEncode.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/uncompressedEncode.spec.ts index 1af77a4dff69..b28c883fb986 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/uncompressedEncode.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/codec/uncompressedEncode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { makeFieldBatchCodec, diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/fieldCursorTestUtilities.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/fieldCursorTestUtilities.ts index d1e7a3d66af9..6d86bdbbed0c 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/fieldCursorTestUtilities.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/fieldCursorTestUtilities.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type FieldUpPath, diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/uniformChunk.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/uniformChunk.spec.ts index 247df31c5b79..0fc4d16d8065 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/uniformChunk.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/uniformChunk.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { BenchmarkType, benchmark } from "@fluid-tools/benchmark"; diff --git a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts index 5c854dbb14ba..3c090b157fca 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type DeltaRoot, diff --git a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultFieldKinds.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultFieldKinds.spec.ts index 17d9fb895e22..ec9d0a559609 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultFieldKinds.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultFieldKinds.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { makeAnonChange } from "../../../core/index.js"; import { diff --git a/packages/dds/tree/src/test/feature-libraries/default-schema/schemaChecker.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-schema/schemaChecker.spec.ts index 522efa7ce350..92d5018e2cba 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-schema/schemaChecker.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-schema/schemaChecker.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; // Reaching into internal module just to test it import { diff --git a/packages/dds/tree/src/test/feature-libraries/deltaUtils.spec.ts b/packages/dds/tree/src/test/feature-libraries/deltaUtils.spec.ts index 931946d352df..85e689d4e136 100644 --- a/packages/dds/tree/src/test/feature-libraries/deltaUtils.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/deltaUtils.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { DeltaDetachedNodeId, diff --git a/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyField.spec.ts b/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyField.spec.ts index f9afe6d1922f..5989a8c56a10 100644 --- a/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyField.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyField.spec.ts @@ -5,7 +5,7 @@ /* eslint-disable import/no-internal-modules */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyNode.spec.ts b/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyNode.spec.ts index 998da8e19ae3..153286b97b73 100644 --- a/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyNode.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/flex-tree/lazyNode.spec.ts @@ -5,7 +5,7 @@ /* eslint-disable import/no-internal-modules */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type Anchor, diff --git a/packages/dds/tree/src/test/feature-libraries/flex-tree/utilities.spec.ts b/packages/dds/tree/src/test/feature-libraries/flex-tree/utilities.spec.ts index 1eda5fe70970..12ce0ff6577c 100644 --- a/packages/dds/tree/src/test/feature-libraries/flex-tree/utilities.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/flex-tree/utilities.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { AnchorSet, diff --git a/packages/dds/tree/src/test/feature-libraries/forest-summary/forestSummarizerCodec.spec.ts b/packages/dds/tree/src/test/feature-libraries/forest-summary/forestSummarizerCodec.spec.ts index 32d8ed6ff91c..fae413a86ee2 100644 --- a/packages/dds/tree/src/test/feature-libraries/forest-summary/forestSummarizerCodec.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/forest-summary/forestSummarizerCodec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/memoizedIdRangeAllocator.spec.ts b/packages/dds/tree/src/test/feature-libraries/memoizedIdRangeAllocator.spec.ts index 12f91878acc8..b954168db840 100644 --- a/packages/dds/tree/src/test/feature-libraries/memoizedIdRangeAllocator.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/memoizedIdRangeAllocator.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangesetLocalId, RevisionTag } from "../../core/index.js"; import { MemoizedIdRangeAllocator } from "../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/mitigatedChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/mitigatedChangeFamily.spec.ts index 0ca5402ad7c0..99bda1169727 100644 --- a/packages/dds/tree/src/test/feature-libraries/mitigatedChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/mitigatedChangeFamily.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangeFamily, diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/comparison.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/comparison.spec.ts index b116fdefe2da..fc81bd37e139 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/comparison.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/comparison.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type FieldKindIdentifier, @@ -508,7 +508,7 @@ function intoSimpleObject(obj: unknown): unknown { if (typeof obj !== "object" || obj === null) { return obj; } - if (obj instanceof Array) { + if (Array.isArray(obj)) { return Array.from(obj, intoSimpleObject); } if (obj instanceof Map) { diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/discrepancies.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/discrepancies.spec.ts index db125a079b8e..2009b1ce56aa 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/discrepancies.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/discrepancies.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import assert from "assert"; +import assert from "node:assert"; import { LeafNodeStoredSchema, MapNodeStoredSchema, @@ -17,7 +17,7 @@ import { import { defaultSchemaPolicy, FieldKinds, - getAllowedContentIncompatibilities, + getAllowedContentDiscrepancies, allowsRepoSuperset, isRepoSuperset as isRepoSupersetOriginal, type FlexFieldKind, @@ -117,7 +117,7 @@ describe("Schema Discrepancies", () => { root, ); - assert.deepEqual(getAllowedContentIncompatibilities(objectNodeSchema, mapNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(objectNodeSchema, mapNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "nodeKind", @@ -126,7 +126,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(mapNodeSchema, leafNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(mapNodeSchema, leafNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "nodeKind", @@ -135,7 +135,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(leafNodeSchema, objectNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(leafNodeSchema, objectNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "nodeKind", @@ -144,14 +144,14 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(mapNodeSchema, mapNodeSchema), []); + assert.deepEqual(getAllowedContentDiscrepancies(mapNodeSchema, mapNodeSchema), []); /** * Below is an inconsistency between 'isRepoSuperset' and 'allowsRepoSuperset'. The 'isRepoSuperset' will * halt further validation if an inconsistency in `nodeKind` is found. However, the current logic of * 'allowsRepoSuperset' permits relaxing an object node to a map node, which allows for a union of all types * permitted on the object node's fields. It is unclear if this behavior is desired, as - * 'getAllowedContentIncompatibilities' currently does not support it. + * 'getAllowedContentDiscrepancies' currently does not support it. * * TODO: If we decide to support this behavior, we will need better e2e tests for this scenario. Additionally, * we may need to adjust the encoding of map nodes and object nodes to ensure consistent encoding. @@ -199,7 +199,7 @@ describe("Schema Discrepancies", () => { root1, ); - assert.deepEqual(getAllowedContentIncompatibilities(mapNodeSchema1, mapNodeSchema2), [ + assert.deepEqual(getAllowedContentDiscrepancies(mapNodeSchema1, mapNodeSchema2), [ { identifier: undefined, mismatch: "fieldKind", @@ -220,7 +220,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(mapNodeSchema2, mapNodeSchema3), [ + assert.deepEqual(getAllowedContentDiscrepancies(mapNodeSchema2, mapNodeSchema3), [ { identifier: testTreeNodeIdentifier, mismatch: "allowedTypes", @@ -264,25 +264,22 @@ describe("Schema Discrepancies", () => { root, ); - assert.deepEqual( - getAllowedContentIncompatibilities(objectNodeSchema1, objectNodeSchema2), - [ - { - identifier: testTreeNodeIdentifier, - mismatch: "nodeKind", - view: "object", - stored: undefined, - }, - { - identifier: "tree2", - mismatch: "nodeKind", - view: undefined, - stored: "object", - }, - ], - ); + assert.deepEqual(getAllowedContentDiscrepancies(objectNodeSchema1, objectNodeSchema2), [ + { + identifier: testTreeNodeIdentifier, + mismatch: "nodeKind", + view: "object", + stored: undefined, + }, + { + identifier: "tree2", + mismatch: "nodeKind", + view: undefined, + stored: "object", + }, + ]); - assert.deepEqual(getAllowedContentIncompatibilities(mapNodeSchema, objectNodeSchema2), [ + assert.deepEqual(getAllowedContentDiscrepancies(mapNodeSchema, objectNodeSchema2), [ { identifier: testTreeNodeIdentifier, mismatch: "nodeKind", @@ -320,35 +317,32 @@ describe("Schema Discrepancies", () => { root, ); - assert.deepEqual( - getAllowedContentIncompatibilities(objectNodeSchema1, objectNodeSchema2), - [ - { - identifier: testTreeNodeIdentifier, - mismatch: "fields", - differences: [ - { - identifier: "x", - mismatch: "allowedTypes", - view: ["number"], - stored: ["string"], - }, - { - identifier: "x", - mismatch: "fieldKind", - view: "Value", - stored: "Optional", - }, - { - identifier: "y", - mismatch: "fieldKind", - view: "Forbidden", - stored: "Optional", - }, - ], - }, - ], - ); + assert.deepEqual(getAllowedContentDiscrepancies(objectNodeSchema1, objectNodeSchema2), [ + { + identifier: testTreeNodeIdentifier, + mismatch: "fields", + differences: [ + { + identifier: "x", + mismatch: "allowedTypes", + view: ["number"], + stored: ["string"], + }, + { + identifier: "x", + mismatch: "fieldKind", + view: "Value", + stored: "Optional", + }, + { + identifier: "y", + mismatch: "fieldKind", + view: "Forbidden", + stored: "Optional", + }, + ], + }, + ]); }); it("Differing value types on leaf node schema", () => { @@ -370,7 +364,7 @@ describe("Schema Discrepancies", () => { root, ); - assert.deepEqual(getAllowedContentIncompatibilities(leafNodeSchema1, leafNodeSchema2), [ + assert.deepEqual(getAllowedContentDiscrepancies(leafNodeSchema1, leafNodeSchema2), [ { identifier: testTreeNodeIdentifier, mismatch: "valueSchema", @@ -379,7 +373,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(leafNodeSchema1, leafNodeSchema3), []); + assert.deepEqual(getAllowedContentDiscrepancies(leafNodeSchema1, leafNodeSchema3), []); }); describe("Special types of tree schemas", () => { @@ -404,7 +398,7 @@ describe("Schema Discrepancies", () => { ); it("neverTree", () => { - assert.deepEqual(getAllowedContentIncompatibilities(neverTree, neverTree2), [ + assert.deepEqual(getAllowedContentDiscrepancies(neverTree, neverTree2), [ { identifier: testTreeNodeIdentifier, mismatch: "nodeKind", @@ -413,7 +407,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(neverTree, mapNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(neverTree, mapNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "allowedTypes", @@ -422,7 +416,7 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual(getAllowedContentIncompatibilities(neverTree2, objectNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(neverTree2, objectNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "fields", @@ -455,9 +449,9 @@ describe("Schema Discrepancies", () => { true, ); - assert.deepEqual(getAllowedContentIncompatibilities(emptyTree, emptyLocalFieldTree), []); + assert.deepEqual(getAllowedContentDiscrepancies(emptyTree, emptyLocalFieldTree), []); - assert.deepEqual(getAllowedContentIncompatibilities(emptyTree, objectNodeSchema), [ + assert.deepEqual(getAllowedContentDiscrepancies(emptyTree, objectNodeSchema), [ { identifier: testTreeNodeIdentifier, mismatch: "fields", @@ -472,29 +466,26 @@ describe("Schema Discrepancies", () => { }, ]); - assert.deepEqual( - getAllowedContentIncompatibilities(emptyLocalFieldTree, objectNodeSchema), - [ - { - identifier: testTreeNodeIdentifier, - mismatch: "fields", - differences: [ - { - identifier: "x", - mismatch: "allowedTypes", - view: [], - stored: ["number"], - }, - { - identifier: "x", - mismatch: "fieldKind", - view: "Forbidden", - stored: "Value", - }, - ], - }, - ], - ); + assert.deepEqual(getAllowedContentDiscrepancies(emptyLocalFieldTree, objectNodeSchema), [ + { + identifier: testTreeNodeIdentifier, + mismatch: "fields", + differences: [ + { + identifier: "x", + mismatch: "allowedTypes", + view: [], + stored: ["number"], + }, + { + identifier: "x", + mismatch: "fieldKind", + view: "Forbidden", + stored: "Value", + }, + ], + }, + ]); }); }); diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/genericFieldKind.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/genericFieldKind.spec.ts index 13e786293e4d..dfbaa41d1167 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/genericFieldKind.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/genericFieldKind.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; import { diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/isNeverTree.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/isNeverTree.spec.ts index 558b30c51dbd..86d606bf41ed 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/isNeverTree.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/isNeverTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type FieldKindIdentifier, diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts index 67cd001d4fbe..dafb71262fa7 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangesetUtil.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangesetUtil.ts index e7938389a85b..481654969a2d 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangesetUtil.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangesetUtil.ts @@ -40,7 +40,7 @@ import { newTupleBTree, // eslint-disable-next-line import/no-internal-modules } from "../../../feature-libraries/modular-schema/modularChangeFamily.js"; -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { assertStructuralEquality } from "../../objMerge.js"; import { BTree } from "@tylerbu/sorted-btree-es6"; diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/rangeMap.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/rangeMap.spec.ts index 3c6c0fc60732..f31d935de761 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/rangeMap.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/rangeMap.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type RangeEntry, diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts index 46b7f5a53d5a..fb729b13b573 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type Adapters, @@ -22,7 +22,7 @@ import { import { allowsFieldSuperset, allowsTreeSuperset, - getAllowedContentIncompatibilities, + getAllowedContentDiscrepancies, // eslint-disable-next-line import/no-internal-modules } from "../../../feature-libraries/modular-schema/index.js"; import { @@ -49,7 +49,7 @@ class TestSchemaRepository extends TreeStoredSchemaRepository { public tryUpdateRootFieldSchema(schema: TreeFieldStoredSchema): boolean { if (allowsFieldSuperset(this.policy, this, this.rootFieldSchema, schema)) { this.rootFieldSchemaData = schema; - this.events.emit("afterSchemaChange", this); + this._events.emit("afterSchemaChange", this); return true; } return false; @@ -64,7 +64,7 @@ class TestSchemaRepository extends TreeStoredSchemaRepository { const original = this.nodeSchema.get(name); if (allowsTreeSuperset(this.policy, this, original, storedSchema)) { this.nodeSchemaData.set(name, storedSchema); - this.events.emit("afterSchemaChange", this); + this._events.emit("afterSchemaChange", this); return true; } return false; @@ -212,7 +212,7 @@ describe("Schema Evolution Examples", () => { // which will notify and applications with the document open. // They can recheck their compatibility: const compatNew = view2.checkCompatibility(stored); - const report = getAllowedContentIncompatibilities(viewCollection2, stored); + const report = getAllowedContentDiscrepancies(viewCollection2, stored); assert.deepEqual(report, []); assertEnumEqual(Compatibility, compatNew.read, Compatibility.Compatible); // It is now possible to write our date into the document. diff --git a/packages/dds/tree/src/test/feature-libraries/node-key/nodeKey.spec.ts b/packages/dds/tree/src/test/feature-libraries/node-key/nodeKey.spec.ts index 7b1ae37a633a..23e7d364d1e5 100644 --- a/packages/dds/tree/src/test/feature-libraries/node-key/nodeKey.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/node-key/nodeKey.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import type { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions/internal"; import type { IIdCompressor } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts b/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts index aeec11706907..66940010fe5e 100644 --- a/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalChangeRebaser.test.ts b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalChangeRebaser.test.ts index c982818b9601..13189bf7e58b 100644 --- a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalChangeRebaser.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalChangeRebaser.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { describeStress, StressMode } from "@fluid-private/stochastic-test-utils"; import type { CrossFieldManager } from "../../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalField.spec.ts b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalField.spec.ts index 0ff7814b5e3b..acd374b0454c 100644 --- a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalField.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalField.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type ChangeAtomId, diff --git a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldSnapshots.test.ts b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldSnapshots.test.ts index c15e43fca062..c52dd7b965f9 100644 --- a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldSnapshots.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldSnapshots.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import path from "path"; +import path from "node:path"; import type { IIdCompressor } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldUtils.ts b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldUtils.ts index 9ff13e39e847..979889492678 100644 --- a/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldUtils.ts +++ b/packages/dds/tree/src/test/feature-libraries/optional-field/optionalFieldUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type ChangeAtomId, diff --git a/packages/dds/tree/src/test/feature-libraries/schema-index/codec.spec.ts b/packages/dds/tree/src/test/feature-libraries/schema-index/codec.spec.ts index a80869bce7e5..e21a5f254795 100644 --- a/packages/dds/tree/src/test/feature-libraries/schema-index/codec.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/schema-index/codec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; // Allow importing from this specific file which is being tested: diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/compose.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/compose.test.ts index cf98a357bfee..846159097651 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/compose.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/compose.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type ChangeAtomId, diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/markListFactory.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/markListFactory.test.ts index fe8ecce85214..f70f956537b5 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/markListFactory.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/markListFactory.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangesetLocalId, RevisionTag } from "../../../core/index.js"; import { SequenceField as SF } from "../../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/rebase.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/rebase.test.ts index 1b312c941906..a70334a71d43 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/rebase.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/rebase.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { mintRevisionTag } from "../../utils.js"; import type { NodeId, SequenceField as SF } from "../../../feature-libraries/index.js"; import { type ChangeAtomId, type RevisionTag, makeAnonChange } from "../../../core/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/relevantRemovedRoots.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/relevantRemovedRoots.test.ts index 10e422126f92..b3ee58932e33 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/relevantRemovedRoots.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/relevantRemovedRoots.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangeAtomId, DeltaDetachedNodeId } from "../../../core/index.js"; import { type NodeId, SequenceField as SF } from "../../../feature-libraries/index.js"; import { brand } from "../../../util/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.test.ts index f7d2803e4b72..f5b7a564a50b 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.test.ts @@ -5,7 +5,7 @@ import { describeStress, StressMode } from "@fluid-private/stochastic-test-utils"; import { assert } from "@fluidframework/core-utils/internal"; -import { strict } from "assert"; +import { strict } from "node:assert"; import { type ChangesetLocalId, diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldEditor.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldEditor.test.ts index 8731aab47d11..d3f807243ba7 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldEditor.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldEditor.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangesetLocalId } from "../../../core/index.js"; import { SequenceField as SF } from "../../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldSnapshots.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldSnapshots.test.ts index 0fb048360064..b655a1aeb8ab 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldSnapshots.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldSnapshots.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import path from "path"; +import path from "node:path"; import { RevisionTagCodec } from "../../../core/index.js"; import { SequenceField } from "../../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.test.ts index 48f1a7e94413..ca66ea2b5255 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type ChangesetLocalId, diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldUtils.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldUtils.test.ts index 8664de46a06b..d129755903d3 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldUtils.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceFieldUtils.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangeAtomId } from "../../../core/index.js"; import type { SequenceField as SF } from "../../../feature-libraries/index.js"; diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceGetNestedChanges.test.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceGetNestedChanges.test.ts index 68ed8f1e06e4..d42704c9583a 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceGetNestedChanges.test.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/sequenceGetNestedChanges.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { NodeId } from "../../../feature-libraries/index.js"; // eslint-disable-next-line import/no-internal-modules diff --git a/packages/dds/tree/src/test/feature-libraries/sequence-field/utils.ts b/packages/dds/tree/src/test/feature-libraries/sequence-field/utils.ts index eeda70719d08..71d22464d3e1 100644 --- a/packages/dds/tree/src/test/feature-libraries/sequence-field/utils.ts +++ b/packages/dds/tree/src/test/feature-libraries/sequence-field/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict } from "assert"; +import { strict } from "node:assert"; import { assert } from "@fluidframework/core-utils/internal"; import { createAlwaysFinalizedIdCompressor } from "@fluidframework/id-compressor/internal/test-utils"; diff --git a/packages/dds/tree/src/test/feature-libraries/treeCursorUtils.spec.ts b/packages/dds/tree/src/test/feature-libraries/treeCursorUtils.spec.ts index 1b288587b4e1..07c6b10af22a 100644 --- a/packages/dds/tree/src/test/feature-libraries/treeCursorUtils.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/treeCursorUtils.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { CursorLocationType, diff --git a/packages/dds/tree/src/test/feature-libraries/valueUtilities.spec.ts b/packages/dds/tree/src/test/feature-libraries/valueUtilities.spec.ts index f1c0843c5ac8..2aaef16c64fd 100644 --- a/packages/dds/tree/src/test/feature-libraries/valueUtilities.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/valueUtilities.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { MockHandle } from "@fluidframework/test-runtime-utils/internal"; @@ -64,7 +64,7 @@ describe("valueUtilities", () => { it("isTreeValue", () => { assert(isTreeValue(0)); assert(isTreeValue(0.001)); - assert(isTreeValue(NaN)); + assert(isTreeValue(Number.NaN)); assert(isTreeValue(true)); assert(isTreeValue(false)); assert(isTreeValue("")); diff --git a/packages/dds/tree/src/test/forestTestSuite.ts b/packages/dds/tree/src/test/forestTestSuite.ts index ff262bee5926..74011bf27d95 100644 --- a/packages/dds/tree/src/test/forestTestSuite.ts +++ b/packages/dds/tree/src/test/forestTestSuite.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type DeltaFieldChanges, @@ -606,7 +606,7 @@ export function testForest(config: ForestTestConfiguration): void { ); const log: string[] = []; - forest.on("beforeChange", () => { + forest.events.on("beforeChange", () => { const cursor = forest.allocateCursor(); moveToDetachedField(forest, cursor); log.push("beforeChange"); @@ -629,7 +629,7 @@ export function testForest(config: ForestTestConfiguration): void { ); const log: string[] = []; - forest.on("beforeChange", () => { + forest.events.on("beforeChange", () => { log.push("beforeChange"); }); diff --git a/packages/dds/tree/src/test/memory/tree.spec.ts b/packages/dds/tree/src/test/memory/tree.spec.ts index 0084f1086f9a..0525aa284baf 100644 --- a/packages/dds/tree/src/test/memory/tree.spec.ts +++ b/packages/dds/tree/src/test/memory/tree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type IMemoryTestObject, benchmarkMemory, diff --git a/packages/dds/tree/src/test/objMerge.ts b/packages/dds/tree/src/test/objMerge.ts index adaa1a5e235b..df70bbd81760 100644 --- a/packages/dds/tree/src/test/objMerge.ts +++ b/packages/dds/tree/src/test/objMerge.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { fail } from "../util/index.js"; // eslint-disable-next-line @typescript-eslint/no-extraneous-class diff --git a/packages/dds/tree/src/test/rebase/findAncestor.spec.ts b/packages/dds/tree/src/test/rebase/findAncestor.spec.ts index 91f7d94dda8f..9b7b7aca3d6d 100644 --- a/packages/dds/tree/src/test/rebase/findAncestor.spec.ts +++ b/packages/dds/tree/src/test/rebase/findAncestor.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; // Allow importing from these specific files which are being tested: /* eslint-disable-next-line import/no-internal-modules */ diff --git a/packages/dds/tree/src/test/rebase/rebaseBranch.spec.ts b/packages/dds/tree/src/test/rebase/rebaseBranch.spec.ts index 4f91313a246a..2f5f79c5aaca 100644 --- a/packages/dds/tree/src/test/rebase/rebaseBranch.spec.ts +++ b/packages/dds/tree/src/test/rebase/rebaseBranch.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/rebase/rebaser.spec.ts b/packages/dds/tree/src/test/rebase/rebaser.spec.ts index b45b8226cbd3..41b0d6ff9aa6 100644 --- a/packages/dds/tree/src/test/rebase/rebaser.spec.ts +++ b/packages/dds/tree/src/test/rebase/rebaser.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { ChangeRebaser, RevisionTag } from "../../core/index.js"; diff --git a/packages/dds/tree/src/test/rebase/revisionTagCodec.spec.ts b/packages/dds/tree/src/test/rebase/revisionTagCodec.spec.ts index 6cb45b551e2c..0378e47d5b1f 100644 --- a/packages/dds/tree/src/test/rebase/revisionTagCodec.spec.ts +++ b/packages/dds/tree/src/test/rebase/revisionTagCodec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { createIdCompressor, createSessionId } from "@fluidframework/id-compressor/internal"; diff --git a/packages/dds/tree/src/test/rebaserAxiomaticTests.ts b/packages/dds/tree/src/test/rebaserAxiomaticTests.ts index 9fe54c28382d..d0193fe41ee3 100644 --- a/packages/dds/tree/src/test/rebaserAxiomaticTests.ts +++ b/packages/dds/tree/src/test/rebaserAxiomaticTests.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type RevisionMetadataSource, diff --git a/packages/dds/tree/src/test/scalableTestTrees.ts b/packages/dds/tree/src/test/scalableTestTrees.ts index 08dfd814104a..e2fef715a0b8 100644 --- a/packages/dds/tree/src/test/scalableTestTrees.ts +++ b/packages/dds/tree/src/test/scalableTestTrees.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { EmptyKey, diff --git a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts index 68107d969a58..1daec4e6240a 100644 --- a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; @@ -337,7 +337,7 @@ describe("Branches", () => { it("emit a fork event after forking", () => { let fork: DefaultBranch | undefined; const branch = create(); - branch.on("fork", (f) => (fork = f)); + branch.events.on("fork", (f) => (fork = f)); // The fork event should return the new branch, just as the fork method does assert.equal(branch.fork(), fork); assert.equal(branch.fork(), fork); @@ -346,7 +346,7 @@ describe("Branches", () => { it("emit a dispose event after disposing", () => { const branch = create(); let disposed = false; - branch.on("dispose", () => (disposed = true)); + branch.events.on("dispose", () => (disposed = true)); branch.dispose(); assert.equal(disposed, true); }); @@ -358,7 +358,7 @@ describe("Branches", () => { it(`emit a transactionStarted event after a new transaction scope is opened ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionStarted", (isOuterTransaction) => { + branch.events.on("transactionStarted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -385,7 +385,7 @@ describe("Branches", () => { it(`emit a transactionAborted event after a transaction scope is aborted ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionAborted", (isOuterTransaction) => { + branch.events.on("transactionAborted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -414,7 +414,7 @@ describe("Branches", () => { it(`emit a transactionCommitted event after a new transaction scope is committed ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionCommitted", (isOuterTransaction) => { + branch.events.on("transactionCommitted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -443,7 +443,7 @@ describe("Branches", () => { it(`emit a transactionRolledBack event after a transaction scope is rolled back ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionRolledBack", (isOuterTransaction) => { + branch.events.on("transactionRolledBack", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -686,11 +686,9 @@ describe("Branches", () => { it("registers listener on forks created inside of the listener", () => { const branch = create(); let forkCount = 0; - onForkTransitive(branch, () => { - forkCount += 1; - assert(branch.hasListeners("fork")); - if (forkCount <= 1) { - branch.fork(); + onForkTransitive(branch, (f) => { + if (forkCount++ === 0) { + f.fork(); } }); branch.fork(); @@ -709,12 +707,12 @@ describe("Branches", () => { const branch = new SharedTreeBranch(initCommit, defaultChangeFamily, mintRevisionTag); let head = branch.getHead(); - branch.on("beforeChange", (c) => { + branch.events.on("beforeChange", (c) => { // Check that the branch head never changes in the "before" event; it should only change after the "after" event. assert.equal(branch.getHead(), head); onChange?.(c); }); - branch.on("afterChange", (c) => { + branch.events.on("afterChange", (c) => { head = branch.getHead(); onChange?.(c); }); diff --git a/packages/dds/tree/src/test/shared-tree-core/defaultResubmitMachine.spec.ts b/packages/dds/tree/src/test/shared-tree-core/defaultResubmitMachine.spec.ts index 6a0304f17b84..368a69688ee0 100644 --- a/packages/dds/tree/src/test/shared-tree-core/defaultResubmitMachine.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/defaultResubmitMachine.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { GraphCommit, RevisionTag, TaggedChange } from "../../core/index.js"; import { testIdCompressor } from "../utils.js"; import { diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts index abf6eed82d73..fab3e68dfdce 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type BenchmarkTimer, BenchmarkType, benchmark } from "@fluid-tools/benchmark"; @@ -272,7 +272,7 @@ describe("EditManager - Bench", () => { const family = testChangeFamilyFactory(new NoOpChangeRebaser()); const manager = editManagerFactory(family); // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + manager.localBranch.events.on("afterChange", ({ change }) => {}); const sequencedEdits: Commit[] = []; for (let iChange = 0; iChange < count; iChange++) { const revision = mintRevisionTag(); diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts index 711f3065f83e..308a6b4d8a8d 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { describeStress, StressMode } from "@fluid-private/stochastic-test-utils"; import type { SessionId } from "@fluidframework/id-compressor"; @@ -749,7 +749,7 @@ function trackTrimmed( branch: SharedTreeBranch, ): ReadonlySet { const trimmedCommits = new Set(); - branch.on("ancestryTrimmed", (trimmedRevisions) => { + branch.events.on("ancestryTrimmed", (trimmedRevisions) => { trimmedRevisions.forEach((revision) => trimmedCommits.add(revision)); }); return trimmedCommits; diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerPerf.test.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerPerf.test.ts index 290549126ec0..e979a0139032 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerPerf.test.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerPerf.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerScenario.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerScenario.ts index 1d0a404f8b13..2ffaf9c4632c 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerScenario.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerScenario.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { unreachableCase } from "@fluidframework/core-utils/internal"; import type { SessionId } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts index 9d9419e5bcde..df6e01dcfef6 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts @@ -120,8 +120,7 @@ export function rebaseLocalEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < localEditCount; iChange++) { const revision = mintRevisionTag(); manager.localBranch.apply({ change: mintChange(undefined), revision }); @@ -205,8 +204,7 @@ export function rebasePeerEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < trunkEditCount; iChange++) { const revision = mintRevisionTag(); manager.addSequencedChange( @@ -303,8 +301,7 @@ export function rebaseAdvancingPeerEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < editCount; iChange++) { const revision = mintRevisionTag(); manager.addSequencedChange( @@ -399,8 +396,7 @@ export function rebaseConcurrentPeerEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); const peerEdits: Commit[] = []; for (let iChange = 0; iChange < editsPerPeerCount; iChange++) { for (let iPeer = 0; iPeer < peerCount; iPeer++) { @@ -434,7 +430,7 @@ export function addSequencedChange( ...args: Parameters<(typeof editManager)["addSequencedChange"]> ): DeltaRoot { let delta: DeltaRoot = emptyDelta; - const offChange = editManager.localBranch.on("afterChange", ({ change }) => { + const offChange = editManager.localBranch.events.on("afterChange", ({ change }) => { if (change !== undefined) { delta = asDelta(change.change.intentions); } @@ -443,3 +439,13 @@ export function addSequencedChange( offChange(); return delta; } + +/** Subscribe to the local branch to emulate the behavior of SharedTree */ +function subscribeToLocalBranch( + manager: EditManager>, +): void { + manager.localBranch.events.on("afterChange", (branchChange) => { + // Reading the change property causes lazy computation to occur, and is important to accurately emulate SharedTree behavior + const _change = branchChange.change; + }); +} diff --git a/packages/dds/tree/src/test/shared-tree-core/messageCodec.spec.ts b/packages/dds/tree/src/test/shared-tree-core/messageCodec.spec.ts index 607c47a77000..b279175f208a 100644 --- a/packages/dds/tree/src/test/shared-tree-core/messageCodec.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/messageCodec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; import { createSessionId } from "@fluidframework/id-compressor/internal"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index 229a443b0463..ebc5185e8229 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { IsoBuffer, TypedEventEmitter } from "@fluid-internal/client-utils"; import type { IEvent } from "@fluidframework/core-interfaces"; @@ -634,7 +634,7 @@ describe("SharedTreeCore", () => { assert.equal(tree.preparedCommitsCount, 0); // Temporarily make commit application fail - const disableFailure = tree.getLocalBranch().on("beforeChange", () => { + const disableFailure = tree.getLocalBranch().events.on("beforeChange", () => { throw new Error("Invalid commit"); }); assert.throws(() => changeTree(tree)); diff --git a/packages/dds/tree/src/test/shared-tree-core/utils.ts b/packages/dds/tree/src/test/shared-tree-core/utils.ts index 822862425e33..445014cfb693 100644 --- a/packages/dds/tree/src/test/shared-tree-core/utils.ts +++ b/packages/dds/tree/src/test/shared-tree-core/utils.ts @@ -30,7 +30,7 @@ import { type Summarizable, } from "../../shared-tree-core/index.js"; import { testIdCompressor } from "../utils.js"; -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; /** * A `SharedTreeCore` with diff --git a/packages/dds/tree/src/test/shared-tree/editing.spec.ts b/packages/dds/tree/src/test/shared-tree/editing.spec.ts index 2816019bda9d..0d9395585cea 100644 --- a/packages/dds/tree/src/test/shared-tree/editing.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/editing.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { unreachableCase } from "@fluidframework/core-utils/internal"; @@ -77,6 +77,27 @@ describe("Editing", () => { expectJsonTree([tree1, tree2], expected); }); + it("replace vs insert", () => { + const root = makeTreeFromJsonSequence(["A", "C"]); + + const tree1 = root.branch(); + remove(tree1, 0, 2); + insert(tree1, 0, "a", "c"); + + const tree2 = root.branch(); + insert(tree2, 1, "b"); + + const merge1then2 = root.branch(); + merge1then2.merge(tree1, false); + merge1then2.merge(tree2, false); + + const merge2then1 = root.branch(); + merge2then1.merge(tree2, false); + merge2then1.merge(tree1, false); + + expectJsonTree([merge1then2, merge2then1], ["a", "c", "b"]); + }); + it("can rebase remove over move", () => { const tree1 = makeTreeFromJsonSequence([]); const tree2 = tree1.branch(); @@ -1770,7 +1791,7 @@ describe("Editing", () => { beforeDetach(source: RangeUpPath, destination: DetachedPlaceUpPath): void {}, afterDetach(source: PlaceUpPath, destination: DetachedRangeUpPath): void {}, }; - const unsubscribePathVisitor = node.on( + const unsubscribePathVisitor = node.events.on( "subtreeChanging", (n: AnchorNode) => pathVisitor, ); @@ -2614,7 +2635,7 @@ describe("Editing", () => { beforeDetach(source: RangeUpPath, destination: DetachedPlaceUpPath): void {}, afterDetach(source: PlaceUpPath, destination: DetachedRangeUpPath): void {}, }; - const unsubscribePathVisitor = node.on( + const unsubscribePathVisitor = node.events.on( "subtreeChanging", (n: AnchorNode) => pathVisitor, ); diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/anchorStability.fuzz.spec.ts b/packages/dds/tree/src/test/shared-tree/fuzz/anchorStability.fuzz.spec.ts index 51c8038c919a..b05602021128 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/anchorStability.fuzz.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/anchorStability.fuzz.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { takeAsync } from "@fluid-private/stochastic-test-utils"; diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/composeVsIndividual.fuzz.spec.ts b/packages/dds/tree/src/test/shared-tree/fuzz/composeVsIndividual.fuzz.spec.ts index f9e27e90f8ba..952274e80531 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/composeVsIndividual.fuzz.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/composeVsIndividual.fuzz.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditGenerators.ts b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditGenerators.ts index 332a54b95e42..54473b2e5618 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditGenerators.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditGenerators.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type AsyncGenerator, diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditReducers.ts b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditReducers.ts index 1995fac98917..cad473aaffde 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditReducers.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzEditReducers.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type AsyncReducer, combineReducers } from "@fluid-private/stochastic-test-utils"; import type { DDSFuzzTestState, Client } from "@fluid-private/test-dds-utils"; diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzUtils.ts b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzUtils.ts index fe29ef06ce93..0cf84bb05612 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/fuzzUtils.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/fuzzUtils.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; -import { join as pathJoin } from "path"; +import { strict as assert } from "node:assert"; +import { join as pathJoin } from "node:path"; import { makeRandom } from "@fluid-private/stochastic-test-utils"; import type { FuzzSerializedIdCompressor } from "@fluid-private/test-dds-utils"; diff --git a/packages/dds/tree/src/test/shared-tree/fuzz/undoRedo.fuzz.spec.ts b/packages/dds/tree/src/test/shared-tree/fuzz/undoRedo.fuzz.spec.ts index 1476c8e43b01..3132cb54b966 100644 --- a/packages/dds/tree/src/test/shared-tree/fuzz/undoRedo.fuzz.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/fuzz/undoRedo.fuzz.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { type AsyncGenerator, takeAsync } from "@fluid-private/stochastic-test-utils"; @@ -225,7 +225,7 @@ function init(state: UndoRedoFuzzTestState) { state.unsubscribe = []; for (const client of state.clients) { const checkout = viewFromState(state, client).checkout; - const unsubscribe = checkout.events.on("commitApplied", (commit, getRevertible) => { + const unsubscribe = checkout.events.on("changed", (commit, getRevertible) => { if (getRevertible !== undefined) { if (commit.kind === CommitKind.Undo) { redoStack.push(getRevertible()); diff --git a/packages/dds/tree/src/test/shared-tree/opSize.bench.ts b/packages/dds/tree/src/test/shared-tree/opSize.bench.ts index bd4e8d95e667..e2dd619016b3 100644 --- a/packages/dds/tree/src/test/shared-tree/opSize.bench.ts +++ b/packages/dds/tree/src/test/shared-tree/opSize.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { BenchmarkType, diff --git a/packages/dds/tree/src/test/shared-tree/repairData.spec.ts b/packages/dds/tree/src/test/shared-tree/repairData.spec.ts index 0ecf989ebce4..c1465ded87bc 100644 --- a/packages/dds/tree/src/test/shared-tree/repairData.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/repairData.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { rootFieldKey } from "../../core/index.js"; import { StringArray, TestTreeProviderLite, createTestUndoRedoStacks } from "../utils.js"; diff --git a/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts b/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts index 329ef2d53b68..972a2d60ecb5 100644 --- a/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { AllowedUpdateType, @@ -102,7 +102,7 @@ describe("schematizeTree", () => { let previousSchema: TreeStoredSchema = new TreeStoredSchemaRepository(storedSchema); expectSchema(storedSchema, previousSchema); - storedSchema.on("afterSchemaChange", () => { + storedSchema.events.on("afterSchemaChange", () => { previousSchema = new TreeStoredSchemaRepository(storedSchema); }); @@ -122,7 +122,7 @@ describe("schematizeTree", () => { const storedSchema = new TreeStoredSchemaRepository(); const log: string[] = []; - storedSchema.on("afterSchemaChange", () => { + storedSchema.events.on("afterSchemaChange", () => { log.push("schema"); }); initializeContent(makeSchemaRepository(storedSchema), content.schema, () => diff --git a/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts b/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts index c7c6bad0f1aa..49182d5c745f 100644 --- a/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/schematizingTreeView.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { UsageError } from "@fluidframework/telemetry-utils/internal"; diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts index ac572af66735..27e7d58bff5a 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type BenchmarkTimer, diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts index a37204a76675..a38ad3e9ea46 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { IContainerExperimental } from "@fluidframework/container-loader/internal"; import { createIdCompressor } from "@fluidframework/id-compressor/internal"; @@ -64,7 +64,7 @@ import { SchemaFactory, toStoredSchema, type TreeFieldFromImplicitField, - type TreeView, + type TreeViewAlpha, TreeViewConfiguration, } from "../../simple-tree/index.js"; import { brand, fail } from "../../util/index.js"; @@ -832,6 +832,83 @@ describe("SharedTree", () => { }); describe("Undo and redo", () => { + it("the insert of a node in a sequence field using the commitApplied event", () => { + const value = "42"; + const provider = new TestTreeProviderLite(2); + const tree1 = provider.trees[0]; + const view1 = tree1.viewWith( + new TreeViewConfiguration({ schema: StringArray, enableSchemaValidation }), + ); + view1.initialize([]); + + const undoStack: Revertible[] = []; + const redoStack: Revertible[] = []; + + function onDispose(disposed: Revertible): void { + const redoIndex = redoStack.indexOf(disposed); + if (redoIndex !== -1) { + redoStack.splice(redoIndex, 1); + } else { + const undoIndex = undoStack.indexOf(disposed); + if (undoIndex !== -1) { + undoStack.splice(undoIndex, 1); + } + } + } + + const unsubscribeFromCommitAppliedEvent = view1.events.on( + "commitApplied", + (commit, getRevertible) => { + if (getRevertible !== undefined) { + const revertible = getRevertible(onDispose); + if (commit.kind === CommitKind.Undo) { + redoStack.push(revertible); + } else { + undoStack.push(revertible); + } + } + }, + ); + const unsubscribe = (): void => { + unsubscribeFromCommitAppliedEvent(); + for (const revertible of undoStack) { + revertible.dispose(); + } + for (const revertible of redoStack) { + revertible.dispose(); + } + }; + + provider.processMessages(); + const tree2 = provider.trees[1]; + const view2 = tree2.viewWith( + new TreeViewConfiguration({ schema: StringArray, enableSchemaValidation }), + ); + provider.processMessages(); + + // Insert node + view1.root.insertAtStart(value); + provider.processMessages(); + + // Validate insertion + assert.deepEqual([...view2.root], [value]); + + // Undo node insertion + undoStack.pop()?.revert(); + provider.processMessages(); + + assert.deepEqual([...view1.root], []); + assert.deepEqual([...view2.root], []); + + // Redo node insertion + redoStack.pop()?.revert(); + provider.processMessages(); + + assert.deepEqual([...view1.root], [value]); + assert.deepEqual([...view2.root], [value]); + unsubscribe(); + }); + it("the insert of a node in a sequence field", () => { const value = "42"; const provider = new TestTreeProviderLite(2); @@ -1159,7 +1236,7 @@ describe("SharedTree", () => { interface Peer extends ConnectionSetter { readonly checkout: TreeCheckout; - readonly view: TreeView; + readonly view: TreeViewAlpha; readonly outerList: TreeFieldFromImplicitField; readonly innerList: TreeFieldFromImplicitField; assertOuterListEquals(expected: readonly (readonly string[])[]): void; @@ -1168,7 +1245,7 @@ describe("SharedTree", () => { function makeUndoableEdit(peer: Peer, edit: () => void): Revertible { const undos: Revertible[] = []; - const unsubscribe = peer.view.events.on("commitApplied", ({ kind }, getRevertible) => { + const unsubscribe = peer.view.events.on("changed", ({ kind }, getRevertible) => { if (kind !== CommitKind.Undo && getRevertible !== undefined) { undos.push(getRevertible()); } @@ -1474,6 +1551,76 @@ describe("SharedTree", () => { unsubscribe1(); unsubscribe2(); }); + + // TODO: move this event test to the tree view tests + it("emits a changed event for local edits", () => { + const value = "42"; + const provider = new TestTreeProviderLite(2); + const tree1 = provider.trees[0]; + const view1 = tree1.viewWith( + new TreeViewConfiguration({ schema: StringArray, enableSchemaValidation }), + ); + view1.initialize([]); + provider.processMessages(); + + let localEdits = 0; + let remoteEdits = 0; + + const unsubscribe = view1.events.on("changed", (metadata) => { + if (metadata.isLocal === true) { + localEdits++; + } else { + remoteEdits++; + } + }); + + // Insert node + view1.root.insertAtStart(value); + provider.processMessages(); + + // Validate insertion + assert.deepEqual([...view1.root], [value]); + + assert.equal(localEdits, 1); + // check that the edit is not counted twice + assert.equal(remoteEdits, 0); + + unsubscribe(); + }); + + it("emits a changed event for remote edits", () => { + const value = "42"; + const provider = new TestTreeProviderLite(2); + const tree1 = provider.trees[0]; + const view1 = tree1.viewWith( + new TreeViewConfiguration({ schema: StringArray, enableSchemaValidation }), + ); + view1.initialize([]); + provider.processMessages(); + const tree2 = provider.trees[1]; + const view2 = tree2.viewWith( + new TreeViewConfiguration({ schema: StringArray, enableSchemaValidation }), + ); + + let remoteEdits = 0; + + const unsubscribe = view1.events.on("changed", (metadata) => { + if (metadata.isLocal !== true) { + remoteEdits++; + } + }); + + // Insert node + view2.root.insertAtStart(value); + provider.processMessages(); + + // Validate insertion + assert.deepEqual([...view1.root], [value]); + + assert.equal(remoteEdits, 1); + + unsubscribe(); + }); }); describe("Rebasing", () => { diff --git a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeCodec.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeCodec.spec.ts index 9199c9f7597a..ef3df8192a6a 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeCodec.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeCodec.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts index b784908d9af5..17b1c963104e 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type ChangesetLocalId, DetachedFieldIndex, diff --git a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts index c93dc5c6262f..aa0418fe9cbc 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { deepFreeze } from "@fluidframework/test-runtime-utils/internal"; import type { ICodecOptions } from "../../codec/index.js"; diff --git a/packages/dds/tree/src/test/shared-tree/summary.bench.ts b/packages/dds/tree/src/test/shared-tree/summary.bench.ts index 3435ed790e77..7000e3bfffeb 100644 --- a/packages/dds/tree/src/test/shared-tree/summary.bench.ts +++ b/packages/dds/tree/src/test/shared-tree/summary.bench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { IsoBuffer } from "@fluid-internal/client-utils"; import { diff --git a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts index a35367595ac9..3e5c429d9e3a 100644 --- a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type IMockLoggerExt, @@ -27,7 +27,7 @@ import { TreeCheckout, type ITreeCheckout, type ITreeCheckoutFork, - type TreeBranch, + type BranchableTree, } from "../../shared-tree/index.js"; import { TestTreeProviderLite, @@ -47,6 +47,7 @@ import { // eslint-disable-next-line import/no-internal-modules import { SchematizingSimpleTreeView } from "../../shared-tree/schematizingTreeView.js"; import { + asTreeViewAlpha, getOrCreateInnerNode, toStoredSchema, type InsertableField, @@ -74,8 +75,8 @@ describe("sharedTreeView", () => { const root = view.root; const anchorNode = getOrCreateInnerNode(root).anchorNode; const log: string[] = []; - const unsubscribe = anchorNode.on("childrenChanging", () => log.push("change")); - const unsubscribeSubtree = anchorNode.on("subtreeChanging", () => { + const unsubscribe = anchorNode.events.on("childrenChanging", () => log.push("change")); + const unsubscribeSubtree = anchorNode.events.on("subtreeChanging", () => { log.push("subtree"); }); const unsubscribeAfter = view.checkout.events.on("afterBatch", () => log.push("after")); @@ -114,10 +115,10 @@ describe("sharedTreeView", () => { const root = view.root; const anchorNode = getOrCreateInnerNode(root).anchorNode; const log: string[] = []; - const unsubscribe = anchorNode.on("childrenChanging", (upPath) => + const unsubscribe = anchorNode.events.on("childrenChanging", (upPath) => log.push(`change-${String(upPath.parentField)}-${upPath.parentIndex}`), ); - const unsubscribeSubtree = anchorNode.on("subtreeChanging", (upPath) => { + const unsubscribeSubtree = anchorNode.events.on("subtreeChanging", (upPath) => { log.push(`subtree-${String(upPath.parentField)}-${upPath.parentIndex}`); }); const unsubscribeAfter = view.checkout.events.on("afterBatch", () => log.push("after")); @@ -148,7 +149,7 @@ describe("sharedTreeView", () => { ]); }); - describe("commitApplied", () => { + describe("changed", () => { const sf1 = new SchemaFactory("commit applied schema"); const mixedSchema = sf1.optional([sf1.string, sf1.number]); const OptionalString = sf1.optional([sf1.string]); @@ -158,9 +159,7 @@ describe("sharedTreeView", () => { const checkout = provider.trees[0].checkout; const log: string[] = []; - const unsubscribe = checkout.events.on("commitApplied", () => - log.push("commitApplied"), - ); + const unsubscribe = checkout.events.on("changed", () => log.push("changed")); assert.equal(log.length, 0); @@ -188,7 +187,7 @@ describe("sharedTreeView", () => { const checkout = provider.trees[0].checkout; const log: string[] = []; - const unsubscribe = checkout.events.on("commitApplied", (data, getRevertible) => + const unsubscribe = checkout.events.on("changed", (data, getRevertible) => log.push(getRevertible === undefined ? "not-revertible" : "revertible"), ); @@ -809,7 +808,7 @@ describe("sharedTreeView", () => { const treeBranch = tree.branch(); const viewBranch = treeBranch.viewWith(view.config); viewBranch.dispose(); - treeBranch.dispose(); + assert.equal(treeBranch.disposed, true); }); itView("disposed forks cannot be edited or double-disposed", ({ view, tree }) => { @@ -1014,7 +1013,7 @@ describe("sharedTreeView", () => { describe("revertibles", () => { itView("can be generated for changes made to the local branch", ({ view }) => { const revertiblesCreated: Revertible[] = []; - const unsubscribe = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const revertible = getRevertible(); assert.equal(revertible.status, RevertibleStatus.Valid); @@ -1042,7 +1041,7 @@ describe("sharedTreeView", () => { ({ view }) => { const revertiblesCreated: Revertible[] = []; - const unsubscribe = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const revertible = getRevertible(onRevertibleDisposed); assert.equal(revertible.status, RevertibleStatus.Valid); @@ -1078,10 +1077,10 @@ describe("sharedTreeView", () => { ); itView( - "revertibles cannot be acquired outside of the commitApplied event callback", + "revertibles cannot be acquired outside of the changed event callback", ({ view }) => { let acquireRevertible: RevertibleFactory | undefined; - const unsubscribe = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); acquireRevertible = getRevertible; }); @@ -1095,13 +1094,13 @@ describe("sharedTreeView", () => { itView("revertibles cannot be acquired more than once", ({ view }) => { const revertiblesCreated: Revertible[] = []; - const unsubscribe1 = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe1 = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const revertible = getRevertible(); assert.equal(revertible.status, RevertibleStatus.Valid); revertiblesCreated.push(revertible); }); - const unsubscribe2 = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe2 = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); assert.throws(() => getRevertible()); }); @@ -1113,7 +1112,7 @@ describe("sharedTreeView", () => { itView("disposed revertibles cannot be released or reverted", ({ view }) => { const revertiblesCreated: Revertible[] = []; - const unsubscribe = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const r = getRevertible(); assert.equal(r.status, RevertibleStatus.Valid); @@ -1135,10 +1134,10 @@ describe("sharedTreeView", () => { unsubscribe(); }); - itView("commitApplied events have the correct commit kinds", ({ view }) => { + itView("changed events have the correct commit kinds", ({ view }) => { const revertiblesCreated: Revertible[] = []; const commitKinds: CommitKind[] = []; - const unsubscribe = view.events.on("commitApplied", ({ kind }, getRevertible) => { + const unsubscribe = view.events.on("changed", ({ kind }, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const revertible = getRevertible(); assert.equal(revertible.status, RevertibleStatus.Valid); @@ -1157,9 +1156,9 @@ describe("sharedTreeView", () => { itView("disposing of a view also disposes of its revertibles", ({ view, tree }) => { const treeBranch = tree.branch(); - const viewBranch = treeBranch.viewWith(view.config); + const viewBranch = asTreeViewAlpha(treeBranch.viewWith(view.config)); const revertiblesCreated: Revertible[] = []; - const unsubscribe = viewBranch.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = viewBranch.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "commit should be revertible"); const r = getRevertible(onRevertibleDisposed); assert.equal(r.status, RevertibleStatus.Valid); @@ -1188,7 +1187,7 @@ describe("sharedTreeView", () => { itView("can be reverted after rebasing", ({ view, tree }) => { const treeBranch = tree.branch(); - const viewBranch = treeBranch.viewWith(view.config); + const viewBranch = asTreeViewAlpha(treeBranch.viewWith(view.config)); viewBranch.root.insertAtStart("A"); const stacks = createTestUndoRedoStacks(viewBranch.events); @@ -1213,7 +1212,7 @@ describe("sharedTreeView", () => { for (const ageToTest of [0, 1, 5]) { itView(`Telemetry logs track reversion age (${ageToTest})`, ({ view, logger }) => { let revertible: Revertible | undefined; - const unsubscribe = view.events.on("commitApplied", (_, getRevertible) => { + const unsubscribe = view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined, "Expected commit to be revertible."); // Only save off the first revertible, as it's the only one we'll use. if (revertible === undefined) { @@ -1258,7 +1257,7 @@ function itView< title: string, fn: (args: { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; }) => void, options: { @@ -1270,7 +1269,7 @@ function itView( title: string, fn: (args: { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; }) => void, options?: { @@ -1284,7 +1283,7 @@ function itView< title: string, fn: (args: { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; }) => void, options: { @@ -1298,7 +1297,7 @@ function itView< thunk: typeof fn, makeViewFromConfig: (config: TreeViewConfiguration) => { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; }, ): void { @@ -1316,7 +1315,7 @@ function itView< const { view, tree, logger } = ( makeViewFromConfig as unknown as (config: TreeViewConfiguration) => { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; } )( @@ -1330,7 +1329,7 @@ function itView< ( thunk as unknown as (args: { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; }) => void )({ view, tree, logger }); @@ -1342,7 +1341,7 @@ function itView< fork: boolean, ): { view: SchematizingSimpleTreeView; - tree: TreeBranch; + tree: BranchableTree; logger: IMockLoggerExt; } { const logger = createMockLoggerExt(); diff --git a/packages/dds/tree/src/test/shared-tree/treeNodeApi.spec.ts b/packages/dds/tree/src/test/shared-tree/treeNodeApi.spec.ts index b71253e62101..9fb6fb3d6223 100644 --- a/packages/dds/tree/src/test/shared-tree/treeNodeApi.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/treeNodeApi.spec.ts @@ -11,6 +11,7 @@ import { CheckoutFlexTreeView, type TransactionConstraint, Tree, + TreeAlpha, type rollback, } from "../../shared-tree/index.js"; import { @@ -21,6 +22,8 @@ import { type InsertableTypedNode, type TreeNodeSchema, type NodeFromSchema, + asTreeViewAlpha, + type TreeViewAlpha, } from "../../simple-tree/index.js"; import { TestTreeProviderLite, @@ -99,11 +102,11 @@ describe("treeApi", () => { try { run(view, (root) => { root.content = 43; - throw Error("Oh no"); + throw new Error("Oh no"); }); - } catch (e) { - assert(e instanceof Error); - assert.equal(e.message, "Oh no"); + } catch (error) { + assert(error instanceof Error); + assert.equal(error.message, "Oh no"); } assert.equal(view.root.content, 42); }); @@ -401,4 +404,33 @@ describe("treeApi", () => { assert.equal(Tree.contains(level3, level2), false); assert.equal(Tree.contains(level2, level1), false); }); + + it("context", () => { + const schemaFactory = new SchemaFactory(undefined); + class Array extends schemaFactory.array("array", schemaFactory.number) {} + const view = getView( + new TreeViewConfiguration({ schema: Array, enableSchemaValidation: true }), + ); + view.initialize([1, 2, 3]); + + // Hydrated + const array = view.root; + const context = TreeAlpha.branch(array); + assert(context !== undefined); + + // Unhydrated + assert.equal(TreeAlpha.branch(new Array([1, 2, 3])), undefined); + }); + + it("can cast to alpha", () => { + const schemaFactory = new SchemaFactory(undefined); + const view = getView( + new TreeViewConfiguration({ schema: schemaFactory.null, enableSchemaValidation: true }), + ); + view.initialize(null); + assert.equal( + asTreeViewAlpha(view) satisfies TreeViewAlpha, + view, + ); + }); }); diff --git a/packages/dds/tree/src/test/shared-tree/undo.spec.ts b/packages/dds/tree/src/test/shared-tree/undo.spec.ts index dd7a04e21ec7..5115b9140183 100644 --- a/packages/dds/tree/src/test/shared-tree/undo.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/undo.spec.ts @@ -20,7 +20,7 @@ import { MockFluidDataStoreRuntime, MockStorage, } from "@fluidframework/test-runtime-utils/internal"; -import assert from "assert"; +import assert from "node:assert"; import { SchemaFactory, TreeViewConfiguration } from "../../simple-tree/index.js"; // eslint-disable-next-line import/no-internal-modules import { initialize } from "../../shared-tree/schematizeTree.js"; @@ -444,7 +444,7 @@ describe("Undo and redo", () => { view.initialize({ foo: 1 }); assert.equal(tree.isAttached(), false); let revertible: Revertible | undefined; - view.events.on("commitApplied", (_, getRevertible) => { + view.events.on("changed", (_, getRevertible) => { revertible = getRevertible?.(); }); view.root.foo = 2; diff --git a/packages/dds/tree/src/test/simple-tree/api/create.spec.ts b/packages/dds/tree/src/test/simple-tree/api/create.spec.ts index d18262c94418..4c74be3324cd 100644 --- a/packages/dds/tree/src/test/simple-tree/api/create.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/create.spec.ts @@ -6,8 +6,7 @@ import { strict as assert } from "node:assert"; import { createFromInsertable, SchemaFactory } from "../../../simple-tree/index.js"; -// eslint-disable-next-line import/no-internal-modules -import { createFromVerbose } from "../../../simple-tree/api/create.js"; +import { TreeAlpha } from "../../../shared-tree/index.js"; const schema = new SchemaFactory("com.example"); @@ -25,7 +24,7 @@ describe("simple-tree create", () => { }); it("createFromVerbose", () => { - const canvas1 = createFromVerbose(Canvas, { + const canvas1 = TreeAlpha.importVerbose(Canvas, { type: Canvas.identifier, fields: { stuff: { type: NodeList.identifier, fields: [] } }, }); diff --git a/packages/dds/tree/src/test/simple-tree/api/getSimpleSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/api/getSimpleSchema.spec.ts index 0c6444f120b9..39989301449b 100644 --- a/packages/dds/tree/src/test/simple-tree/api/getSimpleSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/getSimpleSchema.spec.ts @@ -33,8 +33,8 @@ describe("getSimpleSchema", () => { }, ], ]), + metadata: { description: "An optional string." }, allowedTypes: new Set(["com.fluidframework.leaf.string"]), - description: "An optional string.", }; assert.deepEqual(actual, expected); }); diff --git a/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts b/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts index 2c0a51b34e01..2ff896ebc044 100644 --- a/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/schemaFactoryRecursive.spec.ts @@ -21,6 +21,7 @@ import { type InternalTreeNode, type FlexListToUnion, type ApplyKindInput, + type NodeBuilderData, } from "../../../simple-tree/index.js"; import type { ValidateRecursiveSchema, @@ -579,27 +580,50 @@ describe("SchemaFactory Recursive methods", () => { }); describe("mapRecursive", () => { - it("simple", () => { - class MapRecursive extends sf.mapRecursive("Map", [() => MapRecursive]) {} - { - type _check = ValidateRecursiveSchema; - } + class MapRecursive extends sf.mapRecursive("Map", [() => MapRecursive]) {} + { + type _check = ValidateRecursiveSchema; + } + + it("basic use", () => { const node = hydrate(MapRecursive, new MapRecursive([])); const data = [...node]; assert.deepEqual(data, []); // Nested { - type T = InsertableTreeNodeFromImplicitAllowedTypes; - const _check: T = new MapRecursive([]); + type TInsert = InsertableTreeNodeFromImplicitAllowedTypes; + const _check: TInsert = new MapRecursive([]); + // Only explicitly constructed recursive maps are currently allowed: - type _check = requireTrue>; + type _check1 = requireTrue>; + + // Check constructor + type TBuild = NodeBuilderData; + type _check2 = requireAssignableTo; + type _check3 = requireAssignableTo<[], TBuild>; + type _check4 = requireAssignableTo<[[string, TInsert]], TBuild>; } node.set("x", new MapRecursive([])); node.get("x")?.set("x", new MapRecursive(new Map())); }); + + it("constructors", () => { + const fromIterator = new MapRecursive([["x", new MapRecursive()]]); + const fromMap = new MapRecursive(new Map([["x", new MapRecursive()]])); + const fromObject = new MapRecursive({ x: new MapRecursive() }); + + const fromNothing = new MapRecursive(); + const fromUndefined = new MapRecursive(undefined); + + // If supporting implicit construction, these would work: + // @ts-expect-error Implicit construction disabled + const fromNestedNeverArray = new MapRecursive({ x: [] }); + // @ts-expect-error Implicit construction disabled + const fromNestedObject = new MapRecursive({ x: { x: [] } }); + }); }); it("recursive under non-recursive", () => { diff --git a/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts b/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts index 36e78f2c9871..7f199f91b245 100644 --- a/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/simpleSchemaToJsonSchema.spec.ts @@ -172,12 +172,19 @@ describe("simpleSchemaToJsonSchema", () => { "foo": { kind: FieldKind.Optional, allowedTypes: new Set(["test.number"]), - description: "A number representing the concept of Foo.", + metadata: { description: "A number representing the concept of Foo." }, }, "bar": { kind: FieldKind.Required, allowedTypes: new Set(["test.string"]), - description: "A string representing the concept of Bar.", + metadata: { description: "A string representing the concept of Bar." }, + }, + "id": { + kind: FieldKind.Identifier, + allowedTypes: new Set(["test.string"]), + metadata: { + description: "Unique identifier for the test object.", + }, }, }, }, @@ -204,6 +211,10 @@ describe("simpleSchemaToJsonSchema", () => { $ref: "#/$defs/test.string", description: "A string representing the concept of Bar.", }, + id: { + $ref: "#/$defs/test.string", + description: "Unique identifier for the test object.", + }, }, required: ["bar"], additionalProperties: false, diff --git a/packages/dds/tree/src/test/simple-tree/api/treeApi.spec.ts b/packages/dds/tree/src/test/simple-tree/api/treeApi.spec.ts index fac5e80a94e6..72de90a45980 100644 --- a/packages/dds/tree/src/test/simple-tree/api/treeApi.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/treeApi.spec.ts @@ -17,13 +17,16 @@ import { type StableNodeKey, } from "../../../feature-libraries/index.js"; import { + isTreeNode, type NodeFromSchema, SchemaFactory, treeNodeApi as Tree, TreeBeta, type TreeChangeEvents, + type TreeLeafValue, type TreeNode, TreeViewConfiguration, + type UnsafeUnknownSchema, } from "../../../simple-tree/index.js"; import { getView, validateUsageError } from "../../utils.js"; import { getViewForForkedBranch, hydrate } from "../utils.js"; @@ -39,6 +42,10 @@ import { } from "../../../simple-tree/leafNodeSchema.js"; // eslint-disable-next-line import/no-internal-modules import { tryGetSchema } from "../../../simple-tree/api/treeNodeApi.js"; +import { testSimpleTrees } from "../../testTrees.js"; +import { FluidClientVersion } from "../../../codec/index.js"; +import { ajvValidator } from "../../codec/index.js"; +import { TreeAlpha } from "../../../shared-tree/index.js"; const schema = new SchemaFactory("com.example"); @@ -1116,5 +1123,247 @@ describe("treeNodeApi", () => { const clonedMetadata = TreeBeta.clone(topLeftPoint.metadata); assert.equal(clonedMetadata, topLeftPoint.metadata, "String not cloned properly"); }); + + describe("test-trees", () => { + for (const testCase of testSimpleTrees) { + it(testCase.name, () => { + const tree = TreeAlpha.create(testCase.schema, testCase.root()); + const exported = TreeBeta.clone(tree); + if (isTreeNode(tree)) { + // New instance + assert.notEqual(tree, exported); + } + expectTreesEqual(tree, exported); + }); + } + }); + }); + + // create is mostly the same as node constructors which have their own tests, so just cover the new cases (optional and top level unions) here. + describe("create", () => { + it("undefined", () => { + // Valid + assert.equal(TreeAlpha.create(schema.optional([]), undefined), undefined); + // Undefined where not allowed + assert.throws( + () => TreeAlpha.create(schema.required([]), undefined as never), + validateUsageError(/undefined for non-optional field/), + ); + // Undefined required, not provided + assert.throws( + () => TreeAlpha.create(schema.optional([]), 1 as unknown as undefined), + validateUsageError(/incompatible/), + ); + }); + + it("union", () => { + // Valid + assert.equal(TreeAlpha.create([schema.null, schema.number], null), null); + // invalid + assert.throws( + () => TreeAlpha.create([schema.null, schema.number], "x" as unknown as number), + validateUsageError(/incompatible/), + ); + }); + + // Integration test object complex objects work (mainly covered by tests elsewhere) + it("object", () => { + const A = schema.object("A", { x: schema.number }); + const a = TreeAlpha.create(A, { x: 1 }); + assert.deepEqual(a, { x: 1 }); + }); + }); + + describe("concise", () => { + describe("importConcise", () => { + it("undefined", () => { + // Valid + assert.equal(TreeAlpha.importConcise(schema.optional([]), undefined), undefined); + // Undefined where not allowed + assert.throws( + () => TreeAlpha.importConcise(schema.required([]), undefined), + validateUsageError(/Got undefined for non-optional field/), + ); + // Undefined required, not provided + assert.throws( + () => TreeAlpha.importConcise(schema.optional([]), 1), + validateUsageError(/incompatible with all of the types allowed/), + ); + }); + + it("union", () => { + // Valid + assert.equal(TreeAlpha.importConcise([schema.null, schema.number], null), null); + // invalid + assert.throws( + () => TreeAlpha.importConcise([schema.null, schema.number], "x"), + validateUsageError(/The provided data is incompatible/), + ); + }); + + it("object", () => { + const A = schema.object("A", { x: schema.number }); + const a = TreeAlpha.importConcise(A, { x: 1 }); + assert.deepEqual(a, { x: 1 }); + }); + }); + + describe("roundtrip", () => { + for (const testCase of testSimpleTrees) { + if (testCase.root() !== undefined) { + it(testCase.name, () => { + const tree = TreeAlpha.create( + testCase.schema, + testCase.root(), + ); + assert(tree !== undefined); + const exported = TreeAlpha.exportConcise(tree); + if (testCase.ambiguous) { + assert.throws( + () => TreeAlpha.importConcise(testCase.schema, exported), + validateUsageError(/compatible with more than one type/), + ); + } else { + const imported = TreeAlpha.importConcise( + testCase.schema, + exported, + ); + expectTreesEqual(tree, imported); + } + }); + } + } + }); + + describe("export-stored", () => { + for (const testCase of testSimpleTrees) { + if (testCase.root() !== undefined) { + it(testCase.name, () => { + const tree = TreeAlpha.create( + testCase.schema, + testCase.root(), + ); + assert(tree !== undefined); + const _exported = TreeAlpha.exportConcise(tree, { useStoredKeys: true }); + // We have nothing that imports concise trees with stored keys, so no validation here. + }); + } + } + }); + }); + + describe("verbose", () => { + describe("importVerbose", () => { + it("undefined", () => { + // Valid + assert.equal(TreeAlpha.importVerbose(schema.optional([]), undefined), undefined); + // Undefined where not allowed + assert.throws( + () => TreeAlpha.importVerbose(schema.required([]), undefined), + validateUsageError(/non-optional/), + ); + // Undefined required, not provided + assert.throws( + () => TreeAlpha.importVerbose(schema.optional([]), 1), + validateUsageError(/does not conform to schema/), + ); + }); + + it("union", () => { + // Valid + assert.equal(TreeAlpha.importVerbose([schema.null, schema.number], null), null); + // invalid + assert.throws( + () => TreeAlpha.importVerbose([schema.null, schema.number], "x"), + validateUsageError(/does not conform to schema/), + ); + }); + + it("object", () => { + const A = schema.object("A", { x: schema.number }); + const a = TreeAlpha.importVerbose(A, { type: A.identifier, fields: { x: 1 } }); + assert.deepEqual(a, { x: 1 }); + }); + }); + + describe("roundtrip", () => { + for (const testCase of testSimpleTrees) { + if (testCase.root() !== undefined) { + it(testCase.name, () => { + const tree = TreeAlpha.create( + testCase.schema, + testCase.root(), + ); + assert(tree !== undefined); + const exported = TreeAlpha.exportVerbose(tree); + const imported = TreeAlpha.importVerbose(testCase.schema, exported); + expectTreesEqual(tree, imported); + }); + } + } + }); + + describe("roundtrip-stored", () => { + for (const testCase of testSimpleTrees) { + if (testCase.root() !== undefined) { + it(testCase.name, () => { + const tree = TreeAlpha.create( + testCase.schema, + testCase.root(), + ); + assert(tree !== undefined); + const exported = TreeAlpha.exportVerbose(tree, { useStoredKeys: true }); + const imported = TreeAlpha.importVerbose(testCase.schema, exported, { + useStoredKeys: true, + }); + expectTreesEqual(tree, imported); + }); + } + } + }); + }); + + describe("compressed", () => { + describe("roundtrip", () => { + for (const testCase of testSimpleTrees) { + if (testCase.root() !== undefined) { + it(testCase.name, () => { + const tree = TreeAlpha.create( + testCase.schema, + testCase.root(), + ); + assert(tree !== undefined); + const exported = TreeAlpha.exportCompressed(tree, { + oldestCompatibleClient: FluidClientVersion.v2_0, + }); + const imported = TreeAlpha.importCompressed(testCase.schema, exported, { + jsonValidator: ajvValidator, + }); + expectTreesEqual(tree, imported); + }); + } + } + }); }); }); + +function expectTreesEqual( + a: TreeNode | TreeLeafValue | undefined, + b: TreeNode | TreeLeafValue | undefined, +): void { + if (a === undefined || b === undefined) { + assert.equal(a === undefined, b === undefined); + return; + } + + // Validate the same schema objects are used. + assert.equal(Tree.schema(a), Tree.schema(b)); + + // This should catch all cases, assuming exportVerbose works correctly. + assert.deepEqual(TreeAlpha.exportVerbose(a), TreeAlpha.exportVerbose(b)); + + // Since this uses some of the tools to compare trees that this is testing for, perform the comparison in a few ways to reduce risk of a bug making this pass when it shouldn't: + // This case could have false negatives (two trees with ambiguous schema could export the same concise tree), + // but should have no false positives since equal trees always have the same concise tree. + assert.deepEqual(TreeAlpha.exportConcise(a), TreeAlpha.exportConcise(b)); +} diff --git a/packages/dds/tree/src/test/simple-tree/arrayNode.spec.ts b/packages/dds/tree/src/test/simple-tree/arrayNode.spec.ts index 1119002157b0..956a0e653893 100644 --- a/packages/dds/tree/src/test/simple-tree/arrayNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/arrayNode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; import { describeHydration, hydrate } from "./utils.js"; import { diff --git a/packages/dds/tree/src/test/simple-tree/core/treeNodeKernel.spec.ts b/packages/dds/tree/src/test/simple-tree/core/treeNodeKernel.spec.ts index 1ce1e9613b53..a996793ddb2f 100644 --- a/packages/dds/tree/src/test/simple-tree/core/treeNodeKernel.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/core/treeNodeKernel.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; import { createIdCompressor } from "@fluidframework/id-compressor/internal"; diff --git a/packages/dds/tree/src/test/simple-tree/core/unhydratedFlexTree.spec.ts b/packages/dds/tree/src/test/simple-tree/core/unhydratedFlexTree.spec.ts index a8413d988036..06d69a77c751 100644 --- a/packages/dds/tree/src/test/simple-tree/core/unhydratedFlexTree.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/core/unhydratedFlexTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { FieldKinds, type FlexTreeOptionalField } from "../../../feature-libraries/index.js"; import { diff --git a/packages/dds/tree/src/test/simple-tree/flexList.spec.ts b/packages/dds/tree/src/test/simple-tree/flexList.spec.ts index 2afb9f3e9eea..d2f8df62a74e 100644 --- a/packages/dds/tree/src/test/simple-tree/flexList.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/flexList.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type FlexList, diff --git a/packages/dds/tree/src/test/simple-tree/list.spec.ts b/packages/dds/tree/src/test/simple-tree/list.spec.ts index 863882bc768f..d9a11e569fe5 100644 --- a/packages/dds/tree/src/test/simple-tree/list.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/list.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { SchemaFactory } from "../../simple-tree/index.js"; @@ -95,6 +95,7 @@ describe("List", () => { // An older technique for detecting arrays is to use the 'instanceof' operator. // However, this is not reliable in the presence of multiple global contexts (e.g., // if an Array is passed across frame boundaries.) + // eslint-disable-next-line unicorn/no-instanceof-array test0("instanceof Array", (target: unknown) => target instanceof Array); // 'deepEquals' requires that objects have the same prototype to be considered equal. @@ -194,8 +195,8 @@ describe("List", () => { check(/* length: */ 2, /* index: */ 0); check(/* length: */ 2, /* index: */ 1); check(/* length: */ 2, /* index: */ -2); - check(/* length: */ 2, /* index: */ Infinity); - check(/* length: */ 2, /* index: */ -Infinity); + check(/* length: */ 2, /* index: */ Number.POSITIVE_INFINITY); + check(/* length: */ 2, /* index: */ Number.NEGATIVE_INFINITY); }); describe("[Symbol.isConcatSpreadable] matches array defaults", () => { @@ -345,8 +346,8 @@ describe("List", () => { check([]); check(["a"]); check(["a", "b"]); - check(["a", "b"], -Infinity); - check(["a", "b"], 0, Infinity); + check(["a", "b"], Number.NEGATIVE_INFINITY); + check(["a", "b"], 0, Number.POSITIVE_INFINITY); for (let i = 0; i < 4; i++) { check(["a", "b"], i); @@ -511,8 +512,8 @@ describe("List", () => { check(["a", "b"], "a", /* start: */ -1); check(["a", "b"], "b", /* start: */ -1); check(["a", "b"], "a", /* start: */ -2); - check(["a", "b"], "a", /* start: */ Infinity); - check(["a", "b"], "a", /* start: */ -Infinity); + check(["a", "b"], "a", /* start: */ Number.POSITIVE_INFINITY); + check(["a", "b"], "a", /* start: */ Number.NEGATIVE_INFINITY); }); describe("indexOf()", () => { @@ -528,8 +529,8 @@ describe("List", () => { check(["a", "b"], "a", /* start: */ -1); check(["a", "b"], "b", /* start: */ -1); check(["a", "b"], "a", /* start: */ -2); - check(["a", "b"], "a", /* start: */ Infinity); - check(["a", "b"], "a", /* start: */ -Infinity); + check(["a", "b"], "a", /* start: */ Number.POSITIVE_INFINITY); + check(["a", "b"], "a", /* start: */ Number.NEGATIVE_INFINITY); }); describe("at()", () => { @@ -562,8 +563,8 @@ describe("List", () => { check(["a", "b"], -3.000001); // Truncated to -3 - out of bounds check(["a", "b"], -3.5); // Truncated to -3 - out of bounds // Extreme values - check(["a", "b"], Infinity); - check(["a", "b"], -Infinity); + check(["a", "b"], Number.POSITIVE_INFINITY); + check(["a", "b"], Number.NEGATIVE_INFINITY); check(["a", "b"], Number.MAX_SAFE_INTEGER); check(["a", "b"], Number.MIN_SAFE_INTEGER); check(["a", "b"], Number.MAX_VALUE); @@ -579,7 +580,7 @@ describe("List", () => { check(["a", "b"], "-2.999999"); check(["a", "b"], "-3.0"); check(["a", "b"], "not-a-number"); - check(["a", "b"], NaN); + check(["a", "b"], Number.NaN); check(["a", "b"], true); check(["a", "b"], false); check(["a", "b"], undefined); @@ -625,8 +626,8 @@ describe("List", () => { check(["a", "b"], "a", /* start: */ -1); check(["a", "b"], "b", /* start: */ -1); check(["a", "b"], "a", /* start: */ -2); - check(["a", "b"], "a", /* start: */ Infinity); - check(["a", "b"], "a", /* start: */ -Infinity); + check(["a", "b"], "a", /* start: */ Number.POSITIVE_INFINITY); + check(["a", "b"], "a", /* start: */ Number.NEGATIVE_INFINITY); }); describe("some()", () => { diff --git a/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts b/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts index 013d6ffb3228..b9574df4a9b8 100644 --- a/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/mapNode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { SchemaFactory, type NodeFromSchema } from "../../simple-tree/index.js"; import { describeHydration } from "./utils.js"; diff --git a/packages/dds/tree/src/test/simple-tree/object.spec.ts b/packages/dds/tree/src/test/simple-tree/object.spec.ts index d5017e391a90..b9329d1f2a00 100644 --- a/packages/dds/tree/src/test/simple-tree/object.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/object.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type ImplicitFieldSchema, @@ -193,10 +193,10 @@ function testObjectLike(testCases: TestCaseErased[]) { test1((subject) => { try { return Object.prototype.toLocaleString.call(subject); - } catch (e: unknown) { - assert(e instanceof Error); + } catch (error: unknown) { + assert(error instanceof Error); // toLocaleString errors if there is a field named toString. - return e.message; + return error.message; } }); }); @@ -235,10 +235,10 @@ function testObjectLike(testCases: TestCaseErased[]) { try { // eslint-disable-next-line @typescript-eslint/no-base-to-string return subject.toString(); - } catch (e: unknown) { - assert(e instanceof Error); + } catch (error: unknown) { + assert(error instanceof Error); // toString errors if there is a field named toString. - return e.message; + return error.message; } }); }); @@ -248,10 +248,10 @@ function testObjectLike(testCases: TestCaseErased[]) { test1((subject) => { try { return subject.toLocaleString(); - } catch (e: unknown) { - assert(e instanceof Error); + } catch (error: unknown) { + assert(error instanceof Error); // toLocaleString errors if there is a field named toString. - return e.message; + return error.message; } }); }); diff --git a/packages/dds/tree/src/test/simple-tree/objectFactory.spec.ts b/packages/dds/tree/src/test/simple-tree/objectFactory.spec.ts index 58273362f8b7..a0be3132a502 100644 --- a/packages/dds/tree/src/test/simple-tree/objectFactory.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/objectFactory.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type InsertableTreeFieldFromImplicitField, diff --git a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts index e094b8faddc0..c818240df935 100644 --- a/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/objectNode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; @@ -277,11 +277,11 @@ describeHydration( class Root extends schemaFactory.object("", { x: [schemaFactory.number, schemaFactory.null], }) {} - const node = init(Root, { x: NaN }); + const node = init(Root, { x: Number.NaN }); assert.equal(node.x, null); node.x = 6; assert.equal(node.x, 6); - node.x = Infinity; + node.x = Number.POSITIVE_INFINITY; assert.equal(node.x, null); node.x = -0; assert(Object.is(node.x, 0)); @@ -293,7 +293,7 @@ describeHydration( }) {} const node = init(Root, { x: 1 }); assert.throws(() => { - node.x = NaN; + node.x = Number.NaN; }, validateUsageError(/NaN/)); assert.equal(node.x, 1); node.x = -0; diff --git a/packages/dds/tree/src/test/simple-tree/primitives.spec.ts b/packages/dds/tree/src/test/simple-tree/primitives.spec.ts index 5649f2de9e95..d5371550d2b9 100644 --- a/packages/dds/tree/src/test/simple-tree/primitives.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/primitives.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type ImplicitFieldSchema, @@ -157,7 +157,7 @@ describe("Primitives", () => { // JSON coerces non-finite numbers to 'null'. If 'null' violates schema, // this must throw a TypeError. - [-Infinity, NaN, Infinity].forEach((value) => { + [Number.NEGATIVE_INFINITY, Number.NaN, Number.POSITIVE_INFINITY].forEach((value) => { checkThrows(schema, value); }); @@ -171,7 +171,9 @@ describe("Primitives", () => { // JSON coerces non-finite numbers to 'null'. This succeeds when 'null' is // permitted by schema. const schema = [schemaFactory.number, schemaFactory.null] as const; - [-Infinity, NaN, Infinity].forEach((value) => checkCoerced(schema, value)); + [Number.NEGATIVE_INFINITY, Number.NaN, Number.POSITIVE_INFINITY].forEach((value) => + checkCoerced(schema, value), + ); }); }); diff --git a/packages/dds/tree/src/test/simple-tree/proxies.spec.ts b/packages/dds/tree/src/test/simple-tree/proxies.spec.ts index 07d8580dba87..f49a491de54e 100644 --- a/packages/dds/tree/src/test/simple-tree/proxies.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/proxies.spec.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { MockHandle } from "@fluidframework/test-runtime-utils/internal"; +// TODO: import and unit test other things from "proxies" file. + +import { MockNodeKeyManager } from "../../feature-libraries/index.js"; import { type booleanSchema, type InsertableTreeNodeFromImplicitAllowedTypes, @@ -17,14 +20,10 @@ import { type TreeNodeSchema, TreeViewConfiguration, } from "../../simple-tree/index.js"; - -// TODO: import and unit test other things from "proxies" file. -// // eslint-disable-next-line import/no-internal-modules +import type { requireAssignableTo } from "../../util/index.js"; +import { getView } from "../utils.js"; import { hydrate, pretty } from "./utils.js"; -import { getView } from "../utils.js"; -import { MockNodeKeyManager } from "../../feature-libraries/index.js"; -import type { requireAssignableTo } from "../../util/index.js"; describe("simple-tree proxies", () => { const sb = new SchemaFactory("test"); diff --git a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts index e9f85c578071..e9092290dd88 100644 --- a/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/schemaTypes.spec.ts @@ -32,6 +32,7 @@ import { type TreeFieldFromImplicitField, type TreeLeafValue, type TreeNodeFromImplicitAllowedTypes, + areImplicitFieldSchemaEqual, normalizeAllowedTypes, // eslint-disable-next-line import/no-internal-modules } from "../../simple-tree/schemaTypes.js"; @@ -439,4 +440,47 @@ describe("schemaTypes", () => { ); }); }); + + it("areImplicitFieldSchemaEqual", () => { + const sf = new SchemaFactory("test"); + function check(a: ImplicitFieldSchema, b: ImplicitFieldSchema, expected: boolean) { + assert.equal(areImplicitFieldSchemaEqual(a, b), expected); + } + + check(sf.number, sf.number, true); // Same type + check(sf.number, sf.string, false); // Different types + check([sf.number], sf.number, true); // Array vs. single + check([sf.number], [sf.number], true); // Both arrays + check([sf.number, sf.string], [sf.number, sf.string], true); // Multiple types + check([sf.number, sf.string], [sf.string, sf.number], true); // Multiple types in different order + check(sf.required(sf.number), sf.number, true); // Explicit vs. implicit + check(sf.required(sf.number), [sf.number], true); // Explicit vs. implicit in array + check(sf.required([sf.number, sf.string]), [sf.string, sf.number], true); // Multiple explicit vs. implicit + check(sf.required(sf.number), sf.optional(sf.number), false); // Different kinds + check(sf.required(sf.number), sf.required(sf.number, {}), true); // One with empty props + check(sf.required(sf.number, { key: "a" }), sf.required(sf.number, { key: "a" }), true); // Props with same key + check(sf.required(sf.number, { key: "a" }), sf.required(sf.number, { key: "b" }), false); // Props with different key + check(sf.required(sf.number, {}), sf.required(sf.number, { metadata: {} }), true); // One with empty metadata + check( + sf.required(sf.number, { metadata: { description: "a" } }), + sf.required(sf.number, { metadata: { description: "a" } }), + true, + ); // Same description + check( + sf.required(sf.number, { metadata: { description: "a" } }), + sf.required(sf.number, { metadata: { description: "b" } }), + false, + ); // Different description + check( + sf.required(sf.number, { metadata: { custom: "a" } }), + sf.required(sf.number, { metadata: { custom: "a" } }), + true, + ); // Same custom metadata + check( + sf.required(sf.number, { metadata: { custom: "a" } }), + sf.required(sf.number, { metadata: { custom: "b" } }), + false, + ); // Different custom metadata + check(sf.identifier, sf.optional(sf.string), false); // Identifier vs. optional string + }); }); diff --git a/packages/dds/tree/src/test/simple-tree/toMapTree.spec.ts b/packages/dds/tree/src/test/simple-tree/toMapTree.spec.ts index 3b3cb23fb20f..983f730ec153 100644 --- a/packages/dds/tree/src/test/simple-tree/toMapTree.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/toMapTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { MockHandle, @@ -1629,7 +1629,7 @@ describe("toMapTree", () => { }); describe("deepCopyMapTree", () => { - /** Used by `generateMapTree` to give unique types and values to each MapTree */ + // Used by `generateMapTree` to give unique types and values to each MapTree let mapTreeGeneration = 0; function generateMapTree(depth: number): ExclusiveMapTree { const generation = mapTreeGeneration++; diff --git a/packages/dds/tree/src/test/simple-tree/treeContext.spec.ts b/packages/dds/tree/src/test/simple-tree/treeContext.spec.ts new file mode 100644 index 000000000000..d0cac62e889b --- /dev/null +++ b/packages/dds/tree/src/test/simple-tree/treeContext.spec.ts @@ -0,0 +1,172 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { + SchemaFactory, + TreeViewConfiguration, + type TreeView, + type TreeViewAlpha, +} from "../../simple-tree/index.js"; + +import { getView } from "../utils.js"; +import { TreeAlpha } from "../../shared-tree/index.js"; +import type { requireAssignableTo } from "../../util/index.js"; +import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; + +describe("TreeBranch", () => { + const schemaFactory = new SchemaFactory(undefined); + class Array extends schemaFactory.array("array", schemaFactory.string) {} + + function init(content: string[]): TreeViewAlpha { + const view = getView( + new TreeViewConfiguration({ schema: Array, enableSchemaValidation: true }), + ); + view.initialize(content); + return view; + } + + { + // Test that branching from a TreeView returns a typed view (as opposed to an untyped context). + const view = init([]); + const branch = view.fork(); + type _check = requireAssignableTo; + } + + it("can downcast to a view", () => { + const view = init(["a", "b", "c"]); + const array = view.root; + const context = TreeAlpha.branch(array); + assert(context !== undefined); + assert.equal(context.hasRootSchema(Array), true); + assert.equal(context.hasRootSchema(schemaFactory.number), false); + assert.deepEqual([...array], ["a", "b", "c"]); + }); + + describe("branches", () => { + function newBranch(view: TreeView) { + const context = TreeAlpha.branch(view.root); + assert(context !== undefined); + const branch = context.fork(); + assert(branch.hasRootSchema(Array)); + return branch; + } + + it("can downcast to a view", () => { + const view = init(["a", "b", "c"]); + const branch = newBranch(view); + assert(branch.hasRootSchema(Array)); + assert.deepEqual([...branch.root], ["a", "b", "c"]); + }); + + it("can be edited", () => { + const view = init(["a", "b", "c"]); + const branch = newBranch(view); + branch.root.removeAt(0); + branch.root.insertAtEnd("d"); + assert.deepEqual([...branch.root], ["b", "c", "d"]); + }); + + it("are isolated from their parent's changes", () => { + const view = init(["x"]); + const branch = newBranch(view); + view.root.removeAt(0); + view.root.insertAtStart("y"); + assert.deepEqual([...view.root], ["y"]); + assert.deepEqual([...branch.root], ["x"]); + }); + + it("are isolated from their children's changes", () => { + const view = init(["x"]); + const branch = newBranch(view); + branch.root.removeAt(0); + branch.root.insertAtStart("y"); + assert.deepEqual([...view.root], ["x"]); + assert.deepEqual([...branch.root], ["y"]); + const branchBranch = newBranch(branch); + branchBranch.root.removeAt(0); + branchBranch.root.insertAtStart("z"); + assert.deepEqual([...view.root], ["x"]); + assert.deepEqual([...branch.root], ["y"]); + assert.deepEqual([...branchBranch.root], ["z"]); + }); + + it("can rebase a child over a parent", () => { + const view = init(["x"]); + const branch = newBranch(view); + view.root.removeAt(0); + view.root.insertAtStart("y"); + branch.rebaseOnto(view); + assert.deepEqual([...view.root], ["y"]); + assert.deepEqual([...branch.root], ["y"]); + }); + + it("can rebase a parent over a child", () => { + const view = init(["x"]); + const branch = newBranch(view); + const branchBranch = newBranch(branch); + branchBranch.root.removeAt(0); + branchBranch.root.insertAtStart("y"); + branch.rebaseOnto(branchBranch); + assert.deepEqual([...view.root], ["x"]); + assert.deepEqual([...branch.root], ["y"]); + assert.deepEqual([...branchBranch.root], ["y"]); + assert.throws( + () => view.rebaseOnto(branch), + (e: Error) => + validateAssertionError(e, /The main branch cannot be rebased onto another branch./), + ); + }); + + it("can merge a child into a parent", () => { + const view = init(["x"]); + const branch = newBranch(view); + branch.root.removeAt(0); + branch.root.insertAtStart("y"); + view.merge(branch, false); + assert.deepEqual([...view.root], ["y"]); + assert.deepEqual([...branch.root], ["y"]); + }); + + it("can merge a parent into a child", () => { + const view = init(["x"]); + const branch = newBranch(view); + const branchBranch = newBranch(branch); + branch.root.removeAt(0); + branch.root.insertAtStart("y"); + branchBranch.merge(branch, false); + assert.deepEqual([...view.root], ["x"]); + assert.deepEqual([...branch.root], ["y"]); + assert.deepEqual([...branchBranch.root], ["y"]); + view.root.removeAt(0); + view.root.insertAtStart("z"); + branch.merge(view); // No need to pass `false` here, because it's the main branch + assert.deepEqual([...branch.root], ["z", "y"]); + }); + + it("can be manually disposed", () => { + const view = init(["x"]); + const branch = newBranch(view); + branch.dispose(); + assert.throws(() => { + branch.root.removeAt(0); + }, /disposed/); + }); + + it("are properly disposed after merging", () => { + const view = init(["x"]); + const branch = newBranch(view); + branch.merge(view, true); // Should not dispose, because it's the main branch + branch.merge(view); // Should not dispose, because it's the main branch + view.merge(branch, false); // Should not dispose, because we passed 'false' + branch.root.removeAt(0); + view.merge(branch); // Should dispose, because default is 'true' + assert.throws(() => { + branch.root.insertAtStart("y"); + }, /disposed/); + }); + }); +}); diff --git a/packages/dds/tree/src/test/simple-tree/unhydratedNode.spec.ts b/packages/dds/tree/src/test/simple-tree/unhydratedNode.spec.ts index 01ca505b8de2..dc20a83d1ece 100644 --- a/packages/dds/tree/src/test/simple-tree/unhydratedNode.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/unhydratedNode.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { Tree } from "../../shared-tree/index.js"; import { rootFieldKey } from "../../core/index.js"; diff --git a/packages/dds/tree/src/test/snapshots/gc.spec.ts b/packages/dds/tree/src/test/snapshots/gc.spec.ts index 1122ec7c5eee..77a3bdd00a93 100644 --- a/packages/dds/tree/src/test/snapshots/gc.spec.ts +++ b/packages/dds/tree/src/test/snapshots/gc.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type IGCTestProvider, runGCTests } from "@fluid-private/test-dds-utils"; import { toFluidHandleInternal } from "@fluidframework/runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/snapshots/snapshotTestScenarios.ts b/packages/dds/tree/src/test/snapshots/snapshotTestScenarios.ts index cf6a9ed86402..67b99e71c4a3 100644 --- a/packages/dds/tree/src/test/snapshots/snapshotTestScenarios.ts +++ b/packages/dds/tree/src/test/snapshots/snapshotTestScenarios.ts @@ -333,19 +333,20 @@ export function generateTestTrees(options: SharedTreeOptions) { name: "nested-sequence-change", runScenario: async (takeSnapshot) => { const sf = new SchemaFactory("test trees"); - class SequenceMap extends sf.mapRecursive("Recursive Map", [ - () => sf.array(SequenceMap), + class Array extends sf.arrayRecursive('Array<["test trees.Recursive Map"]>', [ + () => SequenceMap, ]) {} + class SequenceMap extends sf.mapRecursive("Recursive Map", [() => Array]) {} const provider = new TestTreeProviderLite(1, factory, true); const tree = provider.trees[0]; const view = tree.viewWith( new TreeViewConfiguration({ - schema: [sf.array(SequenceMap)], + schema: Array, enableSchemaValidation, }), ); - view.initialize([]); + view.initialize(new Array([])); provider.processMessages(); // We must make this shallow change to the sequence field as part of the same transaction as the @@ -354,7 +355,8 @@ export function generateTestTrees(options: SharedTreeOptions) { view.root.insertAtStart(new SequenceMap([])); const map = view.root[0]; const innerArray: SequenceMap[] = []; - map.set("foo", [new SequenceMap([["bar", innerArray]])]); + map.set("foo", new Array([new SequenceMap([["bar", new Array(innerArray)]])])); + // Since innerArray is an array, not an actual node, this does nothing (other than ensure innerArray was copied and thus the tree was not modified by this change) innerArray.push(new SequenceMap([])); }); diff --git a/packages/dds/tree/src/test/snapshots/snapshotTools.ts b/packages/dds/tree/src/test/snapshots/snapshotTools.ts index b7b76ccc203b..1b446e593ce5 100644 --- a/packages/dds/tree/src/test/snapshots/snapshotTools.ts +++ b/packages/dds/tree/src/test/snapshots/snapshotTools.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"; -import path from "path"; +import { strict as assert } from "node:assert"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; import type { JsonCompatibleReadOnly } from "../../util/index.js"; import { testSrcPath } from "../testSrcPath.cjs"; diff --git a/packages/dds/tree/src/test/snapshots/summary.spec.ts b/packages/dds/tree/src/test/snapshots/summary.spec.ts index 75e6e1d15187..9befc24496a9 100644 --- a/packages/dds/tree/src/test/snapshots/summary.spec.ts +++ b/packages/dds/tree/src/test/snapshots/summary.spec.ts @@ -5,8 +5,9 @@ import { TreeCompressionStrategy } from "../../feature-libraries/index.js"; import { SharedTreeFormatVersion, type SharedTreeOptions } from "../../shared-tree/index.js"; -import { useSnapshotDirectory } from "./snapshotTools.js"; + import { generateTestTrees } from "./snapshotTestScenarios.js"; +import { useSnapshotDirectory } from "./snapshotTools.js"; import { takeSummarySnapshot } from "./utils.js"; describe("snapshot tests", () => { diff --git a/packages/dds/tree/src/test/snapshots/utils.ts b/packages/dds/tree/src/test/snapshots/utils.ts index 2a2404c27ae3..15ad7895dce7 100644 --- a/packages/dds/tree/src/test/snapshots/utils.ts +++ b/packages/dds/tree/src/test/snapshots/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { Uint8ArrayToString } from "@fluid-internal/client-utils"; import { @@ -20,12 +20,15 @@ function getSummaryTypeName(summaryObject: SummaryObject): "blob" | "tree" { switch (type) { case SummaryType.Blob: - case SummaryType.Attachment: + case SummaryType.Attachment: { return "blob"; - case SummaryType.Tree: + } + case SummaryType.Tree: { return "tree"; - default: + } + default: { throw new Error(`Unknown type: ${type}`); + } } } @@ -66,7 +69,7 @@ function serializeTree(parentHandle: string, tree: ISummaryTree, rootNodeName: s } case SummaryType.Handle: { if (!parentHandle) { - throw Error("Parent summary does not exist to reference by handle."); + throw new Error("Parent summary does not exist to reference by handle."); } let handlePath = summaryObject.handle; if (handlePath.length > 0 && !handlePath.startsWith("/")) { diff --git a/packages/dds/tree/src/test/testChange.spec.ts b/packages/dds/tree/src/test/testChange.spec.ts index de8a5ab37f66..72b6e2a76bfd 100644 --- a/packages/dds/tree/src/test/testChange.spec.ts +++ b/packages/dds/tree/src/test/testChange.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { SessionId } from "@fluidframework/id-compressor"; diff --git a/packages/dds/tree/src/test/testChange.ts b/packages/dds/tree/src/test/testChange.ts index 858ca3715728..360a0afd5332 100644 --- a/packages/dds/tree/src/test/testChange.ts +++ b/packages/dds/tree/src/test/testChange.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { type IJsonCodec, makeCodecFamily } from "../codec/index.js"; import { diff --git a/packages/dds/tree/src/test/testNodeId.ts b/packages/dds/tree/src/test/testNodeId.ts index be08b8a52705..f8a19b0937fe 100644 --- a/packages/dds/tree/src/test/testNodeId.ts +++ b/packages/dds/tree/src/test/testNodeId.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { fail } from "assert"; +import { fail } from "node:assert"; import type { NodeId } from "../feature-libraries/index.js"; import { type ChangeEncodingContext, diff --git a/packages/dds/tree/src/test/testTrees.ts b/packages/dds/tree/src/test/testTrees.ts index 057f5a0090e0..7a426bfe3cc3 100644 --- a/packages/dds/tree/src/test/testTrees.ts +++ b/packages/dds/tree/src/test/testTrees.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert, fail } from "assert"; +import { strict as assert, fail } from "node:assert"; import { MockHandle } from "@fluidframework/test-runtime-utils/internal"; diff --git a/packages/dds/tree/src/test/testUtilsTests.spec.ts b/packages/dds/tree/src/test/testUtilsTests.spec.ts index 6f86ad29fb9c..6dd8a6a06a1d 100644 --- a/packages/dds/tree/src/test/testUtilsTests.spec.ts +++ b/packages/dds/tree/src/test/testUtilsTests.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { JsonableTree } from "../core/index.js"; import { brand } from "../util/index.js"; diff --git a/packages/dds/tree/src/test/tree/anchorSet.spec.ts b/packages/dds/tree/src/test/tree/anchorSet.spec.ts index acc2ef2c87d6..cacff34f634d 100644 --- a/packages/dds/tree/src/test/tree/anchorSet.spec.ts +++ b/packages/dds/tree/src/test/tree/anchorSet.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type Anchor, @@ -416,8 +416,8 @@ describe("AnchorSet", () => { // AnchorSet does not guarantee event ordering within a batch so use UnorderedTestLogger. const log = new UnorderedTestLogger(); const anchors = new AnchorSet(); - anchors.on("childrenChanging", log.logger("root childrenChange")); - anchors.on("treeChanging", log.logger("root treeChange")); + anchors.events.on("childrenChanging", log.logger("root childrenChange")); + anchors.events.on("treeChanging", log.logger("root treeChange")); const detachMark: DeltaMark = { count: 1, @@ -436,10 +436,10 @@ describe("AnchorSet", () => { const anchor0 = anchors.track(makePath([rootFieldKey, 0])); const node0 = anchors.locate(anchor0) ?? assert.fail(); - node0.on("childrenChanging", log.logger("childrenChanging")); - node0.on("childrenChanged", log.logger("childrenChanged")); - node0.on("subtreeChanging", log.logger("subtreeChange")); - node0.on("afterDestroy", log.logger("afterDestroy")); + node0.events.on("childrenChanging", log.logger("childrenChanging")); + node0.events.on("childrenChanged", log.logger("childrenChanged")); + node0.events.on("subtreeChanging", log.logger("subtreeChange")); + node0.events.on("afterDestroy", log.logger("afterDestroy")); log.expect([]); @@ -506,7 +506,7 @@ describe("AnchorSet", () => { const expectedChangedFields = new Set([fieldOne, fieldTwo, fieldThree]); let listenerFired = false; - node0.on("childrenChangedAfterBatch", ({ changedFields }) => { + node0.events.on("childrenChangedAfterBatch", ({ changedFields }) => { // This is the main validation of this test assert.deepEqual(changedFields, expectedChangedFields); listenerFired = true; @@ -653,7 +653,10 @@ describe("AnchorSet", () => { )(); }, }; - const unsubscribePathVisitor = node0.on("subtreeChanging", (n: AnchorNode) => pathVisitor); + const unsubscribePathVisitor = node0.events.on( + "subtreeChanging", + (n: AnchorNode) => pathVisitor, + ); announceTestDelta(insertAtFoo4, anchors, undefined, undefined, build); log.expect([ ["visitSubtreeChange.beforeAttach-src:Temp-0[0, 1]-dst:foo[4]", 1], diff --git a/packages/dds/tree/src/test/tree/detachedFieldIndex.spec.ts b/packages/dds/tree/src/test/tree/detachedFieldIndex.spec.ts index 25e266610e73..72527b52dffa 100644 --- a/packages/dds/tree/src/test/tree/detachedFieldIndex.spec.ts +++ b/packages/dds/tree/src/test/tree/detachedFieldIndex.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import type { IIdCompressor } from "@fluidframework/id-compressor"; import { createIdCompressor } from "@fluidframework/id-compressor/internal"; diff --git a/packages/dds/tree/src/test/tree/pathTree.spec.ts b/packages/dds/tree/src/test/tree/pathTree.spec.ts index eb4745d00b85..8ad17b6a35b0 100644 --- a/packages/dds/tree/src/test/tree/pathTree.spec.ts +++ b/packages/dds/tree/src/test/tree/pathTree.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type FieldKey, diff --git a/packages/dds/tree/src/test/tree/visitDelta.spec.ts b/packages/dds/tree/src/test/tree/visitDelta.spec.ts index 0ddfea1cf471..562994fb5c46 100644 --- a/packages/dds/tree/src/test/tree/visitDelta.spec.ts +++ b/packages/dds/tree/src/test/tree/visitDelta.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type DeltaDetachedNodeBuild, diff --git a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts index 2401026015f5..989b9ebdb489 100644 --- a/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts +++ b/packages/dds/tree/src/test/types/validateTreePrevious.generated.ts @@ -292,7 +292,8 @@ declare type current_as_old_for_Interface_InternalTypes_TreeApi = requireAssigna * typeValidation.broken: * "Interface_InternalTypes_TreeArrayNodeBase": {"backCompat": false} */ -declare type current_as_old_for_Interface_InternalTypes_TreeArrayNodeBase = requireAssignableTo>, TypeOnly>> +// @ts-expect-error compatibility expected to be broken +declare type current_as_old_for_Interface_InternalTypes_TreeArrayNodeBase = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. @@ -312,6 +313,24 @@ declare type current_as_old_for_Interface_InternalTypes_TreeArrayNodeUnsafe = re */ declare type current_as_old_for_Interface_InternalTypes_TreeMapNodeUnsafe = requireAssignableTo>, TypeOnly>> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "Interface_InternalTypes_TreeNodeSchemaNonClassUnsafe": {"forwardCompat": false} + */ +declare type old_as_current_for_Interface_InternalTypes_TreeNodeSchemaNonClassUnsafe = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "Interface_InternalTypes_TreeNodeSchemaNonClassUnsafe": {"backCompat": false} + */ +declare type current_as_old_for_Interface_InternalTypes_TreeNodeSchemaNonClassUnsafe = requireAssignableTo>, TypeOnly>> + /* * Validate backward compatibility by using the current type in place of the old type. * If this test starts failing, it indicates a change that is not backward compatible. @@ -346,7 +365,6 @@ declare type current_as_old_for_Interface_ITreeConfigurationOptions = requireAss * typeValidation.broken: * "Interface_ITreeViewConfiguration": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_Interface_ITreeViewConfiguration = requireAssignableTo, TypeOnly> /* @@ -394,6 +412,15 @@ declare type old_as_current_for_Interface_NodeInDocumentConstraint = requireAssi */ declare type current_as_old_for_Interface_NodeInDocumentConstraint = requireAssignableTo, TypeOnly> +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "Interface_ReadonlyArrayNode": {"backCompat": false} + */ +declare type current_as_old_for_Interface_ReadonlyArrayNode = requireAssignableTo, TypeOnly> + /* * Validate backward compatibility by using the current type in place of the old type. * If this test starts failing, it indicates a change that is not backward compatible. @@ -428,7 +455,6 @@ declare type current_as_old_for_Interface_SchemaCompatibilityStatus = requireAss * typeValidation.broken: * "Interface_TreeArrayNode": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_TreeArrayNode = requireAssignableTo, TypeOnly> /* @@ -477,31 +503,31 @@ declare type current_as_old_for_Interface_TreeMapNodeUnsafe = requireAssignableT declare type current_as_old_for_Interface_TreeNodeApi = requireAssignableTo, TypeOnly> /* - * Validate backward compatibility by using the current type in place of the old type. - * If this test starts failing, it indicates a change that is not backward compatible. + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. * To acknowledge the breaking change, add the following to package.json under * typeValidation.broken: - * "Interface_TreeNodeSchemaClass": {"backCompat": false} + * "Interface_TreeNodeSchemaClassUnsafe": {"forwardCompat": false} */ -declare type current_as_old_for_Interface_TreeNodeSchemaClass = requireAssignableTo, TypeOnly> +declare type old_as_current_for_Interface_TreeNodeSchemaClassUnsafe = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. * If this test starts failing, it indicates a change that is not backward compatible. * To acknowledge the breaking change, add the following to package.json under * typeValidation.broken: - * "Interface_TreeNodeSchemaCore": {"backCompat": false} + * "Interface_TreeNodeSchemaClassUnsafe": {"backCompat": false} */ -declare type current_as_old_for_Interface_TreeNodeSchemaCore = requireAssignableTo>, TypeOnly>> +declare type current_as_old_for_Interface_TreeNodeSchemaClassUnsafe = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. * If this test starts failing, it indicates a change that is not backward compatible. * To acknowledge the breaking change, add the following to package.json under * typeValidation.broken: - * "Interface_TreeNodeSchemaNonClass": {"backCompat": false} + * "Interface_TreeNodeSchemaCore": {"backCompat": false} */ -declare type current_as_old_for_Interface_TreeNodeSchemaNonClass = requireAssignableTo, TypeOnly> +declare type current_as_old_for_Interface_TreeNodeSchemaCore = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. @@ -564,7 +590,6 @@ declare type current_as_old_for_TypeAlias_AllowedTypes = requireAssignableTo, TypeOnly> /* @@ -583,7 +608,6 @@ declare type current_as_old_for_TypeAlias_ImplicitAllowedTypes = requireAssignab * typeValidation.broken: * "TypeAlias_ImplicitFieldSchema": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_ImplicitFieldSchema = requireAssignableTo, TypeOnly> /* @@ -595,6 +619,26 @@ declare type old_as_current_for_TypeAlias_ImplicitFieldSchema = requireAssignabl */ declare type current_as_old_for_TypeAlias_ImplicitFieldSchema = requireAssignableTo, TypeOnly> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_Input": {"forwardCompat": false} + */ +// @ts-expect-error compatibility expected to be broken +declare type old_as_current_for_TypeAlias_Input = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_Input": {"backCompat": false} + */ +// @ts-expect-error compatibility expected to be broken +declare type current_as_old_for_TypeAlias_Input = requireAssignableTo>, TypeOnly>> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -620,8 +664,7 @@ declare type current_as_old_for_TypeAlias_InsertableObjectFromSchemaRecordUnsafe * typeValidation.broken: * "TypeAlias_InsertableTreeFieldFromImplicitField": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitField = requireAssignableTo, TypeOnly> +declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitField = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. @@ -630,8 +673,7 @@ declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitField = * typeValidation.broken: * "TypeAlias_InsertableTreeFieldFromImplicitField": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitField = requireAssignableTo, TypeOnly> +declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitField = requireAssignableTo>, TypeOnly>> /* * Validate forward compatibility by using the old type in place of the current type. @@ -640,7 +682,6 @@ declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitField = * typeValidation.broken: * "TypeAlias_InsertableTreeFieldFromImplicitFieldUnsafe": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitFieldUnsafe = requireAssignableTo>, TypeOnly>> /* @@ -652,6 +693,42 @@ declare type old_as_current_for_TypeAlias_InsertableTreeFieldFromImplicitFieldUn */ declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitFieldUnsafe = requireAssignableTo>, TypeOnly>> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InsertableTreeNodeFromAllowedTypes": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromAllowedTypes = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InsertableTreeNodeFromAllowedTypes": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_InsertableTreeNodeFromAllowedTypes = requireAssignableTo>, TypeOnly>> + +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InsertableTreeNodeFromAllowedTypesUnsafe": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromAllowedTypesUnsafe = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InsertableTreeNodeFromAllowedTypesUnsafe": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_InsertableTreeNodeFromAllowedTypesUnsafe = requireAssignableTo>, TypeOnly>> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -659,8 +736,7 @@ declare type current_as_old_for_TypeAlias_InsertableTreeFieldFromImplicitFieldUn * typeValidation.broken: * "TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes = requireAssignableTo, TypeOnly> +declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. @@ -669,8 +745,7 @@ declare type old_as_current_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedT * typeValidation.broken: * "TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type current_as_old_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes = requireAssignableTo, TypeOnly> +declare type current_as_old_for_TypeAlias_InsertableTreeNodeFromImplicitAllowedTypes = requireAssignableTo>, TypeOnly>> /* * Validate forward compatibility by using the old type in place of the current type. @@ -726,6 +801,24 @@ declare type old_as_current_for_TypeAlias_InternalTypes__InlineTrick = requireAs */ declare type current_as_old_for_TypeAlias_InternalTypes__InlineTrick = requireAssignableTo, TypeOnly> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_AllowedTypesUnsafe": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_InternalTypes_AllowedTypesUnsafe = requireAssignableTo, TypeOnly> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_AllowedTypesUnsafe": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_InternalTypes_AllowedTypesUnsafe = requireAssignableTo, TypeOnly> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -733,8 +826,7 @@ declare type current_as_old_for_TypeAlias_InternalTypes__InlineTrick = requireAs * typeValidation.broken: * "TypeAlias_InternalTypes_ApplyKind": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type old_as_current_for_TypeAlias_InternalTypes_ApplyKind = requireAssignableTo>, TypeOnly>> +declare type old_as_current_for_TypeAlias_InternalTypes_ApplyKind = requireAssignableTo>, TypeOnly>> /* * Validate backward compatibility by using the current type in place of the old type. @@ -743,8 +835,25 @@ declare type old_as_current_for_TypeAlias_InternalTypes_ApplyKind = requireAssig * typeValidation.broken: * "TypeAlias_InternalTypes_ApplyKind": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken -declare type current_as_old_for_TypeAlias_InternalTypes_ApplyKind = requireAssignableTo>, TypeOnly>> +declare type current_as_old_for_TypeAlias_InternalTypes_ApplyKind = requireAssignableTo>, TypeOnly>> + +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_ApplyKindInput": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_InternalTypes_ApplyKindInput = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_ApplyKindInput": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_InternalTypes_ApplyKindInput = requireAssignableTo>, TypeOnly>> /* * Validate forward compatibility by using the old type in place of the current type. @@ -852,7 +961,6 @@ declare type current_as_old_for_TypeAlias_InternalTypes_FlexListToUnion = requir * typeValidation.broken: * "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord = requireAssignableTo>, TypeOnly>> /* @@ -862,7 +970,6 @@ declare type old_as_current_for_TypeAlias_InternalTypes_InsertableObjectFromSche * typeValidation.broken: * "TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_InternalTypes_InsertableObjectFromSchemaRecord = requireAssignableTo>, TypeOnly>> /* @@ -890,7 +997,6 @@ declare type current_as_old_for_TypeAlias_InternalTypes_InsertableObjectFromSche * typeValidation.broken: * "TypeAlias_InternalTypes_InsertableTreeFieldFromImplicitFieldUnsafe": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_TypeAlias_InternalTypes_InsertableTreeFieldFromImplicitFieldUnsafe = requireAssignableTo>, TypeOnly>> /* @@ -1082,6 +1188,24 @@ declare type old_as_current_for_TypeAlias_InternalTypes_TreeNodeFromImplicitAllo */ declare type current_as_old_for_TypeAlias_InternalTypes_TreeNodeFromImplicitAllowedTypesUnsafe = requireAssignableTo>, TypeOnly>> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_TreeNodeSchemaUnsafe": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_InternalTypes_TreeNodeSchemaUnsafe = requireAssignableTo, TypeOnly> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_InternalTypes_TreeNodeSchemaUnsafe": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_InternalTypes_TreeNodeSchemaUnsafe = requireAssignableTo, TypeOnly> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -1188,7 +1312,6 @@ declare type old_as_current_for_TypeAlias_NodeFromSchema = requireAssignableTo>, TypeOnly>> /* @@ -1288,7 +1411,6 @@ declare type old_as_current_for_TypeAlias_TreeFieldFromImplicitField = requireAs * typeValidation.broken: * "TypeAlias_TreeFieldFromImplicitField": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_TreeFieldFromImplicitField = requireAssignableTo, TypeOnly> /* @@ -1325,7 +1447,6 @@ declare type old_as_current_for_TypeAlias_TreeNodeFromImplicitAllowedTypes = req * typeValidation.broken: * "TypeAlias_TreeNodeFromImplicitAllowedTypes": {"backCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_TypeAlias_TreeNodeFromImplicitAllowedTypes = requireAssignableTo, TypeOnly> /* @@ -1337,6 +1458,24 @@ declare type current_as_old_for_TypeAlias_TreeNodeFromImplicitAllowedTypes = req */ declare type current_as_old_for_TypeAlias_TreeNodeSchema = requireAssignableTo, TypeOnly> +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_TreeNodeSchemaClass": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_TreeNodeSchemaClass = requireAssignableTo, TypeOnly> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_TreeNodeSchemaNonClass": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_TreeNodeSchemaNonClass = requireAssignableTo, TypeOnly> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -1409,6 +1548,24 @@ declare type old_as_current_for_TypeAlias_Unhydrated = requireAssignableTo>, TypeOnly>> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_UnionToIntersection": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_UnionToIntersection = requireAssignableTo>, TypeOnly>> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_UnionToIntersection": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_UnionToIntersection = requireAssignableTo>, TypeOnly>> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. diff --git a/packages/dds/tree/src/test/util/breakable.spec.ts b/packages/dds/tree/src/test/util/breakable.spec.ts index ba79fe133e93..780ea979d66f 100644 --- a/packages/dds/tree/src/test/util/breakable.spec.ts +++ b/packages/dds/tree/src/test/util/breakable.spec.ts @@ -16,7 +16,7 @@ import { import { validateUsageError } from "../utils.js"; describe("Breakable", () => { - const breakError = Error("BreakFoo"); + const breakError = new Error("BreakFoo"); class Foo implements WithBreakable { public readonly breaker: Breakable = new Breakable("Foo"); diff --git a/packages/dds/tree/src/test/util/nestedMap.spec.ts b/packages/dds/tree/src/test/util/nestedMap.spec.ts index 50dee167f4f8..ff1e2eaa90b1 100644 --- a/packages/dds/tree/src/test/util/nestedMap.spec.ts +++ b/packages/dds/tree/src/test/util/nestedMap.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { type NestedMap, diff --git a/packages/dds/tree/src/test/util/offsetList.spec.ts b/packages/dds/tree/src/test/util/offsetList.spec.ts index 6a2b08b61303..de5347955eec 100644 --- a/packages/dds/tree/src/test/util/offsetList.spec.ts +++ b/packages/dds/tree/src/test/util/offsetList.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { OffsetListFactory } from "../../util/index.js"; diff --git a/packages/dds/tree/src/test/util/testTreeProvider.spec.ts b/packages/dds/tree/src/test/util/testTreeProvider.spec.ts index 126a21f00932..8b11d7f1bd73 100644 --- a/packages/dds/tree/src/test/util/testTreeProvider.spec.ts +++ b/packages/dds/tree/src/test/util/testTreeProvider.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import assert from "assert"; +import assert from "node:assert"; import { SharedTreeCore } from "../../shared-tree-core/index.js"; import { SummarizeType, TestTreeProvider, spyOnMethod } from "../utils.js"; diff --git a/packages/dds/tree/src/test/util/utils.spec.ts b/packages/dds/tree/src/test/util/utils.spec.ts index b0a65b24bc34..830040e1b805 100644 --- a/packages/dds/tree/src/test/util/utils.spec.ts +++ b/packages/dds/tree/src/test/util/utils.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { capitalize, mapIterable, transformObjectMap } from "../../util/index.js"; import { benchmark } from "@fluid-tools/benchmark"; diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index 629d1cf67d42..e59db37a61f3 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -4,13 +4,13 @@ */ import { strict as assert } from "node:assert"; + import { createMockLoggerExt, type IMockLoggerExt, type ITelemetryLoggerExt, UsageError, } from "@fluidframework/telemetry-utils/internal"; - import { makeRandom } from "@fluid-private/stochastic-test-utils"; import { LocalServerTestDriver } from "@fluid-private/test-drivers"; import type { IContainer } from "@fluidframework/container-definitions/internal"; @@ -132,8 +132,8 @@ import { type TreeViewConfiguration, SchemaFactory, toStoredSchema, - type TreeViewEvents, type TreeView, + type TreeBranchEvents, } from "../simple-tree/index.js"; import { type JsonCompatible, @@ -1047,7 +1047,7 @@ export function rootFromDeltaFieldMap( } export function createTestUndoRedoStacks( - events: Listenable, + events: Listenable, ): { undoStack: Revertible[]; redoStack: Revertible[]; @@ -1079,9 +1079,9 @@ export function createTestUndoRedoStacks( } } - const unsubscribeFromCommitApplied = events.on("commitApplied", onNewCommit); + const unsubscribeFromChangedEvent = events.on("changed", onNewCommit); const unsubscribe = (): void => { - unsubscribeFromCommitApplied(); + unsubscribeFromChangedEvent(); for (const revertible of undoStack) { revertible.dispose(); } diff --git a/packages/dds/tree/src/util/index.ts b/packages/dds/tree/src/util/index.ts index d53fdc710156..913985198364 100644 --- a/packages/dds/tree/src/util/index.ts +++ b/packages/dds/tree/src/util/index.ts @@ -52,6 +52,7 @@ export type { requireFalse, requireTrue, requireAssignableTo, + areOnlyKeys, } from "./typeCheck.js"; export { StackyIterator } from "./stackyIterator.js"; export { diff --git a/packages/dds/tree/src/util/nestedMap.ts b/packages/dds/tree/src/util/nestedMap.ts index 3db1bfc7f8d7..b668b503823f 100644 --- a/packages/dds/tree/src/util/nestedMap.ts +++ b/packages/dds/tree/src/util/nestedMap.ts @@ -4,6 +4,7 @@ */ import { oob } from "@fluidframework/core-utils/internal"; + import type { MapGetSet } from "./utils.js"; /** diff --git a/packages/dds/tree/src/util/typeCheck.ts b/packages/dds/tree/src/util/typeCheck.ts index ad126dc293df..932c63af5f5d 100644 --- a/packages/dds/tree/src/util/typeCheck.ts +++ b/packages/dds/tree/src/util/typeCheck.ts @@ -190,3 +190,16 @@ export type isAny = boolean extends (T extends never ? true : false) ? true : * `type _check = requireAssignableTo;` */ export type requireAssignableTo<_A extends B, B> = true; + +/** + * Returns a type parameter that is true iff the `Keys` union includes all the keys of `T`. + * @example + * ```ts + * type _check = requireTrue> // true` + * type _check = requireTrue> // false` + * ``` + */ +export type areOnlyKeys = isAssignableTo< + Record, + Omit +>; diff --git a/packages/dds/tree/src/util/utils.ts b/packages/dds/tree/src/util/utils.ts index 7cb358c240a4..09874b71d880 100644 --- a/packages/dds/tree/src/util/utils.ts +++ b/packages/dds/tree/src/util/utils.ts @@ -105,18 +105,26 @@ export function compareSets({ }): boolean { for (const item of a.keys()) { if (!b.has(item)) { - if (aExtra && !aExtra(item)) { + if (aExtra !== undefined) { + if (!aExtra(item)) { + return false; + } + } else { return false; } } else { - if (same && !same(item)) { + if (same !== undefined && !same(item)) { return false; } } } for (const item of b.keys()) { if (!a.has(item)) { - if (bExtra && !bExtra(item)) { + if (bExtra !== undefined) { + if (!bExtra(item)) { + return false; + } + } else { return false; } } @@ -219,19 +227,24 @@ export function count(iterable: Iterable): number { /** * Use for Json compatible data. + * + * @typeparam TExtra - Type permitted in addition to the normal JSON types. + * Commonly used for to allow {@link @fluidframework/core-interfaces#IFluidHandle} within the otherwise JSON compatible content. + * * @remarks * This does not robustly forbid non json comparable data via type checking, * but instead mostly restricts access to it. * @alpha */ -export type JsonCompatible = +export type JsonCompatible = | string | number | boolean // eslint-disable-next-line @rushstack/no-new-null | null - | JsonCompatible[] - | JsonCompatibleObject; + | JsonCompatible[] + | JsonCompatibleObject + | TExtra; /** * Use for Json object compatible data. @@ -240,7 +253,7 @@ export type JsonCompatible = * but instead mostly restricts access to it. * @alpha */ -export type JsonCompatibleObject = { [P in string]?: JsonCompatible }; +export type JsonCompatibleObject = { [P in string]?: JsonCompatible }; /** * Use for readonly view of Json compatible data. diff --git a/packages/drivers/debugger/.eslintrc.cjs b/packages/drivers/debugger/.eslintrc.cjs index e6a8ec0cd616..cc6e5b280cf6 100644 --- a/packages/drivers/debugger/.eslintrc.cjs +++ b/packages/drivers/debugger/.eslintrc.cjs @@ -14,5 +14,6 @@ module.exports = { "@typescript-eslint/strict-boolean-expressions": "off", "import/no-nodejs-modules": "off", "no-inner-declarations": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/drivers/debugger/CHANGELOG.md b/packages/drivers/debugger/CHANGELOG.md index 3f4f8560b46b..fd6512c71fdc 100644 --- a/packages/drivers/debugger/CHANGELOG.md +++ b/packages/drivers/debugger/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/debugger +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/debugger/package.json b/packages/drivers/debugger/package.json index dc26dce653a8..7ea50841f8b5 100644 --- a/packages/drivers/debugger/package.json +++ b/packages/drivers/debugger/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/debugger", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid Debugger - a tool to play through history of a file", "homepage": "https://fluidframework.com", "repository": { @@ -93,10 +93,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/debugger-previous": "npm:@fluidframework/debugger@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/debugger-previous": "npm:@fluidframework/debugger@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/drivers/driver-base/CHANGELOG.md b/packages/drivers/driver-base/CHANGELOG.md index 32ddbea79921..f5a6c4951a88 100644 --- a/packages/drivers/driver-base/CHANGELOG.md +++ b/packages/drivers/driver-base/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/driver-base +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/driver-base/package.json b/packages/drivers/driver-base/package.json index 4ae3c7de3122..2b1d1db286d8 100644 --- a/packages/drivers/driver-base/package.json +++ b/packages/drivers/driver-base/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/driver-base", - "version": "2.5.0", + "version": "2.10.0", "description": "Shared driver code for Fluid driver implementations", "homepage": "https://fluidframework.com", "repository": { @@ -110,10 +110,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/driver-base-previous": "npm:@fluidframework/driver-base@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/driver-base-previous": "npm:@fluidframework/driver-base@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/drivers/driver-base/src/packageVersion.ts b/packages/drivers/driver-base/src/packageVersion.ts index 195d8d743ea3..15d84a56b4c1 100644 --- a/packages/drivers/driver-base/src/packageVersion.ts +++ b/packages/drivers/driver-base/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/driver-base"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/drivers/driver-web-cache/.eslintrc.cjs b/packages/drivers/driver-web-cache/.eslintrc.cjs index 4fdca2b680bf..57a6eaf3fc2f 100644 --- a/packages/drivers/driver-web-cache/.eslintrc.cjs +++ b/packages/drivers/driver-web-cache/.eslintrc.cjs @@ -17,5 +17,6 @@ module.exports = { "@typescript-eslint/strict-boolean-expressions": "off", "@typescript-eslint/promise-function-async": "off", "@typescript-eslint/no-misused-promises": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/drivers/driver-web-cache/CHANGELOG.md b/packages/drivers/driver-web-cache/CHANGELOG.md index a3cbd6be654c..8f65ff5789fa 100644 --- a/packages/drivers/driver-web-cache/CHANGELOG.md +++ b/packages/drivers/driver-web-cache/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/driver-web-cache +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/driver-web-cache/package.json b/packages/drivers/driver-web-cache/package.json index 1d4c3385162e..c68f9b1975aa 100644 --- a/packages/drivers/driver-web-cache/package.json +++ b/packages/drivers/driver-web-cache/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/driver-web-cache", - "version": "2.5.0", + "version": "2.10.0", "description": "Implementation of the driver caching API for a web browser", "homepage": "https://fluidframework.com", "repository": { @@ -100,10 +100,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/driver-web-cache-previous": "npm:@fluidframework/driver-web-cache@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/driver-web-cache-previous": "npm:@fluidframework/driver-web-cache@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/jest": "29.5.3", diff --git a/packages/drivers/driver-web-cache/src/packageVersion.ts b/packages/drivers/driver-web-cache/src/packageVersion.ts index 0662ddaef912..3ffdc4f2a6f6 100644 --- a/packages/drivers/driver-web-cache/src/packageVersion.ts +++ b/packages/drivers/driver-web-cache/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/driver-web-cache"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/drivers/file-driver/CHANGELOG.md b/packages/drivers/file-driver/CHANGELOG.md index be3f5c373fc4..cd97b23fbac1 100644 --- a/packages/drivers/file-driver/CHANGELOG.md +++ b/packages/drivers/file-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/file-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/file-driver/package.json b/packages/drivers/file-driver/package.json index 8d632ee27981..f1edf87d895c 100644 --- a/packages/drivers/file-driver/package.json +++ b/packages/drivers/file-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/file-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "A driver that reads/write from/to local file storage.", "homepage": "https://fluidframework.com", "repository": { @@ -76,11 +76,11 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/file-driver-previous": "npm:@fluidframework/file-driver@~2.4.0", + "@fluidframework/file-driver-previous": "npm:@fluidframework/file-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", "concurrently": "^8.2.1", diff --git a/packages/drivers/local-driver/.eslintrc.cjs b/packages/drivers/local-driver/.eslintrc.cjs index 1c65568f7669..070154952d39 100644 --- a/packages/drivers/local-driver/.eslintrc.cjs +++ b/packages/drivers/local-driver/.eslintrc.cjs @@ -13,6 +13,7 @@ module.exports = { }, rules: { "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/drivers/local-driver/CHANGELOG.md b/packages/drivers/local-driver/CHANGELOG.md index 6a0436a34e63..57b17ea9145c 100644 --- a/packages/drivers/local-driver/CHANGELOG.md +++ b/packages/drivers/local-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/local-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/local-driver/package.json b/packages/drivers/local-driver/package.json index 90d157ec651f..0ed0f5893a26 100644 --- a/packages/drivers/local-driver/package.json +++ b/packages/drivers/local-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/local-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid local driver", "homepage": "https://fluidframework.com", "repository": { @@ -138,11 +138,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/local-driver-previous": "npm:@fluidframework/local-driver@~2.4.0", + "@fluidframework/local-driver-previous": "npm:@fluidframework/local-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/jsrsasign": "^10.5.12", "@types/mocha": "^9.1.1", diff --git a/packages/drivers/odsp-driver-definitions/CHANGELOG.md b/packages/drivers/odsp-driver-definitions/CHANGELOG.md index 79b8490b8e88..02fa6a328df6 100644 --- a/packages/drivers/odsp-driver-definitions/CHANGELOG.md +++ b/packages/drivers/odsp-driver-definitions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/odsp-driver-definitions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/odsp-driver-definitions/package.json b/packages/drivers/odsp-driver-definitions/package.json index bad42498ab93..4674474c9539 100644 --- a/packages/drivers/odsp-driver-definitions/package.json +++ b/packages/drivers/odsp-driver-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/odsp-driver-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Socket storage implementation for SPO and ODC", "homepage": "https://fluidframework.com", "repository": { @@ -93,11 +93,11 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/odsp-driver-definitions-previous": "npm:@fluidframework/odsp-driver-definitions@~2.4.0", + "@fluidframework/odsp-driver-definitions-previous": "npm:@fluidframework/odsp-driver-definitions@2.5.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", "copyfiles": "^2.4.1", diff --git a/packages/drivers/odsp-driver/.eslintrc.cjs b/packages/drivers/odsp-driver/.eslintrc.cjs index df69aaf1121e..6758df724712 100644 --- a/packages/drivers/odsp-driver/.eslintrc.cjs +++ b/packages/drivers/odsp-driver/.eslintrc.cjs @@ -16,6 +16,7 @@ module.exports = { // This library uses and serializes "utf-8". "unicorn/text-encoding-identifier-case": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/drivers/odsp-driver/CHANGELOG.md b/packages/drivers/odsp-driver/CHANGELOG.md index b5e412e74848..60e302a4b5d6 100644 --- a/packages/drivers/odsp-driver/CHANGELOG.md +++ b/packages/drivers/odsp-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/odsp-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/odsp-driver/package.json b/packages/drivers/odsp-driver/package.json index daeeb3d44a6e..01a1f485c9e8 100644 --- a/packages/drivers/odsp-driver/package.json +++ b/packages/drivers/odsp-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/odsp-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "Socket storage implementation for SPO and ODC", "homepage": "https://fluidframework.com", "repository": { @@ -134,11 +134,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/odsp-driver-previous": "npm:@fluidframework/odsp-driver@~2.4.0", + "@fluidframework/odsp-driver-previous": "npm:@fluidframework/odsp-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/drivers/odsp-driver/src/createFile.ts b/packages/drivers/odsp-driver/src/createFile/createFile.ts similarity index 93% rename from packages/drivers/odsp-driver/src/createFile.ts rename to packages/drivers/odsp-driver/src/createFile/createFile.ts index 11aed612dfdd..ab283dd23afd 100644 --- a/packages/drivers/odsp-driver/src/createFile.ts +++ b/packages/drivers/odsp-driver/src/createFile/createFile.ts @@ -20,27 +20,27 @@ import { PerformanceEvent, } from "@fluidframework/telemetry-utils/internal"; -import { ICreateFileResponse } from "./contracts.js"; -import { ClpCompliantAppHeader } from "./contractsPublic.js"; -import { - convertCreateNewSummaryTreeToTreeAndBlobs, - convertSummaryIntoContainerSnapshot, - createNewFluidContainerCore, -} from "./createNewUtils.js"; -import { createOdspUrl } from "./createOdspUrl.js"; -import { EpochTracker } from "./epochTracker.js"; -import { getHeadersWithAuth } from "./getUrlAndHeadersWithAuth.js"; -import { OdspDriverUrlResolver } from "./odspDriverUrlResolver.js"; -import { getApiRoot } from "./odspUrlHelper.js"; +import { ICreateFileResponse } from "./../contracts.js"; +import { ClpCompliantAppHeader } from "./../contractsPublic.js"; +import { createOdspUrl } from "./../createOdspUrl.js"; +import { EpochTracker } from "./../epochTracker.js"; +import { getHeadersWithAuth } from "./../getUrlAndHeadersWithAuth.js"; +import { OdspDriverUrlResolver } from "./../odspDriverUrlResolver.js"; +import { getApiRoot } from "./../odspUrlHelper.js"; import { INewFileInfo, buildOdspShareLinkReqParams, createCacheSnapshotKey, getWithRetryForTokenRefresh, snapshotWithLoadingGroupIdSupported, -} from "./odspUtils.js"; -import { pkgVersion as driverVersion } from "./packageVersion.js"; -import { runWithRetry } from "./retryUtils.js"; +} from "./../odspUtils.js"; +import { pkgVersion as driverVersion } from "./../packageVersion.js"; +import { runWithRetry } from "./../retryUtils.js"; +import { + convertCreateNewSummaryTreeToTreeAndBlobs, + convertSummaryIntoContainerSnapshot, + createNewFluidContainerCore, +} from "./createNewUtils.js"; const isInvalidFileName = (fileName: string): boolean => { const invalidCharsRegex = /["*/:<>?\\|]+/g; diff --git a/packages/drivers/odsp-driver/src/createNewContainerOnExistingFile.ts b/packages/drivers/odsp-driver/src/createFile/createNewContainerOnExistingFile.ts similarity index 89% rename from packages/drivers/odsp-driver/src/createNewContainerOnExistingFile.ts rename to packages/drivers/odsp-driver/src/createFile/createNewContainerOnExistingFile.ts index ca883ccb3781..b1836b170432 100644 --- a/packages/drivers/odsp-driver/src/createNewContainerOnExistingFile.ts +++ b/packages/drivers/odsp-driver/src/createFile/createNewContainerOnExistingFile.ts @@ -16,22 +16,22 @@ import { loggerToMonitoringContext, } from "@fluidframework/telemetry-utils/internal"; -import { IWriteSummaryResponse } from "./contracts.js"; -import { ClpCompliantAppHeader } from "./contractsPublic.js"; +import { IWriteSummaryResponse } from "./../contracts.js"; +import { ClpCompliantAppHeader } from "./../contractsPublic.js"; +import { createOdspUrl } from "./../createOdspUrl.js"; +import { EpochTracker } from "./../epochTracker.js"; +import { OdspDriverUrlResolver } from "./../odspDriverUrlResolver.js"; +import { getApiRoot } from "./../odspUrlHelper.js"; +import { + IExistingFileInfo, + createCacheSnapshotKey, + snapshotWithLoadingGroupIdSupported, +} from "./../odspUtils.js"; import { convertCreateNewSummaryTreeToTreeAndBlobs, convertSummaryIntoContainerSnapshot, createNewFluidContainerCore, } from "./createNewUtils.js"; -import { createOdspUrl } from "./createOdspUrl.js"; -import { EpochTracker } from "./epochTracker.js"; -import { OdspDriverUrlResolver } from "./odspDriverUrlResolver.js"; -import { getApiRoot } from "./odspUrlHelper.js"; -import { - IExistingFileInfo, - createCacheSnapshotKey, - snapshotWithLoadingGroupIdSupported, -} from "./odspUtils.js"; /** * Creates a new Fluid container on an existing file. diff --git a/packages/drivers/odsp-driver/src/createNewModule.ts b/packages/drivers/odsp-driver/src/createFile/createNewModule.ts similarity index 76% rename from packages/drivers/odsp-driver/src/createNewModule.ts rename to packages/drivers/odsp-driver/src/createFile/createNewModule.ts index 743f10c4fe9a..0690a35ab96b 100644 --- a/packages/drivers/odsp-driver/src/createNewModule.ts +++ b/packages/drivers/odsp-driver/src/createFile/createNewModule.ts @@ -5,3 +5,4 @@ export { createNewFluidFile } from "./createFile.js"; export { createNewContainerOnExistingFile } from "./createNewContainerOnExistingFile.js"; +export { convertCreateNewSummaryTreeToTreeAndBlobs } from "./createNewUtils.js"; diff --git a/packages/drivers/odsp-driver/src/createNewUtils.ts b/packages/drivers/odsp-driver/src/createFile/createNewUtils.ts similarity index 97% rename from packages/drivers/odsp-driver/src/createNewUtils.ts rename to packages/drivers/odsp-driver/src/createFile/createNewUtils.ts index bbb5c3fe45df..62f34971fbab 100644 --- a/packages/drivers/odsp-driver/src/createNewUtils.ts +++ b/packages/drivers/odsp-driver/src/createFile/createNewUtils.ts @@ -29,11 +29,11 @@ import { IOdspSummaryTree, OdspSummaryTreeEntry, OdspSummaryTreeValue, -} from "./contracts.js"; -import { EpochTracker, FetchType } from "./epochTracker.js"; -import { getHeadersWithAuth } from "./getUrlAndHeadersWithAuth.js"; -import { getWithRetryForTokenRefresh, maxUmpPostBodySize } from "./odspUtils.js"; -import { runWithRetry } from "./retryUtils.js"; +} from "./../contracts.js"; +import { EpochTracker, FetchType } from "./../epochTracker.js"; +import { getHeadersWithAuth } from "./../getUrlAndHeadersWithAuth.js"; +import { getWithRetryForTokenRefresh, maxUmpPostBodySize } from "./../odspUtils.js"; +import { runWithRetry } from "./../retryUtils.js"; /** * Converts a summary(ISummaryTree) taken in detached container to snapshot tree and blobs diff --git a/packages/drivers/odsp-driver/src/createFile/index.ts b/packages/drivers/odsp-driver/src/createFile/index.ts new file mode 100644 index 000000000000..4815d8d6a10f --- /dev/null +++ b/packages/drivers/odsp-driver/src/createFile/index.ts @@ -0,0 +1,28 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; + +export async function useCreateNewModule( + odspLogger: ITelemetryLoggerExt, + func: ( + m: typeof import("./createNewModule.js") /* webpackChunkName: "createNewModule" */, + ) => Promise, +): Promise { + // We can delay load this module as this path will not be executed in load flows and create flow + // while only happens once in lifetime of a document which happens in the background after creation of + // detached container. + const module = await import(/* webpackChunkName: "createNewModule" */ "./createNewModule.js") + .then((m) => { + odspLogger.sendTelemetryEvent({ eventName: "createNewModuleLoaded" }); + return m; + }) + .catch((error) => { + odspLogger.sendErrorEvent({ eventName: "createNewModuleLoadFailed" }, error); + throw error; + }); + + return func(module); +} diff --git a/packages/drivers/odsp-driver/src/odspDocumentDeltaConnection.ts b/packages/drivers/odsp-driver/src/odspDocumentDeltaConnection.ts index 3519dd7d931b..969356229af6 100644 --- a/packages/drivers/odsp-driver/src/odspDocumentDeltaConnection.ts +++ b/packages/drivers/odsp-driver/src/odspDocumentDeltaConnection.ts @@ -671,7 +671,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection { assert( documentId !== undefined, - "documentId is required when multiplexing is enabled.", + 0xa65 /* documentId is required when multiplexing is enabled. */, ); if (documentId !== this.documentId) { diff --git a/packages/drivers/odsp-driver/src/odspDocumentServiceFactoryCore.ts b/packages/drivers/odsp-driver/src/odspDocumentServiceFactoryCore.ts index 3f4fc9b044d9..91d283861b98 100644 --- a/packages/drivers/odsp-driver/src/odspDocumentServiceFactoryCore.ts +++ b/packages/drivers/odsp-driver/src/odspDocumentServiceFactoryCore.ts @@ -32,6 +32,7 @@ import { import { PerformanceEvent, createChildLogger } from "@fluidframework/telemetry-utils/internal"; import { v4 as uuid } from "uuid"; +import { useCreateNewModule } from "./createFile/index.js"; import { ICacheAndTracker, createOdspCacheAndTracker } from "./epochTracker.js"; import { INonPersistentCache, @@ -173,44 +174,32 @@ export class OdspDocumentServiceFactoryCore resolvedUrlData, this.getStorageToken, ); - // We can delay load this module as this path will not be executed in load flows and create flow - // while only happens once in lifetime of a document happens in the background after creation of - // detached container. - const module = await import( - /* webpackChunkName: "createNewModule" */ "./createNewModule.js" - ) - .then((m) => { - odspLogger.sendTelemetryEvent({ eventName: "createNewModuleLoaded" }); - return m; - }) - .catch((error) => { - odspLogger.sendErrorEvent({ eventName: "createNewModuleLoadFailed" }, error); - throw error; - }); - const _odspResolvedUrl = isNewFileInfo(fileInfo) - ? await module.createNewFluidFile( - getAuthHeader, - fileInfo, - odspLogger, - createNewSummary, - cacheAndTracker.epochTracker, - fileEntry, - this.hostPolicy.cacheCreateNewSummary ?? true, - !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader, - odspResolvedUrl.isClpCompliantApp, - this.hostPolicy.enableSingleRequestForShareLinkWithCreate, - ) - : await module.createNewContainerOnExistingFile( - getAuthHeader, - fileInfo, - odspLogger, - createNewSummary, - cacheAndTracker.epochTracker, - fileEntry, - this.hostPolicy.cacheCreateNewSummary ?? true, - !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader, - odspResolvedUrl.isClpCompliantApp, - ); + const _odspResolvedUrl = await useCreateNewModule(odspLogger, async (module) => { + return isNewFileInfo(fileInfo) + ? module.createNewFluidFile( + getAuthHeader, + fileInfo, + odspLogger, + createNewSummary, + cacheAndTracker.epochTracker, + fileEntry, + this.hostPolicy.cacheCreateNewSummary ?? true, + !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader, + odspResolvedUrl.isClpCompliantApp, + this.hostPolicy.enableSingleRequestForShareLinkWithCreate, + ) + : module.createNewContainerOnExistingFile( + getAuthHeader, + fileInfo, + odspLogger, + createNewSummary, + cacheAndTracker.epochTracker, + fileEntry, + this.hostPolicy.cacheCreateNewSummary ?? true, + !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader, + odspResolvedUrl.isClpCompliantApp, + ); + }); const docService = this.createDocumentServiceCore( _odspResolvedUrl, odspLogger, diff --git a/packages/drivers/odsp-driver/src/packageVersion.ts b/packages/drivers/odsp-driver/src/packageVersion.ts index 094b62c91930..d3ee6f28a7b0 100644 --- a/packages/drivers/odsp-driver/src/packageVersion.ts +++ b/packages/drivers/odsp-driver/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/odsp-driver"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/drivers/odsp-driver/src/test/createNewUtilsTests.spec.ts b/packages/drivers/odsp-driver/src/test/createNewUtilsTests.spec.ts index 28bcf91c8baf..2b36a7f105a9 100644 --- a/packages/drivers/odsp-driver/src/test/createNewUtilsTests.spec.ts +++ b/packages/drivers/odsp-driver/src/test/createNewUtilsTests.spec.ts @@ -17,9 +17,7 @@ import { } from "@fluidframework/odsp-driver-definitions/internal"; import { createChildLogger } from "@fluidframework/telemetry-utils/internal"; -import { createNewFluidFile } from "../createFile.js"; -import { createNewContainerOnExistingFile } from "../createNewContainerOnExistingFile.js"; -import { convertCreateNewSummaryTreeToTreeAndBlobs } from "../createNewUtils.js"; +import { useCreateNewModule } from "../createFile/index.js"; import { EpochTracker } from "../epochTracker.js"; import { LocalPersistentCache } from "../odspCache.js"; import { getHashedDocumentId } from "../odspPublicUtils.js"; @@ -144,25 +142,32 @@ describe("Create New Utils Tests", () => { }; it("Should convert as expected and check contents", async () => { - const snapshot = convertCreateNewSummaryTreeToTreeAndBlobs(createSummary(), ""); - test(snapshot); + await useCreateNewModule(createChildLogger(), async (module) => { + const snapshot: ISnapshot = module.convertCreateNewSummaryTreeToTreeAndBlobs( + createSummary(), + "", + ); + test(snapshot); + }); }); it("Should cache converted summary during createNewFluidFile", async () => { - const odspResolvedUrl = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const snapshot = await epochTracker.get(createCacheSnapshotKey(odspResolvedUrl, false)); @@ -183,20 +188,22 @@ describe("Create New Utils Tests", () => { siteUrl, driveId, }; - const odspResolvedUrl = await mockFetchOk( - async () => - createNewContainerOnExistingFile( - async (_options) => "token", - existingFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewContainerOnExistingFile( + async (_options) => "token", + existingFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const snapshot = await epochTracker.get(createCacheSnapshotKey(odspResolvedUrl, false)); @@ -233,27 +240,29 @@ describe("Create New Utils Tests", () => { shareId: "c40e6f0a-666e-48bf-9509-066900a73b2b", sharingLink: mockSharingLinkData, }; - let odspResolvedUrl = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - false /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - undefined /* isClpCompliantApp */, - true /* enableSingleRequestForShareLinkWithCreate */, - ), - { - itemId: "mockItemId", - id: "mockId", - sharing: mockSharingData, - sharingLinkErrorReason: undefined, - }, - { "x-fluid-epoch": "epoch1" }, + let odspResolvedUrl = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + false /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + undefined /* isClpCompliantApp */, + true /* enableSingleRequestForShareLinkWithCreate */, + ), + { + itemId: "mockItemId", + id: "mockId", + sharing: mockSharingData, + sharingLinkErrorReason: undefined, + }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert.deepStrictEqual(odspResolvedUrl.shareLinkInfo?.createLink, { shareId: mockSharingData.shareId, @@ -278,27 +287,29 @@ describe("Create New Utils Tests", () => { }, }, }; - odspResolvedUrl = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - false /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - undefined /* isClpCompliantApp */, - true /* enableSingleRequestForShareLinkWithCreate */, - ), - { - itemId: "mockItemId", - id: "mockId", - sharingLinkErrorReason: "mockError", - sharing: mockSharingError, - }, - { "x-fluid-epoch": "epoch1" }, + odspResolvedUrl = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + false /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + undefined /* isClpCompliantApp */, + true /* enableSingleRequestForShareLinkWithCreate */, + ), + { + itemId: "mockItemId", + id: "mockId", + sharingLinkErrorReason: "mockError", + sharing: mockSharingError, + }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert.deepStrictEqual(odspResolvedUrl.shareLinkInfo?.createLink, { shareId: undefined, @@ -309,39 +320,43 @@ describe("Create New Utils Tests", () => { }); it("Should set the isClpCompliantApp prop on resolved url if already present when createNewFluidFile", async () => { - const odspResolvedUrl1 = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - true /* isClpCompliantApp */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl1 = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + true /* isClpCompliantApp */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert(odspResolvedUrl1.isClpCompliantApp, "isClpCompliantApp should be set"); - const odspResolvedUrl2 = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - undefined /* isClpCompliantApp */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl2 = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + undefined /* isClpCompliantApp */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert(!odspResolvedUrl2.isClpCompliantApp, "isClpCompliantApp should be falsy"); await epochTracker.removeEntries().catch(() => {}); @@ -354,39 +369,43 @@ describe("Create New Utils Tests", () => { siteUrl, driveId, }; - const odspResolvedUrl1 = await mockFetchOk( - async () => - createNewContainerOnExistingFile( - async (_options) => "token", - existingFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - true /* isClpCompliantApp */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl1 = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewContainerOnExistingFile( + async (_options) => "token", + existingFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + true /* isClpCompliantApp */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert(odspResolvedUrl1.isClpCompliantApp, "isClpCompliantApp should be set"); - const odspResolvedUrl2 = await mockFetchOk( - async () => - createNewFluidFile( - async (_options) => "token", - newFileParams, - createChildLogger(), - createSummary(), - epochTracker, - fileEntry, - true /* createNewCaching */, - false /* forceAccessTokenViaAuthorizationHeader */, - undefined /* isClpCompliantApp */, - ), - { itemId: "itemId1", id: "Summary handle" }, - { "x-fluid-epoch": "epoch1" }, + const odspResolvedUrl2 = await useCreateNewModule(createChildLogger(), async (module) => + mockFetchOk( + async () => + module.createNewFluidFile( + async (_options) => "token", + newFileParams, + createChildLogger(), + createSummary(), + epochTracker, + fileEntry, + true /* createNewCaching */, + false /* forceAccessTokenViaAuthorizationHeader */, + undefined /* isClpCompliantApp */, + ), + { itemId: "itemId1", id: "Summary handle" }, + { "x-fluid-epoch": "epoch1" }, + ), ); assert(!odspResolvedUrl2.isClpCompliantApp, "isClpCompliantApp should be falsy"); await epochTracker.removeEntries().catch(() => {}); diff --git a/packages/drivers/odsp-urlResolver/CHANGELOG.md b/packages/drivers/odsp-urlResolver/CHANGELOG.md index daa7ce249aa8..0ce8b5a9f26c 100644 --- a/packages/drivers/odsp-urlResolver/CHANGELOG.md +++ b/packages/drivers/odsp-urlResolver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/odsp-urlresolver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/odsp-urlResolver/package.json b/packages/drivers/odsp-urlResolver/package.json index 9bfa10fa3754..4e2e98008b87 100644 --- a/packages/drivers/odsp-urlResolver/package.json +++ b/packages/drivers/odsp-urlResolver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/odsp-urlresolver", - "version": "2.5.0", + "version": "2.10.0", "description": "Url Resolver for odsp urls.", "homepage": "https://fluidframework.com", "repository": { @@ -86,11 +86,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/odsp-urlresolver-previous": "npm:@fluidframework/odsp-urlresolver@~2.4.0", + "@fluidframework/odsp-urlresolver-previous": "npm:@fluidframework/odsp-urlresolver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/drivers/replay-driver/.eslintrc.cjs b/packages/drivers/replay-driver/.eslintrc.cjs index fcae210b6902..a8f260775d3c 100644 --- a/packages/drivers/replay-driver/.eslintrc.cjs +++ b/packages/drivers/replay-driver/.eslintrc.cjs @@ -10,5 +10,6 @@ module.exports = { ], rules: { "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/drivers/replay-driver/CHANGELOG.md b/packages/drivers/replay-driver/CHANGELOG.md index 6b7899ab9598..596fa69ecd8a 100644 --- a/packages/drivers/replay-driver/CHANGELOG.md +++ b/packages/drivers/replay-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/replay-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/replay-driver/package.json b/packages/drivers/replay-driver/package.json index e6051cecf34b..4623ca0e5127 100644 --- a/packages/drivers/replay-driver/package.json +++ b/packages/drivers/replay-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/replay-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "Document replay version of Socket.IO implementation", "homepage": "https://fluidframework.com", "repository": { @@ -76,11 +76,11 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/replay-driver-previous": "npm:@fluidframework/replay-driver@~2.4.0", + "@fluidframework/replay-driver-previous": "npm:@fluidframework/replay-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/nock": "^9.3.0", "@types/node": "^18.19.0", diff --git a/packages/drivers/routerlicious-driver/.eslintrc.cjs b/packages/drivers/routerlicious-driver/.eslintrc.cjs index 80de27c908d2..1f213a985b45 100644 --- a/packages/drivers/routerlicious-driver/.eslintrc.cjs +++ b/packages/drivers/routerlicious-driver/.eslintrc.cjs @@ -15,5 +15,6 @@ module.exports = { "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/strict-boolean-expressions": "off", "no-case-declarations": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/drivers/routerlicious-driver/CHANGELOG.md b/packages/drivers/routerlicious-driver/CHANGELOG.md index c7a89e99a395..2c175eca9f8b 100644 --- a/packages/drivers/routerlicious-driver/CHANGELOG.md +++ b/packages/drivers/routerlicious-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/routerlicious-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/routerlicious-driver/package.json b/packages/drivers/routerlicious-driver/package.json index 689a13cd19d8..bfd981b6709f 100644 --- a/packages/drivers/routerlicious-driver/package.json +++ b/packages/drivers/routerlicious-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/routerlicious-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "Socket.IO + Git implementation of Fluid service API", "homepage": "https://fluidframework.com", "repository": { @@ -134,11 +134,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/routerlicious-driver-previous": "npm:@fluidframework/routerlicious-driver@~2.4.0", + "@fluidframework/routerlicious-driver-previous": "npm:@fluidframework/routerlicious-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/nock": "^9.3.0", diff --git a/packages/drivers/routerlicious-driver/src/deltaStorageService.ts b/packages/drivers/routerlicious-driver/src/deltaStorageService.ts index 430696579862..acf1af68a00c 100644 --- a/packages/drivers/routerlicious-driver/src/deltaStorageService.ts +++ b/packages/drivers/routerlicious-driver/src/deltaStorageService.ts @@ -91,7 +91,13 @@ export class DocumentDeltaStorageService implements IDocumentDeltaStorageService this.snapshotOps = undefined; } - const ops = await this.deltaStorageService.get(this.tenantId, this.id, from, to); + const ops = await this.deltaStorageService.get( + this.tenantId, + this.id, + from, + to, + fetchReason, + ); validateMessages("storage", ops.messages, from, this.logger, false /* strict */); opsFromStorage += ops.messages.length; return ops; @@ -144,6 +150,7 @@ export class DeltaStorageService implements IDeltaStorageService { id: string, from: number, // inclusive to: number, // exclusive + fetchReason?: string, ): Promise { const ops = await PerformanceEvent.timedExecAsync( this.logger, @@ -158,6 +165,7 @@ export class DeltaStorageService implements IDeltaStorageService { const response = await restWrapper.get(url, { from: from - 1, to, + fetchReason: fetchReason ?? "", }); event.end({ length: response.content.length, diff --git a/packages/drivers/routerlicious-driver/src/packageVersion.ts b/packages/drivers/routerlicious-driver/src/packageVersion.ts index 82406ff9fda8..c7081ebf1db0 100644 --- a/packages/drivers/routerlicious-driver/src/packageVersion.ts +++ b/packages/drivers/routerlicious-driver/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/routerlicious-driver"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/drivers/routerlicious-urlResolver/CHANGELOG.md b/packages/drivers/routerlicious-urlResolver/CHANGELOG.md index caba52d01faf..5952a63c411a 100644 --- a/packages/drivers/routerlicious-urlResolver/CHANGELOG.md +++ b/packages/drivers/routerlicious-urlResolver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/routerlicious-urlresolver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/routerlicious-urlResolver/package.json b/packages/drivers/routerlicious-urlResolver/package.json index b19d8a481d8d..d2bebabca0fb 100644 --- a/packages/drivers/routerlicious-urlResolver/package.json +++ b/packages/drivers/routerlicious-urlResolver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/routerlicious-urlresolver", - "version": "2.5.0", + "version": "2.10.0", "description": "Url Resolver for routerlicious urls.", "homepage": "https://fluidframework.com", "repository": { @@ -84,11 +84,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/routerlicious-urlresolver-previous": "npm:@fluidframework/routerlicious-urlresolver@~2.4.0", + "@fluidframework/routerlicious-urlresolver-previous": "npm:@fluidframework/routerlicious-urlresolver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/nconf": "^0.10.0", diff --git a/packages/drivers/tinylicious-driver/CHANGELOG.md b/packages/drivers/tinylicious-driver/CHANGELOG.md index c74e8bea683a..b62498169778 100644 --- a/packages/drivers/tinylicious-driver/CHANGELOG.md +++ b/packages/drivers/tinylicious-driver/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/tinylicious-driver +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/drivers/tinylicious-driver/package.json b/packages/drivers/tinylicious-driver/package.json index a6cea75bdf81..6059654f4efd 100644 --- a/packages/drivers/tinylicious-driver/package.json +++ b/packages/drivers/tinylicious-driver/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/tinylicious-driver", - "version": "2.5.0", + "version": "2.10.0", "description": "Driver for tinylicious", "homepage": "https://fluidframework.com", "repository": { @@ -85,11 +85,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/tinylicious-driver-previous": "npm:@fluidframework/tinylicious-driver@~2.4.0", + "@fluidframework/tinylicious-driver-previous": "npm:@fluidframework/tinylicious-driver@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/jsrsasign": "^10.5.12", "@types/mocha": "^9.1.1", diff --git a/packages/framework/agent-scheduler/CHANGELOG.md b/packages/framework/agent-scheduler/CHANGELOG.md index 1c9663ba0cdc..8a20107d3865 100644 --- a/packages/framework/agent-scheduler/CHANGELOG.md +++ b/packages/framework/agent-scheduler/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/agent-scheduler +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/agent-scheduler/package.json b/packages/framework/agent-scheduler/package.json index ce9d78b2765d..1b7e15842168 100644 --- a/packages/framework/agent-scheduler/package.json +++ b/packages/framework/agent-scheduler/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/agent-scheduler", - "version": "2.5.0", + "version": "2.10.0", "description": "Built in runtime object for distributing agents across instances of a container", "homepage": "https://fluidframework.com", "repository": { @@ -104,10 +104,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", - "@fluidframework/agent-scheduler-previous": "npm:@fluidframework/agent-scheduler@~2.4.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluidframework/agent-scheduler-previous": "npm:@fluidframework/agent-scheduler@~2.5.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/framework/ai-collab/.eslintrc.cjs b/packages/framework/ai-collab/.eslintrc.cjs index 3a018eaf9b03..5f3c4df45bf3 100644 --- a/packages/framework/ai-collab/.eslintrc.cjs +++ b/packages/framework/ai-collab/.eslintrc.cjs @@ -8,7 +8,6 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, - rules: {}, overrides: [ { // Rules only for test files diff --git a/packages/framework/ai-collab/CHANGELOG.md b/packages/framework/ai-collab/CHANGELOG.md index 8969e0451b76..a51ce081137e 100644 --- a/packages/framework/ai-collab/CHANGELOG.md +++ b/packages/framework/ai-collab/CHANGELOG.md @@ -1,4 +1,8 @@ -# @fluid-experimental/ai-collab +# @fluidframework/ai-collab + +## 2.5.0 + +Dependency updates only. ## 2.4.0 diff --git a/packages/framework/ai-collab/README.md b/packages/framework/ai-collab/README.md index 05e553b58b53..dfdfc0e7a938 100644 --- a/packages/framework/ai-collab/README.md +++ b/packages/framework/ai-collab/README.md @@ -1,34 +1,196 @@ -# @fluid-experimental/ai-collab +# @fluidframework/ai-collab + +## Description + +The ai-collab client library makes adding complex, human-like collaboration with LLM's built directly in your application as simple as one function call. Simply pass your SharedTree and ask AI to collaborate. For example, +- Task Management App: "Reorder this list of tasks in order from least to highest complexity." +- Job Board App: "Create a new job listing and add it to this job board" +- Calender App: "Manage my calender to slot in a new 2:30 appointment" + +## Usage + +### Your SharedTree types file + +This file is where we define the types of our task management application's SharedTree data +```ts +// --------- File name: "types.ts" --------- +import { SchemaFactory, type treeView } from "@fluidframework/tree"; + +const sf = new SchemaFactory("ai-collab-sample-application"); + +export class Task extends sf.object("Task", { + title: sf.required(sf.string, { + metadata: { description: `The title of the task` }, + }), + id: sf.identifier, + description: sf.required(sf.string, { + metadata: { description: `The description of the task` }, + }), + priority: sf.required(sf.string, { + metadata: { description: `The priority of the task in three levels, "low", "medium", "high"` }, + }), + complexity: sf.required(sf.number, { + metadata: { description: `The complexity of the task as a fibonacci number` }, + }), + status: sf.required(sf.string, { + metadata: { description: `The status of the task as either "todo", "in-progress", or "done"` }, + }), + assignee: sf.required(sf.string, { + metadata: { description: `The name of the tasks assignee e.g. "Bob" or "Alice"` }, + }), +}) {} + +export class TaskList extends sf.array("TaskList", SharedTreeTask) {} + +export class Engineer extends sf.object("Engineer", { + name: sf.required(sf.string, { + metadata: { description: `The name of an engineer whom can be assigned to a task` }, + }), + id: sf.identifier, + skills: sf.required(sf.string, { + metadata: { description: `A description of the engineers skills which influence what types of tasks they should be assigned to.` }, + }), + maxCapacity: sf.required(sf.number, { + metadata: { description: `The maximum capacity of tasks this engineer can handle measured in in task complexity points.` }, + }), +}) {} + +export class EngineerList extends sf.array("EngineerList", SharedTreeEngineer) {} + +export class TaskGroup extends sf.object("TaskGroup", { + description: sf.required(sf.string, { + metadata: { description: `The description of the task group, which is a collection of tasks and engineers that can be assigned to said tasks.` }, + }), + id: sf.identifier, + title: sf.required(sf.string, { + metadata: { description: `The title of the task group.` }, + }), + tasks: sf.required(SharedTreeTaskList, { + metadata: { description: `The lists of tasks within this task group.` }, + }), + engineers: sf.required(SharedTreeEngineerList, { + metadata: { description: `The lists of engineers within this task group which can be assigned to tasks.` }, + }), +}) {} + +export class TaskGroupList extends sf.array("TaskGroupList", SharedTreeTaskGroup) {} + +export class PlannerAppState extends sf.object("PlannerAppState", { + taskGroups: sf.required(SharedTreeTaskGroupList, { + metadata: { description: `The list of task groups that are being managed by this task management application.` }, + }), +}) {} +``` + +### Example 1: Collaborate with AI + +```ts +import { aiCollab } from "@fluidframework/ai-collab/alpha"; +import { PlannerAppState } from "./types.ts" +// This is not a real file, this is meant to represent how you initialize your app data. +import { initializeAppState } from "./yourAppInitializationFile.ts" + +// --------- File name: "app.ts" --------- + +// Initialize your app state somehow +const appState: PlannerAppState = initializeAppState({ + taskGroups: [ + { + title: "My First Task Group", + description: "Placeholder for first task group", + tasks: [ + { + assignee: "Alice", + title: "Task #1", + description: + "This is the first Sample task.", + priority: "low", + complexity: 1, + status: "todo", + }, + ], + engineers: [ + { + name: "Alice", + maxCapacity: 15, + skills: + "Senior engineer capable of handling complex tasks. Versed in most languages", + }, + { + name: "Charlie", + maxCapacity: 7, + skills: "Junior engineer capable of handling simple tasks. Versed in Node.JS", + }, + ], + }, + ], +}) + +// Typically, the user would input this through a UI form/input of some sort. +const userAsk = "Update the task group description to be a about creating a new Todo list application. Create a set of tasks to accomplish this and assign them to the available engineers. Keep in mind the max capacity of each engineer as you assign tasks." + +// Collaborate with AI one function call. +const response = await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root.taskGroups[0], + prompt: { + systemRoleContext: + "You are a manager that is helping out with a project management tool. You have been asked to edit a group of tasks.", + userAsk: userAsk, + }, + planningStep: true, + finalReviewStep: true, + dumpDebugLog: true, + }); + +if (response.status === 'sucess') { + // Render the UI view of your task groups. + window.alert(`The AI has successfully completed your request.`); +} else { + window.alert(`Something went wrong! response status: ${response.status}, error message: ${response.errorMessage}`); +} + +``` + +Once the `aiCollab` function call is initiated, an LLM will immediately begin attempting to make changes to your Shared Tree using the provided user prompt, the types of your SharedTree and the provided app guidance. The LLM produces multiple changes, in a loop asynchronously. Meaning, you will immediatley see changes if your UI's render loop is connected to your SharedTree App State. + +### Example 2: Collaborate with AI onto a branched state and let the user merge the review and merge the branch back manually +- **Coming Soon** + + +## Folder Structure + +- `/explicit-strategy`: The new explicit strategy, utilizing the prototype built during the fall FHL, with a few adjustments. + - `agentEditReducer`: This file houses the logic for taking in a `TreeEdit`, which the LLM produces, and applying said edit to the + - actual SharedTree. + - `agentEditTypes.ts`: The types of edits an LLM is prompted to produce in order to modify a SharedTree. + - `idGenerator.ts`: `A manager for producing and mapping simple id's in place of UUID hashes when generating prompts for an LLM + - `jsonTypes.ts`: utility JSON related types used in parsing LLM response and generating LLM prompts. + - `promptGeneration.ts`: Logic for producing the different types of prompts sent to an LLM in order to edit a SharedTree. + - `typeGeneration.ts`: Generates serialized(/able) representations of a SharedTree Schema which is used within prompts and the generated of the structured output JSON schema + - `utils.ts`: Utilities for interacting with a SharedTree +- `/implicit-strategy`: The original implicit strategy, currently not used under the exported aiCollab API surface. + +## Known Issues & limitations + +1. Union types for a TreeNode are not present when generating App Schema. This will require extracting a field schema instead of TreeNodeSchema when passed a non root node. +1. The Editing System prompt & structured out schema currently provide array related edits even when there are no arrays. This forces you to have an array in your schema to produce a valid json schema +1. Optional roots are not allowed, This is because if you pass undefined as your treeNode to the API, we cannot disambiguate whether you passed the root or not. +1. Primitive root nodes are not allowed to be passed to the API. You must use an object or array as your root. +1. Optional nodes are not supported -- when we use optional nodes, the OpenAI API returns an error complaining that the structured output JSON schema is invalid. I have introduced a fix that should work upon manual validation of the json schema, but there looks to be an issue with their API. I have filed a ticket with OpenAI to address this +1. The current scheme does not allow manipulation of arrays of primitive values because you cannot refer to them. We could accomplish this via a path (probably JSON Pointer or JSONPath) from a possibly-null objectId, or wrap arrays in an identified object. +1. Only 100 object fields total are allowed by OpenAI right now, so larger schemas will fail faster if we have a bunch of schema types generated for type-specific edits. +1. We don't support nested arrays yet. +1. Handle 429 rate limit error from OpenAI API. +1. Top level arrays are not supported with current DSL. +1. Structured Output fails when multiple schema types have the same first field name (e.g. id: sf.identifier on multiple types). +1. Your Application's SharedTree Schema must have no more than 4 levels of nesting due to OpenAI structured output limitations. (4 because we add an extra layer of nesting internally) -Utilities for using SharedTree with LLMs. -This package aims to assist SharedTree-based apps that want to leverage LLMs. - -The next steps in LLM/AI-based collaborative experiences with applications involves allowing LLMs to propose updates -to application state directly. - -## The classic LLM developer experience & it's problems - -The classic LLM dev experience involves crafting a prompt for an an LLM with some information about the app, then -having the LLM response in a parseable format. - -From here the developer needs to: - -1. Translate & interpet the LLM response format so it can be applied to their application state -2. Deal with potentially invalid responses -3. Deal with merging LLM responses that use potentially stale state into their apps. - - This in particular comes into play with more dynamic application state, for example a list that users can - add and remove from. - You'll need to make sure the LLM isn't trying to delete something that doesn't exist or overwrite something that no - longer makes sense. -4. Try to preview LLM changes to the user before accepting them. - This requires maintaining a data structure before any LLM changes are applied, and another one where they are. - -### How this library fixes things - -Newer LLM developer tooling has solved issue #1 in a variety of ways, getting the LLM to respond with a format that you -can merge into your application and ensuring that the JSON response schema is valid. -However, problems 3-4 still exist and the current landscape requires bespoke, per-app solutions for dealing with this. -This library simplifies these issues. diff --git a/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.cjs.json b/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.cjs.json new file mode 100644 index 000000000000..6e31eb54fe1a --- /dev/null +++ b/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.cjs.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "/../../../common/build/build-common/api-extractor-lint.entrypoint.json", + "mainEntryPointFilePath": "/dist/alpha.d.ts" +} diff --git a/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.esm.json b/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.esm.json new file mode 100644 index 000000000000..985eb4c274d5 --- /dev/null +++ b/packages/framework/ai-collab/api-extractor/api-extractor-lint-alpha.esm.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "/../../../common/build/build-common/api-extractor-lint.entrypoint.json", + "mainEntryPointFilePath": "/lib/alpha.d.ts" +} diff --git a/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.cjs.json b/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.cjs.json new file mode 100644 index 000000000000..78909ae6084d --- /dev/null +++ b/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.cjs.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "/../../../common/build/build-common/api-extractor-lint.entrypoint.json", + "mainEntryPointFilePath": "/dist/public.d.ts" +} diff --git a/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.esm.json b/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.esm.json new file mode 100644 index 000000000000..aca2093b62bc --- /dev/null +++ b/packages/framework/ai-collab/api-extractor/api-extractor-lint-public.esm.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "/../../../common/build/build-common/api-extractor-lint.entrypoint.json", + "mainEntryPointFilePath": "/lib/public.d.ts" +} diff --git a/packages/framework/ai-collab/api-report/ai-collab.alpha.api.md b/packages/framework/ai-collab/api-report/ai-collab.alpha.api.md index 7a4af0ab6b20..5b2c547b2cf5 100644 --- a/packages/framework/ai-collab/api-report/ai-collab.alpha.api.md +++ b/packages/framework/ai-collab/api-report/ai-collab.alpha.api.md @@ -1,9 +1,45 @@ -## Alpha API Report File for "@fluid-experimental/ai-collab" +## Alpha API Report File for "@fluidframework/ai-collab" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +// @alpha +export function aiCollab(options: AiCollabOptions): Promise; + +// @alpha +export interface AiCollabErrorResponse { + readonly errorMessage: "tokenLimitExceeded" | "tooManyErrors" | "tooManyModelCalls" | "aborted"; + readonly status: "failure" | "partial-failure"; + readonly tokensUsed: TokenUsage; +} + +// @alpha +export interface AiCollabOptions { + readonly dumpDebugLog?: boolean; + readonly finalReviewStep?: boolean; + readonly limiters?: { + readonly abortController?: AbortController; + readonly maxSequentialErrors?: number; + readonly maxModelCalls?: number; + readonly tokenLimits?: TokenLimits; + }; + readonly openAI: OpenAiClientOptions; + readonly planningStep?: boolean; + readonly prompt: { + readonly systemRoleContext: string; + readonly userAsk: string; + }; + readonly treeNode: TreeNode; + readonly validator?: (newContent: TreeNode) => void; +} + +// @alpha +export interface AiCollabSuccessResponse { + readonly status: "success"; + readonly tokensUsed: TokenUsage; +} + // @alpha export function createMergableDiffSeries(diffs: Difference[]): Difference[]; @@ -66,6 +102,12 @@ export interface DifferenceRemove { // @alpha export type ObjectPath = (string | number)[]; +// @alpha +export interface OpenAiClientOptions { + client: OpenAI; + modelName?: string; +} + // @alpha export interface Options { // (undocumented) @@ -85,13 +127,13 @@ export class SharedTreeBranchManager { applyDiff(diff: Difference, objectToUpdate: Record | TreeArrayNode): boolean; checkoutNewMergedBranch(treeView: TreeViewAlpha, treeViewConfiguration: TreeViewConfiguration, absolutePathToObjectNode: ObjectPath, llmResponse: Record | unknown[]): { differences: Difference[]; - originalBranch: TreeBranch; + originalBranch: BranchableTree; forkBranch: TreeBranchFork; forkView: TreeViewAlpha; newBranchTargetNode: Record | TreeArrayNode; }; checkoutNewMergedBranchV2(treeView: TreeViewAlpha, treeViewConfiguration: TreeViewConfiguration, absolutePathToObjectNode: ObjectPath): { - originalBranch: TreeBranch; + originalBranch: BranchableTree; forkBranch: TreeBranchFork; forkView: TreeViewAlpha; newBranchTargetNode: Record | TreeArrayNode; @@ -107,4 +149,16 @@ export function sharedTreeDiff(obj: Record | unknown[], newObj: // @alpha export function sharedTreeTraverse(jsonObject: TreeMapNode | TreeArrayNode | Record, path: ObjectPath): T | undefined; +// @alpha +export interface TokenLimits { + readonly inputTokens?: number; + readonly outputTokens?: number; +} + +// @alpha +export interface TokenUsage { + inputTokens: number; + outputTokens: number; +} + ``` diff --git a/packages/framework/ai-collab/api-report/ai-collab.beta.api.md b/packages/framework/ai-collab/api-report/ai-collab.beta.api.md index 3a6bfa2dadb7..dfecab3975c3 100644 --- a/packages/framework/ai-collab/api-report/ai-collab.beta.api.md +++ b/packages/framework/ai-collab/api-report/ai-collab.beta.api.md @@ -1,4 +1,4 @@ -## Beta API Report File for "@fluid-experimental/ai-collab" +## Beta API Report File for "@fluidframework/ai-collab" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/packages/framework/ai-collab/api-report/ai-collab.public.api.md b/packages/framework/ai-collab/api-report/ai-collab.public.api.md index a351221dbf3e..ddbd8ba1db41 100644 --- a/packages/framework/ai-collab/api-report/ai-collab.public.api.md +++ b/packages/framework/ai-collab/api-report/ai-collab.public.api.md @@ -1,4 +1,4 @@ -## Public API Report File for "@fluid-experimental/ai-collab" +## Public API Report File for "@fluidframework/ai-collab" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/packages/framework/ai-collab/eslintrc.cjs b/packages/framework/ai-collab/eslintrc.cjs index 021c6bc37cb0..f1aaefc67a58 100644 --- a/packages/framework/ai-collab/eslintrc.cjs +++ b/packages/framework/ai-collab/eslintrc.cjs @@ -8,7 +8,4 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, - rules: { - "unicorn/no-null": "off", - }, }; diff --git a/packages/framework/ai-collab/package.json b/packages/framework/ai-collab/package.json index 4de39b47a6b9..77b297e04a59 100644 --- a/packages/framework/ai-collab/package.json +++ b/packages/framework/ai-collab/package.json @@ -1,6 +1,6 @@ { - "name": "@fluid-experimental/ai-collab", - "version": "2.5.0", + "name": "@fluidframework/ai-collab", + "version": "2.10.0", "description": "Experimental package to simplify integrating AI into Fluid-based applications", "homepage": "https://fluidframework.com", "repository": { @@ -15,11 +15,21 @@ "exports": { ".": { "import": { - "types": "./lib/index.d.ts", + "types": "./lib/public.d.ts", "default": "./lib/index.js" }, "require": { - "types": "./dist/index.d.ts", + "types": "./dist/public.d.ts", + "default": "./dist/index.js" + } + }, + "./alpha": { + "import": { + "types": "./lib/alpha.d.ts", + "default": "./lib/index.js" + }, + "require": { + "types": "./dist/alpha.d.ts", "default": "./dist/index.js" } }, @@ -34,7 +44,12 @@ } } }, + "main": "lib/index.js", + "types": "lib/public.d.ts", "scripts": { + "api": "fluid-build . --task api", + "api-extractor:commonjs": "flub generate entrypoints --outDir ./dist", + "api-extractor:esnext": "flub generate entrypoints --outDir ./lib --node10TypeCompat", "build": "fluid-build . --task build", "build:commonjs": "fluid-build . --task commonjs", "build:compile": "fluid-build . --task compile", @@ -47,8 +62,12 @@ "check:biome": "biome check .", "check:exports": "concurrently \"npm:check:exports:*\"", "check:exports:bundle-release-tags": "api-extractor run --config api-extractor/api-extractor-lint-bundle.json", + "check:exports:cjs:alpha": "api-extractor run --config api-extractor/api-extractor-lint-alpha.cjs.json", "check:exports:cjs:index": "api-extractor run --config api-extractor/api-extractor-lint-index.cjs.json", + "check:exports:cjs:public": "api-extractor run --config api-extractor/api-extractor-lint-public.cjs.json", + "check:exports:esm:alpha": "api-extractor run --config api-extractor/api-extractor-lint-alpha.esm.json", "check:exports:esm:index": "api-extractor run --config api-extractor/api-extractor-lint-index.esm.json", + "check:exports:esm:public": "api-extractor run --config api-extractor/api-extractor-lint-public.esm.json", "check:format": "npm run check:biome", "ci:build:docs": "api-extractor run", "clean": "rimraf --glob dist lib \"*.d.ts\" \"**/*.tsbuildinfo\" \"**/*.build.log\" _api-extractor-temp nyc", @@ -61,10 +80,12 @@ "test": "npm run test:mocha", "test:coverage": "c8 npm test", "test:mocha": "npm run test:mocha:esm && echo skipping cjs to avoid overhead - npm run test:mocha:cjs", - "test:mocha:cjs": "echo \"TESTS ARE CURRENTLY FAILING. SKIPPING RUN.;\" # mocha --recursive \"dist/test/**/*.spec.js\"", - "test:mocha:esm": "echo \"TESTS ARE CURRENTLY FAILING. SKIPPING RUN.;\" # mocha --recursive \"lib/test/**/*.spec.js\"", + "test:mocha:cjs": "mocha --recursive \"dist/test/**/*.spec.js\"", + "test:mocha:esm": "mocha --recursive \"lib/test/**/*.spec.js\"", "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha", - "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist" + "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist", + "typetests:gen": "flub generate typetests --dir . -v", + "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" }, "c8": { "all": true, @@ -89,17 +110,25 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { + "@fluidframework/core-utils": "workspace:~", + "@fluidframework/runtime-utils": "workspace:~", + "@fluidframework/telemetry-utils": "workspace:~", "@fluidframework/tree": "workspace:~", + "openai": "^4.67.3", + "typechat": "^0.1.1", "zod": "^3.23.8" }, "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", + "@fluidframework/id-compressor": "workspace:~", + "@fluidframework/runtime-utils": "workspace:~", + "@fluidframework/test-runtime-utils": "workspace:^", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", @@ -116,6 +145,18 @@ "rimraf": "^4.4.0", "typescript": "~5.4.5" }, + "fluidBuild": { + "tasks": { + "build:esnext": [ + "...", + "typetests:gen" + ], + "tsc": [ + "...", + "typetests:gen" + ] + } + }, "typeValidation": { "disabled": true, "broken": {}, diff --git a/packages/framework/ai-collab/src/aiCollab.ts b/packages/framework/ai-collab/src/aiCollab.ts new file mode 100644 index 000000000000..d40a124759bc --- /dev/null +++ b/packages/framework/ai-collab/src/aiCollab.ts @@ -0,0 +1,86 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { + AiCollabErrorResponse, + AiCollabOptions, + AiCollabSuccessResponse, +} from "./aiCollabApi.js"; +import { generateTreeEdits } from "./explicit-strategy/index.js"; + +/** + * Calls an LLM to modify the provided SharedTree in a series of real time edits based on the provided users prompt input. + * @remarks This function is designed to be a controlled "all-in-one" function that handles the entire process of calling an LLM to collaborative edit a SharedTree. + * + * @example + * ```typescript + * import { + * SchemaFactory, + * TreeViewConfiguration, + * type TreeView + * } from "@fluidframework/tree"; + * + * const sf = new SchemaFactory("todo-app"); + * + * class TodoTask extends sf.object("TodoTask", { + * title: sf.string, + * description: sf.string, + * }) {} + * + * class TodoAppState extends sf.object("TodoAppState", { + * tasks: sf.array(TodoTask), + * }) {} + * + * // Initialize your SharedTree + * const treeView: TreeView = tree.viewWith(new TreeViewConfiguration({ schema: TodoAppState })); + * treeView.initialize({ tasks: [] }); + * + * // Collaborate with AI in realtime in just one function call. + * const response = await aiCollab({ + * openAI: { + * client: new OpenAI({ + * apiKey: OPENAI_API_KEY, + * }), + * modelName: "gpt-4o", + * }, + * treeNode: view.root, + * prompt: { + * systemRoleContext: + * "You are an helpful assistant managing a todo list for a user.", + * userAsk: "Create a set of new todos to plan a vacation to Cancun.", + * }, + * planningStep: true, + * finalReviewStep: true, + * dumpDebugLog: true, + * }); + * ``` + * + * @remarks Known Limitiations: + * - Root level array nodes are not supported + * - Nested arrays are not supported + * - Primitive nodes are not supported, e.g. 'string', 'number', 'boolean' + * - Your application's Shared Tree schema must have no more than 4 levels of nesting + * - Optional nodes are not supported in the Shared Tree schema + * - Union types are not supported in the Shared Tree schema + * - See README for more details. + * + * @alpha + */ +export async function aiCollab( + options: AiCollabOptions, +): Promise { + const response = await generateTreeEdits({ + treeNode: options.treeNode, + validator: options.validator, + openAI: options.openAI, + prompt: options.prompt, + limiters: options.limiters, + dumpDebugLog: options.dumpDebugLog, + planningStep: options.planningStep, + finalReviewStep: options.finalReviewStep, + }); + + return response; +} diff --git a/packages/framework/ai-collab/src/aiCollabApi.ts b/packages/framework/ai-collab/src/aiCollabApi.ts new file mode 100644 index 000000000000..c84a89de33aa --- /dev/null +++ b/packages/framework/ai-collab/src/aiCollabApi.ts @@ -0,0 +1,184 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { TreeNode } from "@fluidframework/tree"; +// eslint-disable-next-line import/no-named-as-default +import type OpenAI from "openai"; + +/** + * OpenAI client options for the {@link AiCollabOptions} interface. + * + * @alpha + */ +export interface OpenAiClientOptions { + /** + * The OpenAI client to use for the AI collaboration. + */ + client: OpenAI; + /** + * The name of the target OpenAI model to use for the AI collaboration. + */ + modelName?: string; +} + +/** + * Options for the AI collaboration. + * + * @alpha + */ +export interface AiCollabOptions { + /** + * The OpenAI client options to use for the LLM based AI collaboration. + */ + readonly openAI: OpenAiClientOptions; + /** + * The specific tree node you want the AI to collaborate on. Pass the root node of your tree if you intend + * for the AI to work on the entire tree. + * @remarks + * - Optional root nodes are not supported + * - Primitive root nodes are not supported + */ + readonly treeNode: TreeNode; + /** + * The prompt context to give the LLM in order to collaborate with your applications data. + */ + readonly prompt: { + /** + * The context to give the LLM about its role in the collaboration. + * @remarks It's highly recommended to give context about your applications data model and the LLM's role in the collaboration. + */ + readonly systemRoleContext: string; + /** + * The request from the users to the LLM. + */ + readonly userAsk: string; + }; + /** + * Limiters are various optional ways to limit this library's usage of the LLM. + */ + readonly limiters?: { + /** + * An optional AbortController that can be used to abort the AI collaboration while it is still in progress. + */ + readonly abortController?: AbortController; + /** + * The maximum number of sequential errors the LLM can make before aborting the collaboration. + * If the maximum number of sequential errors is reached, the AI collaboration will be aborted and return with the errorMessage 'tooManyErrors'. + * Leaving this undefined will disable this limiter. + */ + readonly maxSequentialErrors?: number; + /** + * The maximum number of model calls the LLM can make before aborting the collaboration. + * If the maximum number of model calls is reached, the AI collaboration will be aborted and return with the errorMessage 'tooManyModelCalls'. + * Leaving this undefined will disable this limiter. + */ + readonly maxModelCalls?: number; + /** + * The maximum token usage limits for the LLM. + * If the LLM exceeds the token limits, the AI collaboration will be aborted and return with the errorMessage 'tokenLimitExceeded'. + * This happens after the first model call's token usage is calculated, meaning that the limits set may be exceeded by a certain amount. + * Leaving this undefined will disable this limiter. + */ + readonly tokenLimits?: TokenLimits; + }; + /** + * When set to true, the LLM will be asked to first produce a plan, based on the user's ask, before generating any changes to your applications data. + * This can help the LLM produce better results. + * When set to false, the LLM will not be asked to produce a plan. + */ + readonly planningStep?: boolean; + /** + * When set to true, the LLM will be asked to complete a final review of the changes and determine if any additional changes need to be made. + * When set to false, the LLM will not be asked to complete a final review. + */ + readonly finalReviewStep?: boolean; + /** + * An optional validator function that can be used to validate the new content produced by the LLM. + */ + readonly validator?: (newContent: TreeNode) => void; + /** + * When enabled, the library will console.log information useful for debugging the AI collaboration. + */ + readonly dumpDebugLog?: boolean; +} + +/** + * A successful response from the AI collaboration. + * + * @alpha + */ +export interface AiCollabSuccessResponse { + /** + * The status of the Ai Collaboration. + * A 'success' status indicates that the AI collaboration was successful at creating changes. + */ + readonly status: "success"; + /** + * {@inheritDoc TokenUsage} + */ + readonly tokensUsed: TokenUsage; +} + +/** + * An error response from the AI collaboration. + * + * @alpha + */ +export interface AiCollabErrorResponse { + /** + * The status of the Ai Collaboration. + * - A 'partial-failure' status indicates that the AI collaboration was partially successful, but was aborted due to a limiter or other error + * - A "failure" status indicates that the AI collaboration was not successful at creating any changes. + */ + readonly status: "failure" | "partial-failure"; + /** + * The type of known error that occured + * - 'tokenLimitExceeded' indicates that the LLM exceeded the token limits set by the user + * - 'tooManyErrors' indicates that the LLM made too many errors in a row + * - 'tooManyModelCalls' indicates that the LLM made too many model calls + * - 'aborted' indicates that the AI collaboration was aborted by the user or a limiter + */ + readonly errorMessage: + | "tokenLimitExceeded" + | "tooManyErrors" + | "tooManyModelCalls" + | "aborted"; + /** + * {@inheritDoc TokenUsage} + */ + readonly tokensUsed: TokenUsage; +} + +/** + * Total usage of tokens by an LLM. + * + * @alpha + */ +export interface TokenUsage { + /** + * The total number of tokens used by the LLM for input. + */ + inputTokens: number; + /** + * The total number of tokens used by the LLM for output. + */ + outputTokens: number; +} + +/** + * Maximum limits for the total tokens that can be used by an llm + * + * @alpha + */ +export interface TokenLimits { + /** + * The maximum number of tokens that can be used by the LLM for input. + */ + readonly inputTokens?: number; + /** + * The maximum number of tokens that can be used by the LLM for output. + */ + readonly outputTokens?: number; +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/agentEditReducer.ts b/packages/framework/ai-collab/src/explicit-strategy/agentEditReducer.ts new file mode 100644 index 000000000000..c1ddb4562784 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/agentEditReducer.ts @@ -0,0 +1,498 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { assert } from "@fluidframework/core-utils/internal"; +import { isFluidHandle } from "@fluidframework/runtime-utils"; +import { UsageError } from "@fluidframework/telemetry-utils/internal"; +import { + Tree, + NodeKind, + type ImplicitAllowedTypes, + type TreeArrayNode, + type TreeNode, + type TreeNodeSchema, + type SimpleNodeSchema, + FieldKind, + FieldSchema, + normalizeAllowedTypes, + type ImplicitFieldSchema, + booleanSchema, + handleSchema, + nullSchema, + numberSchema, + stringSchema, + type IterableTreeArrayContent, +} from "@fluidframework/tree/internal"; + +import { + type TreeEdit, + type ObjectTarget, + type Selection, + type Range, + type ObjectPlace, + type ArrayPlace, + type TreeEditObject, + type TreeEditValue, + typeField, +} from "./agentEditTypes.js"; +import type { IdGenerator } from "./idGenerator.js"; +import type { JsonValue } from "./jsonTypes.js"; +import { toDecoratedJson } from "./promptGeneration.js"; +import { fail } from "./utils.js"; + +function populateDefaults( + json: JsonValue, + definitionMap: ReadonlyMap, +): void { + if (typeof json === "object") { + if (json === null) { + return; + } + if (Array.isArray(json)) { + for (const element of json) { + populateDefaults(element, definitionMap); + } + } else { + assert( + typeof json[typeField] === "string", + "The typeField must be present in new JSON content", + ); + const nodeSchema = definitionMap.get(json[typeField]); + assert(nodeSchema?.kind === NodeKind.Object, "Expected object schema"); + } + } +} + +function getSchemaIdentifier(content: TreeEditValue): string | undefined { + switch (typeof content) { + case "boolean": { + return booleanSchema.identifier; + } + case "number": { + return numberSchema.identifier; + } + case "string": { + return stringSchema.identifier; + } + case "object": { + if (content === null) { + return nullSchema.identifier; + } + if (Array.isArray(content)) { + throw new UsageError("Arrays are not currently supported in this context"); + } + if (isFluidHandle(content)) { + return handleSchema.identifier; + } + return content[typeField]; + } + default: { + throw new UsageError("Unsupported content type"); + } + } +} + +function contentWithIds(content: TreeNode, idGenerator: IdGenerator): TreeEditObject { + return JSON.parse(toDecoratedJson(idGenerator, content)) as TreeEditObject; +} + +/** + * Manages applying the various types of {@link TreeEdit}'s to a a given {@link TreeNode}. + */ +export function applyAgentEdit( + treeEdit: TreeEdit, + idGenerator: IdGenerator, + definitionMap: ReadonlyMap, + validator?: (edit: TreeNode) => void, +): TreeEdit { + assertObjectIdsExist(treeEdit, idGenerator); + switch (treeEdit.type) { + case "insert": { + const { array, index } = getPlaceInfo(treeEdit.destination, idGenerator); + + const parentNodeSchema = Tree.schema(array); + populateDefaults(treeEdit.content, definitionMap); + + const schemaIdentifier = getSchemaIdentifier(treeEdit.content); + + // We assume that the parentNode for inserts edits are guaranteed to be an arrayNode. + const allowedTypes = [ + ...normalizeAllowedTypes(parentNodeSchema.info as ImplicitAllowedTypes), + ]; + + for (const allowedType of allowedTypes.values()) { + if (allowedType.identifier === schemaIdentifier && typeof allowedType === "function") { + const simpleNodeSchema = allowedType as unknown as new (dummy: unknown) => TreeNode; + const insertNode = new simpleNodeSchema(treeEdit.content); + validator?.(insertNode); + array.insertAt(index, insertNode as unknown as IterableTreeArrayContent); + return { + ...treeEdit, + content: contentWithIds(insertNode, idGenerator), + }; + } + } + fail("inserted node must be of an allowed type"); + } + case "remove": { + const source = treeEdit.source; + if (isObjectTarget(source)) { + const node = getNodeFromTarget(source, idGenerator); + const parentNode = Tree.parent(node); + // Case for deleting rootNode + if (parentNode === undefined) { + throw new UsageError( + "The root is required, and cannot be removed. Please use modify edit instead.", + ); + } else if (Tree.schema(parentNode).kind === NodeKind.Array) { + const nodeIndex = Tree.key(node) as number; + (parentNode as TreeArrayNode).removeAt(nodeIndex); + } else { + const fieldKey = Tree.key(node); + const parentSchema = Tree.schema(parentNode); + const fieldSchema = + (parentSchema.info as Record)[fieldKey] ?? + fail("Expected field schema"); + if (fieldSchema instanceof FieldSchema && fieldSchema.kind === FieldKind.Optional) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (parentNode as any)[fieldKey] = undefined; + } else { + throw new UsageError( + `${fieldKey} is required, and cannot be removed. Please use modify edit instead.`, + ); + } + } + } else if (isRange(source)) { + const { array, startIndex, endIndex } = getRangeInfo(source, idGenerator); + array.removeRange(startIndex, endIndex); + } + return treeEdit; + } + case "modify": { + const node = getNodeFromTarget(treeEdit.target, idGenerator); + const { treeNodeSchema } = getSimpleNodeSchema(node); + + const fieldSchema = + (treeNodeSchema.info as Record)[treeEdit.field] ?? + fail("Expected field schema"); + + const modification = treeEdit.modification; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const schemaIdentifier = (modification as any)[typeField]; + + let insertedObject: TreeNode | undefined; + // if fieldSchema is a LeafnodeSchema, we can check that it's a valid type and set the field. + if (isPrimitive(modification)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = modification; + } + // If the fieldSchema is a function we can grab the constructor and make an instance of that node. + else if (typeof fieldSchema === "function") { + const simpleSchema = fieldSchema as unknown as new (dummy: unknown) => TreeNode; + populateDefaults(modification, definitionMap); + const constructedModification = new simpleSchema(modification); + validator?.(constructedModification); + insertedObject = constructedModification; + + if (Array.isArray(modification)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + const field = (node as any)[treeEdit.field] as TreeArrayNode; + assert(Array.isArray(field), "the field must be an array node"); + assert( + Array.isArray(constructedModification), + "the modification must be an array node", + ); + field.removeRange(0); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = constructedModification; + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = constructedModification; + } + } + // If the fieldSchema is of type FieldSchema, we can check its allowed types and set the field. + else if (fieldSchema instanceof FieldSchema) { + if (fieldSchema.kind === FieldKind.Optional && modification === undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = undefined; + } else { + for (const allowedType of fieldSchema.allowedTypeSet.values()) { + if (allowedType.identifier === schemaIdentifier) { + if (typeof allowedType === "function") { + const simpleSchema = allowedType as unknown as new ( + dummy: unknown, + ) => TreeNode; + const constructedObject = new simpleSchema(modification); + insertedObject = constructedObject; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = constructedObject; + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (node as any)[treeEdit.field] = modification; + } + } + } + } + } + return insertedObject === undefined + ? treeEdit + : { + ...treeEdit, + modification: contentWithIds(insertedObject, idGenerator), + }; + } + case "move": { + // TODO: need to add schema check for valid moves + const source = treeEdit.source; + const destination = treeEdit.destination; + const { array: destinationArrayNode, index: destinationIndex } = getPlaceInfo( + destination, + idGenerator, + ); + + if (isObjectTarget(source)) { + const sourceNode = getNodeFromTarget(source, idGenerator); + const sourceIndex = Tree.key(sourceNode) as number; + const sourceArrayNode = Tree.parent(sourceNode) as TreeArrayNode; + const sourceArraySchema = Tree.schema(sourceArrayNode); + if (sourceArraySchema.kind !== NodeKind.Array) { + throw new UsageError("the source node must be within an arrayNode"); + } + const destinationArraySchema = Tree.schema(destinationArrayNode); + const allowedTypes = [ + ...normalizeAllowedTypes(destinationArraySchema.info as ImplicitAllowedTypes), + ]; + const nodeToMove = sourceArrayNode.at(sourceIndex); + assert(nodeToMove !== undefined, "node to move must exist"); + if (isNodeAllowedType(nodeToMove as TreeNode, allowedTypes)) { + destinationArrayNode.moveRangeToIndex( + destinationIndex, + sourceIndex, + sourceIndex + 1, + sourceArrayNode, + ); + } else { + throw new UsageError("Illegal node type in destination array"); + } + } else if (isRange(source)) { + const { + array, + startIndex: sourceStartIndex, + endIndex: sourceEndIndex, + } = getRangeInfo(source, idGenerator); + const destinationArraySchema = Tree.schema(destinationArrayNode); + const allowedTypes = [ + ...normalizeAllowedTypes(destinationArraySchema.info as ImplicitAllowedTypes), + ]; + for (let i = sourceStartIndex; i < sourceEndIndex; i++) { + const nodeToMove = array.at(i); + assert(nodeToMove !== undefined, "node to move must exist"); + if (!isNodeAllowedType(nodeToMove as TreeNode, allowedTypes)) { + throw new UsageError("Illegal node type in destination array"); + } + } + destinationArrayNode.moveRangeToIndex( + destinationIndex, + sourceStartIndex, + sourceEndIndex, + array, + ); + } + return treeEdit; + } + default: { + fail("invalid tree edit"); + } + } +} + +function isNodeAllowedType(node: TreeNode, allowedTypes: TreeNodeSchema[]): boolean { + for (const allowedType of allowedTypes) { + if (Tree.is(node, allowedType)) { + return true; + } + } + return false; +} + +function isPrimitive(content: unknown): boolean { + return ( + typeof content === "number" || + typeof content === "string" || + typeof content === "boolean" || + content === undefined || + content === null + ); +} + +function isObjectTarget(selection: Selection): selection is ObjectTarget { + return Object.keys(selection).length === 1 && "target" in selection; +} + +function isRange(selection: Selection): selection is Range { + return "from" in selection && "to" in selection; +} + +interface RangeInfo { + array: TreeArrayNode; + startIndex: number; + endIndex: number; +} + +function getRangeInfo(range: Range, idGenerator: IdGenerator): RangeInfo { + const { array: arrayFrom, index: startIndex } = getPlaceInfo(range.from, idGenerator); + const { array: arrayTo, index: endIndex } = getPlaceInfo(range.to, idGenerator); + + if (arrayFrom !== arrayTo) { + throw new UsageError( + 'The "from" node and "to" nodes of the range must be in the same parent array.', + ); + } + + return { array: arrayFrom, startIndex, endIndex }; +} + +function getPlaceInfo( + place: ObjectPlace | ArrayPlace, + idGenerator: IdGenerator, +): { + array: TreeArrayNode; + index: number; +} { + if (place.type === "arrayPlace") { + const parent = idGenerator.getNode(place.parentId) ?? fail("Expected parent node"); + const child = (parent as unknown as Record)[place.field]; + if (child === undefined) { + throw new UsageError(`No child under field field`); + } + const schema = Tree.schema(child as TreeNode); + if (schema.kind !== NodeKind.Array) { + throw new UsageError("Expected child to be in an array node"); + } + return { + array: child as TreeArrayNode, + index: place.location === "start" ? 0 : (child as TreeArrayNode).length, + }; + } else { + const node = getNodeFromTarget(place, idGenerator); + const nodeIndex = Tree.key(node); + const parent = Tree.parent(node); + if (parent === undefined) { + throw new UsageError("TODO: root node target not supported"); + } + const schema = Tree.schema(parent); + if (schema.kind !== NodeKind.Array) { + throw new UsageError("Expected child to be in an array node"); + } + return { + array: parent as unknown as TreeArrayNode, + index: place.place === "before" ? (nodeIndex as number) : (nodeIndex as number) + 1, + }; + } +} + +/** + * Returns the target node with the matching internal objectId using the provided {@link ObjectTarget} + */ +function getNodeFromTarget(target: ObjectTarget, idGenerator: IdGenerator): TreeNode { + const node = idGenerator.getNode(target.target); + assert(node !== undefined, "objectId does not exist in nodeMap"); + return node; +} + +/** + * Checks that the objectIds of the Tree Nodes within the givin the {@link TreeEdit} exist within the given {@link IdGenerator} + * + * @throws An {@link UsageError} if the objectIdKey does not exist in the {@link IdGenerator} + */ +function assertObjectIdsExist(treeEdit: TreeEdit, idGenerator: IdGenerator): void { + switch (treeEdit.type) { + case "insert": { + if (treeEdit.destination.type === "objectPlace") { + if (idGenerator.getNode(treeEdit.destination.target) === undefined) { + throw new UsageError(`objectIdKey ${treeEdit.destination.target} does not exist`); + } + } else { + if (idGenerator.getNode(treeEdit.destination.parentId) === undefined) { + throw new UsageError(`objectIdKey ${treeEdit.destination.parentId} does not exist`); + } + } + break; + } + case "remove": { + if (isRange(treeEdit.source)) { + const missingObjectIds = [ + treeEdit.source.from.target, + treeEdit.source.to.target, + ].filter((id) => !idGenerator.getNode(id)); + + if (missingObjectIds.length > 0) { + throw new UsageError(`objectIdKeys [${missingObjectIds}] does not exist`); + } + } else if ( + isObjectTarget(treeEdit.source) && + idGenerator.getNode(treeEdit.source.target) === undefined + ) { + throw new UsageError(`objectIdKey ${treeEdit.source.target} does not exist`); + } + break; + } + case "modify": { + if (idGenerator.getNode(treeEdit.target.target) === undefined) { + throw new UsageError(`objectIdKey ${treeEdit.target.target} does not exist`); + } + break; + } + case "move": { + const invalidObjectIds: string[] = []; + // check the source + if (isRange(treeEdit.source)) { + const missingObjectIds = [ + treeEdit.source.from.target, + treeEdit.source.to.target, + ].filter((id) => !idGenerator.getNode(id)); + + if (missingObjectIds.length > 0) { + invalidObjectIds.push(...missingObjectIds); + } + } else if ( + isObjectTarget(treeEdit.source) && + idGenerator.getNode(treeEdit.source.target) === undefined + ) { + invalidObjectIds.push(treeEdit.source.target); + } + + // check the destination + if (treeEdit.destination.type === "objectPlace") { + if (idGenerator.getNode(treeEdit.destination.target) === undefined) { + invalidObjectIds.push(treeEdit.destination.target); + } + } else { + if (idGenerator.getNode(treeEdit.destination.parentId) === undefined) { + invalidObjectIds.push(treeEdit.destination.parentId); + } + } + if (invalidObjectIds.length > 0) { + throw new UsageError(`objectIdKeys [${invalidObjectIds}] does not exist`); + } + break; + } + default: { + break; + } + } +} + +interface SchemaInfo { + treeNodeSchema: TreeNodeSchema; + simpleNodeSchema: new (dummy: unknown) => TreeNode; +} + +function getSimpleNodeSchema(node: TreeNode): SchemaInfo { + const treeNodeSchema = Tree.schema(node); + const simpleNodeSchema = treeNodeSchema as unknown as new (dummy: unknown) => TreeNode; + return { treeNodeSchema, simpleNodeSchema }; +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/agentEditTypes.ts b/packages/framework/ai-collab/src/explicit-strategy/agentEditTypes.ts new file mode 100644 index 000000000000..8ebc6a6e4932 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/agentEditTypes.ts @@ -0,0 +1,177 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { JsonPrimitive } from "./jsonTypes.js"; + +/** + * TODO: The current scheme does not allow manipulation of arrays of primitive values because you cannot refer to them. + * We could accomplish this via a path (probably JSON Pointer or JSONPath) from a possibly-null objectId, or wrap arrays in an identified object. + * + * TODO: only 100 object fields total are allowed by OpenAI right now, so larger schemas will fail faster if we have a bunch of schema types generated for type-specific edits. + * + * TODO: experiment using https://github.com/outlines-dev/outlines (and maybe a llama model) to avoid many of the annoyances of OpenAI's JSON Schema subset. + * + * TODO: without field count limits, we could generate a schema for valid paths from the root object to any field, but it's not clear how useful that would be. + * + * TODO: We don't supported nested arrays yet. + * + * TODO: Add a prompt suggestion API! + * + * TODO: Could encourage the model to output more technical explanations of the edits (e.g. "insert a new Foo after "Foo2"). + * + * TODO: Get explanation strings from o1. + * + * TODO: Tests of range edits. + * + * TODO: Handle 429 rate limit error from OpenAI. + * + * TODO: Add an app-specific guidance string. + * + * TODO: Give the model a final chance to evaluate the result. + * + * TODO: Separate system prompt into [system, user, system] for security. + * + * TODO: Top level arrays are not supported with current DSL. + * + * TODO: Structured Output fails when multiple schema types have the same first field name (e.g. id: sf.identifier on multiple types). + * + * TODO: Pass descriptions from schema metadata to the generated TS types that we put in the prompt + */ + +/** + * This is the field we force the LLM to generate to avoid any type ambiguity (e.g. a vector and a point both have x/y and are ambiguous without the LLM telling us which it means). + */ +export const typeField = "__fluid_type"; + +/** + * A field that is auto-generated and injected into nodes before passing data to the LLM to ensure the LLM can refer to nodes in a stable way. + */ +export const objectIdKey = "__fluid_objectId"; + +/** + * Describes an edit to a field within a node. + * @remarks TODO: what is the [key: string] for? + */ +export interface TreeEditObject { + [key: string]: TreeEditValue; + [typeField]: string; +} +/** + * An array of {@link TreeEditValue}'s, allowing a single {@link TreeEdit} to contain edits to multiple fields. + */ +export type TreeEditArray = TreeEditValue[]; + +/** + * The potential values for a given {@link TreeEdit}. + * @remarks These values are typically a field within a node or an entire node, + */ +export type TreeEditValue = JsonPrimitive | TreeEditObject | TreeEditArray; + +/** + * This is the the final object we expected from an LLM response. + * @remarks Because TreeEdit can be multiple different types (polymorphic), + * we need to wrap to avoid anyOf at the root level when generating the necessary JSON Schema. + */ +export interface EditWrapper { + // eslint-disable-next-line @rushstack/no-new-null + edit: TreeEdit | null; +} + +/** + * Union type representing all possible types of edits that can be made to a tree. + */ +export type TreeEdit = Insert | Modify | Remove | Move; + +/** + * The base interface for all types of {@link TreeEdit}. + */ +export interface Edit { + explanation: string; + type: "insert" | "modify" | "remove" | "move"; +} + +/** + * This object provides a way to 'select' either a given node or a range of nodes in an array. + */ +export type Selection = ObjectTarget | Range; + +/** + * A Target object for an {@link TreeEdit}, identified by the target object's Id + */ +export interface ObjectTarget { + target: string; +} + +/** + * Desribes where an object can be inserted into an array. + * For example, if you have an array with 5 objects, and you insert an object at index 3, this differentiates whether you want + * the existing item at index 3 to be shifted forward (if the 'location' is 'start') or shifted backwards (if the 'location' is 'end') + * + * @remarks TODO: Allow support for nested arrays + */ +export interface ArrayPlace { + type: "arrayPlace"; + parentId: string; + field: string; + location: "start" | "end"; +} + +/** + * Desribes where an object can be inserted into an array. + * For example, if you have an array with 5 objects, and you insert an object at index 3, this differentiates whether you want + * the existing item at index 3 to be shifted forward (if the 'location' is 'start') or shifted backwards (if the 'location' is 'end') + * + * @remarks Why does this and {@link ArrayPlace} exist together? + */ +export interface ObjectPlace extends ObjectTarget { + type: "objectPlace"; + // No "start" or "end" because we don't have a way to refer to arrays directly. + place: "before" | "after"; +} + +/** + * A range of objects within an array. This allows the LLM to select multiple nodes at once, + * for example during an {@link Remove} operation to remove a range of nodes. + */ +export interface Range { + from: ObjectPlace; + to: ObjectPlace; +} + +/** + * Describes an operation to insert a new node into the tree. + */ +export interface Insert extends Edit { + type: "insert"; + content: TreeEditObject | JsonPrimitive; + destination: ObjectPlace | ArrayPlace; +} + +/** + * Describes an operation to modify an existing node in the tree. + */ +export interface Modify extends Edit { + type: "modify"; + target: ObjectTarget; + field: string; + modification: TreeEditValue; +} + +/** + * Describes an operation to remove either a specific node or a range of nodes in an array. + */ +export interface Remove extends Edit { + type: "remove"; + source: Selection; +} + +/** + * Describes an operation to move a node within an array + */ +export interface Move extends Edit { + type: "move"; + source: Selection; + destination: ObjectPlace | ArrayPlace; +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/idGenerator.ts b/packages/framework/ai-collab/src/explicit-strategy/idGenerator.ts new file mode 100644 index 000000000000..9669cda704a3 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/idGenerator.ts @@ -0,0 +1,90 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { assert, oob } from "@fluidframework/core-utils/internal"; +import { Tree, NodeKind } from "@fluidframework/tree/internal"; +import type { + TreeNode, + ImplicitFieldSchema, + TreeArrayNode, + TreeFieldFromImplicitField, +} from "@fluidframework/tree/internal"; + +/** + * Given a tree, generates a set of LLM-friendly, unique IDs for each node in the tree. + * @remarks The ability to uniquely and stably in the tree is important for the LLM and this library to create and distinguish between different types certain {@link TreeEdit}s. + */ +export class IdGenerator { + private readonly idCountMap = new Map(); + private readonly prefixMap = new Map(); + private readonly nodeToIdMap = new Map(); + private readonly idToNodeMap = new Map(); + + public constructor() {} + + public getOrCreateId(node: TreeNode): string { + const existingID = this.nodeToIdMap.get(node); + if (existingID !== undefined) { + return existingID; + } + + const schema = Tree.schema(node).identifier; + const id = this.generateID(schema); + this.nodeToIdMap.set(node, id); + this.idToNodeMap.set(id, node); + + return id; + } + + public getNode(id: string): TreeNode | undefined { + return this.idToNodeMap.get(id); + } + + public getId(node: TreeNode): string | undefined { + return this.nodeToIdMap.get(node); + } + + public assignIds(node: TreeFieldFromImplicitField): string | undefined { + if (typeof node === "object" && node !== null) { + const schema = Tree.schema(node as unknown as TreeNode); + if (schema.kind === NodeKind.Array) { + for (const element of node as unknown as TreeArrayNode) { + this.assignIds(element); + } + } else { + // TODO: SharedTree Team needs to either publish TreeNode as a class to use .instanceof() or a typeguard. + // Uncomment this assertion back once we have a typeguard ready. + // assert(isTreeNode(node), "Non-TreeNode value in tree."); + const objId = this.getOrCreateId(node as TreeNode); + for (const key of Object.keys(node)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument + this.assignIds((node as unknown as any)[key]); + } + return objId; + } + } + return undefined; + } + + private generateID(schema: string): string { + const segments = schema.split("."); + + // If there's no period, the schema itself is the last segment + const lastSegment = segments[segments.length - 1] ?? oob(); + const prefix = segments.length > 1 ? segments.slice(0, -1).join(".") : ""; + + // Check if the last segment already exists with a different prefix + assert( + !this.prefixMap.has(lastSegment) || this.prefixMap.get(lastSegment) === prefix, + "Different scopes not supported yet.", + ); + + this.prefixMap.set(lastSegment, prefix); + const count = this.idCountMap.get(lastSegment) ?? 1; + this.idCountMap.set(lastSegment, count + 1); + + return `${lastSegment}${count}`; + } +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/index.ts b/packages/framework/ai-collab/src/explicit-strategy/index.ts new file mode 100644 index 000000000000..3ce54820c4fb --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/index.ts @@ -0,0 +1,364 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { + getSimpleSchema, + Tree, + type SimpleTreeSchema, + type TreeNode, +} from "@fluidframework/tree/internal"; +// eslint-disable-next-line import/no-internal-modules +import { zodResponseFormat } from "openai/helpers/zod"; +import type { + ChatCompletionCreateParams, + // eslint-disable-next-line import/no-internal-modules +} from "openai/resources/index.mjs"; +import { z } from "zod"; + +import type { OpenAiClientOptions, TokenLimits, TokenUsage } from "../aiCollabApi.js"; + +import { applyAgentEdit } from "./agentEditReducer.js"; +import type { EditWrapper, TreeEdit } from "./agentEditTypes.js"; +import { IdGenerator } from "./idGenerator.js"; +import { + getEditingSystemPrompt, + getPlanningSystemPrompt, + getReviewSystemPrompt, + toDecoratedJson, + type EditLog, +} from "./promptGeneration.js"; +import { generateGenericEditTypes } from "./typeGeneration.js"; +import { fail } from "./utils.js"; + +const DEBUG_LOG: string[] = []; + +/** + * {@link generateTreeEdits} options. + * + * @internal + */ +export interface GenerateTreeEditsOptions { + openAI: OpenAiClientOptions; + treeNode: TreeNode; + prompt: { + systemRoleContext: string; + userAsk: string; + }; + limiters?: { + abortController?: AbortController; + maxSequentialErrors?: number; + maxModelCalls?: number; + tokenLimits?: TokenLimits; + }; + finalReviewStep?: boolean; + validator?: (newContent: TreeNode) => void; + dumpDebugLog?: boolean; + planningStep?: boolean; +} + +interface GenerateTreeEditsSuccessResponse { + status: "success"; + tokensUsed: TokenUsage; +} + +interface GenerateTreeEditsErrorResponse { + status: "failure" | "partial-failure"; + errorMessage: "tokenLimitExceeded" | "tooManyErrors" | "tooManyModelCalls" | "aborted"; + tokensUsed: TokenUsage; +} + +/** + * Prompts the provided LLM client to generate valid tree edits. + * Applies those edits to the provided tree branch before returning. + * + * @remarks + * - Optional root nodes are not supported + * - Primitive root nodes are not supported + * + * @internal + */ +export async function generateTreeEdits( + options: GenerateTreeEditsOptions, +): Promise { + const idGenerator = new IdGenerator(); + const editLog: EditLog = []; + let editCount = 0; + let sequentialErrorCount = 0; + + const simpleSchema = getSimpleSchema(Tree.schema(options.treeNode)); + + const tokensUsed = { inputTokens: 0, outputTokens: 0 }; + + try { + for await (const edit of generateEdits( + options, + simpleSchema, + idGenerator, + editLog, + options.limiters?.tokenLimits, + tokensUsed, + )) { + try { + const result = applyAgentEdit( + edit, + idGenerator, + simpleSchema.definitions, + options.validator, + ); + const explanation = result.explanation; + editLog.push({ edit: { ...result, explanation } }); + sequentialErrorCount = 0; + } catch (error: unknown) { + if (error instanceof Error) { + sequentialErrorCount += 1; + editLog.push({ edit, error: error.message }); + DEBUG_LOG?.push(`Error: ${error.message}`); + } else { + throw error; + } + } + + const responseStatus = + editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure"; + + if (options.limiters?.abortController?.signal.aborted === true) { + return { + status: responseStatus, + errorMessage: "aborted", + tokensUsed, + }; + } + + if ( + sequentialErrorCount > + (options.limiters?.maxSequentialErrors ?? Number.POSITIVE_INFINITY) + ) { + return { + status: responseStatus, + errorMessage: "tooManyErrors", + tokensUsed, + }; + } + + if (++editCount >= (options.limiters?.maxModelCalls ?? Number.POSITIVE_INFINITY)) { + return { + status: responseStatus, + errorMessage: "tooManyModelCalls", + tokensUsed, + }; + } + } + } catch (error: unknown) { + if (error instanceof Error) { + DEBUG_LOG?.push(`Error: ${error.message}`); + } + + if (options.dumpDebugLog ?? false) { + console.log(DEBUG_LOG.join("\n\n")); + DEBUG_LOG.length = 0; + } + + if (error instanceof TokenLimitExceededError) { + return { + status: + editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure", + errorMessage: "tokenLimitExceeded", + tokensUsed, + }; + } + throw error; + } + + if (options.dumpDebugLog ?? false) { + console.log(DEBUG_LOG.join("\n\n")); + DEBUG_LOG.length = 0; + } + + return { + status: "success", + tokensUsed, + }; +} + +interface ReviewResult { + goalAccomplished: "yes" | "no"; +} + +/** + * Generates a single {@link TreeEdit} from an LLM. + * + * @remarks + * The design of this async generator function is such that which each iteration of this functions values, + * an LLM will be prompted to generate the next value (a {@link TreeEdit}) based on the users ask. + * Once the LLM believes it has completed the user's ask, it will no longer return an edit and as a result + * this generator will no longer yield a next value. + */ +async function* generateEdits( + options: GenerateTreeEditsOptions, + simpleSchema: SimpleTreeSchema, + idGenerator: IdGenerator, + editLog: EditLog, + tokenLimits: TokenLimits | undefined, + tokensUsed: TokenUsage, +): AsyncGenerator { + const [types, rootTypeName] = generateGenericEditTypes(simpleSchema, true); + + let plan: string | undefined; + if (options.planningStep !== undefined) { + const planningPromt = getPlanningSystemPrompt( + options.treeNode, + options.prompt.userAsk, + options.prompt.systemRoleContext, + ); + DEBUG_LOG?.push(planningPromt); + plan = await getStringFromLlm(planningPromt, options.openAI, tokensUsed); + DEBUG_LOG?.push(`AI Generated the following plan: ${planningPromt}`); + } + + const originalDecoratedJson = + (options.finalReviewStep ?? false) + ? toDecoratedJson(idGenerator, options.treeNode) + : undefined; + // reviewed is implicitly true if finalReviewStep is false + let hasReviewed = (options.finalReviewStep ?? false) ? false : true; + async function getNextEdit(): Promise { + const systemPrompt = getEditingSystemPrompt( + options.prompt.userAsk, + idGenerator, + options.treeNode, + editLog, + options.prompt.systemRoleContext, + plan, + ); + + DEBUG_LOG?.push(systemPrompt); + + const schema = types[rootTypeName] ?? fail("Root type not found."); + const wrapper = await getStructuredOutputFromLlm( + systemPrompt, + options.openAI, + schema, + "A JSON object that represents an edit to a JSON tree.", + tokensUsed, + ); + + // eslint-disable-next-line unicorn/no-null + DEBUG_LOG?.push(JSON.stringify(wrapper, null, 2)); + if (wrapper === undefined) { + DEBUG_LOG?.push("Failed to get response"); + return undefined; + } + + if (wrapper.edit === null) { + DEBUG_LOG?.push("No more edits."); + if ((options.finalReviewStep ?? false) && !hasReviewed) { + const reviewResult = await reviewGoal(); + if (reviewResult === undefined) { + DEBUG_LOG?.push("Failed to get review response"); + return undefined; + } + // eslint-disable-next-line require-atomic-updates + hasReviewed = true; + if (reviewResult.goalAccomplished === "yes") { + return undefined; + } else { + // eslint-disable-next-line require-atomic-updates + editLog.length = 0; + return getNextEdit(); + } + } + } else { + return wrapper.edit; + } + } + + async function reviewGoal(): Promise { + const systemPrompt = getReviewSystemPrompt( + options.prompt.userAsk, + idGenerator, + options.treeNode, + originalDecoratedJson ?? fail("Original decorated tree not provided."), + options.prompt.systemRoleContext, + ); + + DEBUG_LOG?.push(systemPrompt); + + const schema = z.object({ + goalAccomplished: z + .enum(["yes", "no"]) + .describe('Whether the user\'s goal was met in the "after" tree.'), + }); + return getStructuredOutputFromLlm(systemPrompt, options.openAI, schema); + } + + let edit = await getNextEdit(); + while (edit !== undefined) { + yield edit; + if (tokensUsed.inputTokens > (tokenLimits?.inputTokens ?? Number.POSITIVE_INFINITY)) { + throw new TokenLimitExceededError("Input token limit exceeded."); + } + if (tokensUsed.outputTokens > (tokenLimits?.outputTokens ?? Number.POSITIVE_INFINITY)) { + throw new TokenLimitExceededError("Output token limit exceeded."); + } + edit = await getNextEdit(); + } +} + +/** + * Calls the LLM to generate a structured output response based on the provided prompt. + */ +async function getStructuredOutputFromLlm( + prompt: string, + openAi: OpenAiClientOptions, + structuredOutputSchema: Zod.ZodTypeAny, + description?: string, + tokensUsed?: TokenUsage, +): Promise { + const response_format = zodResponseFormat(structuredOutputSchema, "SharedTreeAI", { + description, + }); + + const body: ChatCompletionCreateParams = { + messages: [{ role: "system", content: prompt }], + model: openAi.modelName ?? "gpt-4o", + response_format, + }; + + const result = await openAi.client.beta.chat.completions.parse(body); + + if (result.usage !== undefined && tokensUsed !== undefined) { + tokensUsed.inputTokens += result.usage?.prompt_tokens; + tokensUsed.outputTokens += result.usage?.completion_tokens; + } + + // TODO: fix types so this isn't null and doesn't need a cast + // The type should be derived from the zod schema + return result.choices[0]?.message.parsed as T | undefined; +} + +/** + * Calls the LLM to generate a response based on the provided prompt. + */ +async function getStringFromLlm( + prompt: string, + openAi: OpenAiClientOptions, + tokensUsed?: TokenUsage, +): Promise { + const body: ChatCompletionCreateParams = { + messages: [{ role: "system", content: prompt }], + model: openAi.modelName ?? "gpt-4o", + }; + + const result = await openAi.client.chat.completions.create(body); + + if (result.usage !== undefined && tokensUsed !== undefined) { + tokensUsed.inputTokens += result.usage?.prompt_tokens; + tokensUsed.outputTokens += result.usage?.completion_tokens; + } + + return result.choices[0]?.message.content ?? undefined; +} + +class TokenLimitExceededError extends Error {} diff --git a/packages/framework/ai-collab/src/explicit-strategy/jsonTypes.ts b/packages/framework/ai-collab/src/explicit-strategy/jsonTypes.ts new file mode 100644 index 000000000000..34551c4fb560 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/jsonTypes.ts @@ -0,0 +1,27 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +/** + * Primitive JSON Types + */ +// eslint-disable-next-line @rushstack/no-new-null +export type JsonPrimitive = string | number | boolean | null; + +/** + * A JSON Object, a collection of key to {@link JsonValue} pairs + */ +// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style +export interface JsonObject { + [key: string]: JsonValue; +} +/** + * An Array of {@link JsonValue} + */ +export type JsonArray = JsonValue[]; + +/** + * A union type of all possible JSON values, including primitives, objects, and arrays + */ +export type JsonValue = JsonPrimitive | JsonObject | JsonArray; diff --git a/packages/framework/ai-collab/src/explicit-strategy/promptGeneration.ts b/packages/framework/ai-collab/src/explicit-strategy/promptGeneration.ts new file mode 100644 index 000000000000..848771635023 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/promptGeneration.ts @@ -0,0 +1,294 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { assert } from "@fluidframework/core-utils/internal"; +import { + NodeKind, + type ImplicitFieldSchema, + type TreeFieldFromImplicitField, + getJsonSchema, + type JsonFieldSchema, + type JsonNodeSchema, + type JsonSchemaRef, + type JsonTreeSchema, + getSimpleSchema, + Tree, + type TreeNode, +} from "@fluidframework/tree/internal"; +// eslint-disable-next-line import/no-internal-modules +import { createZodJsonValidator } from "typechat/zod"; + +import { objectIdKey, type TreeEdit } from "./agentEditTypes.js"; +import type { IdGenerator } from "./idGenerator.js"; +import { generateGenericEditTypes } from "./typeGeneration.js"; +import { fail } from "./utils.js"; + +/** + * A log of edits that have been made to a tree. + * @remarks This is primarily used to help an LLM keep track of the active changes it has made. + */ +export type EditLog = { + edit: TreeEdit; + error?: string; +}[]; + +/** + * TBD + */ +export function toDecoratedJson( + idGenerator: IdGenerator, + root: TreeFieldFromImplicitField, +): string { + idGenerator.assignIds(root); + const stringified: string = JSON.stringify(root, (_, value) => { + if (typeof value === "object" && !Array.isArray(value) && value !== null) { + // TODO: SharedTree Team needs to either publish TreeNode as a class to use .instanceof() or a typeguard. + // Uncomment this assertion back once we have a typeguard ready. + // assert(isTreeNode(node), "Non-TreeNode value in tree."); + const objId = + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + idGenerator.getId(value) ?? fail("ID of new node should have been assigned."); + assert( + !Object.prototype.hasOwnProperty.call(value, objectIdKey), + `Collision of object id property.`, + ); + return { + [objectIdKey]: objId, + ...value, + } as unknown; + } + return value as unknown; + }); + return stringified; +} + +/** + * Generates a prompt designed to make an LLM produce a plan to edit the SharedTree to accomplish a user-specified goal. + */ +export function getPlanningSystemPrompt( + treeNode: TreeNode, + userPrompt: string, + systemRoleContext?: string, +): string { + const schema = Tree.schema(treeNode); + + const promptFriendlySchema = getPromptFriendlyTreeSchema(getJsonSchema(schema)); + const role = `I'm an agent who makes plans for another agent to achieve a user-specified goal to update the state of an application.${ + systemRoleContext === undefined + ? "" + : ` + The other agent follows this guidance: ${systemRoleContext}` + }`; + + const systemPrompt = ` + ${role} + The application state tree is a JSON object with the following schema: ${promptFriendlySchema} + The current state is: ${JSON.stringify(treeNode)}. + The user requested that I accomplish the following goal: + "${userPrompt}" + I've made a plan to accomplish this goal by doing a sequence of edits to the tree. + Edits can include setting the root, inserting, modifying, removing, or moving elements in the tree. + Here is my plan:`; + + return systemPrompt; +} + +/** + * Generates a prompt that provides a history of the edits an LLM has made to a SharedTree as well as any errors that occured from attemping to apply each respsecitve edit to the tree. + */ +export function createEditListHistoryPrompt(edits: EditLog): string { + return edits + .map((edit, index) => { + const error = + edit.error === undefined + ? "" + : ` This edit produced an error, and was discarded. The error message was: "${edit.error}"`; + return `${index + 1}. ${JSON.stringify(edit.edit)}${error}`; + }) + .join("\n"); +} + +/** + * Generates the main prompt of this explicit strategy. + * This prompt is designed to give an LLM instructions on how it can modify a SharedTree using specific types of {@link TreeEdit}'s + * and provides with both a serialized version of the current state of the provided tree node as well as the interfaces that compromise said tree nodes data. + */ +export function getEditingSystemPrompt( + userPrompt: string, + idGenerator: IdGenerator, + treeNode: TreeNode, + log: EditLog, + appGuidance?: string, + plan?: string, +): string { + const schema = Tree.schema(treeNode); + const promptFriendlySchema = getPromptFriendlyTreeSchema(getJsonSchema(schema)); + const decoratedTreeJson = toDecoratedJson(idGenerator, treeNode); + + const role = `You are a collaborative agent who interacts with a JSON tree by performing edits to achieve a user-specified goal.${ + appGuidance === undefined + ? "" + : ` + The application that owns the JSON tree has the following guidance about your role: ${appGuidance}` + }`; + + const treeSchemaString = createZodJsonValidator( + ...generateGenericEditTypes(getSimpleSchema(schema), false), + ).getSchemaText(); + + // TODO: security: user prompt in system prompt + const systemPrompt = ` + ${role} + Edits are JSON objects that conform to the following schema. + The top level object you produce is an "EditWrapper" object which contains one of "Insert", "Modify", "Remove", "Move", or null. + ${treeSchemaString} + The tree is a JSON object with the following schema: ${promptFriendlySchema} + ${plan === undefined ? "" : `You have made a plan to accomplish the user's goal. The plan is: "${plan}". You will perform one or more edits that correspond to that plan to accomplish the goal.`} + ${ + log.length === 0 + ? "" + : `You have already performed the following edits: + ${createEditListHistoryPrompt(log)} + This means that the current state of the tree reflects these changes.` + } + The current state of the tree is: ${decoratedTreeJson}. + ${log.length > 0 ? "Before you made the above edits t" : "T"}he user requested you accomplish the following goal: + "${userPrompt}" + If the goal is now completed or is impossible, you should return null. + Otherwise, you should create an edit that makes progress towards the goal. It should have an English description ("explanation") of which edit to perform (specifying one of the allowed edit types).`; + return systemPrompt; +} + +/** + * Generates a prompt designed to make an LLM review the edits it created and applied to a SharedTree based + * on a user-specified goal. This prompt is designed to give the LLM's ability to correct for mistakes and improve the accuracy/fidelity of its final set of tree edits + */ +export function getReviewSystemPrompt( + userPrompt: string, + idGenerator: IdGenerator, + treeNode: TreeNode, + originalDecoratedJson: string, + appGuidance?: string, +): string { + const schema = Tree.schema(treeNode); + const promptFriendlySchema = getPromptFriendlyTreeSchema(getJsonSchema(schema)); + const decoratedTreeJson = toDecoratedJson(idGenerator, treeNode); + + const role = `You are a collaborative agent who interacts with a JSON tree by performing edits to achieve a user-specified goal.${ + appGuidance === undefined + ? "" + : ` + The application that owns the JSON tree has the following guidance: ${appGuidance}` + }`; + + // TODO: security: user prompt in system prompt + const systemPrompt = ` + ${role} + You have performed a number of actions already to accomplish a user request. + You must review the resulting state to determine if the actions you performed successfully accomplished the user's goal. + The tree is a JSON object with the following schema: ${promptFriendlySchema} + The state of the tree BEFORE changes was: ${originalDecoratedJson}. + The state of the tree AFTER changes is: ${decoratedTreeJson}. + The user requested that the following goal should be accomplished: + ${userPrompt} + Was the goal accomplished?`; + return systemPrompt; +} + +/** + * Converts a fully-qualified SharedTree schema name to a single-word name for use in textual TypeScript-style types. + * + * @remarks + * - TODO: Determine what to do with user-provided names that include periods (e.g. "Foo.Bar"). + * - TODO: Should probably ensure name starts with an uppercase character. + */ +export function getPromptFriendlyTreeSchema(jsonSchema: JsonTreeSchema): string { + let stringifiedSchema = ""; + for (const [name, def] of Object.entries(jsonSchema.$defs)) { + if (def.type !== "object" || def._treeNodeSchemaKind === NodeKind.Map) { + continue; + } + + let stringifiedEntry = `interface ${getFriendlySchemaName(name)} {`; + + for (const [fieldName, fieldSchema] of Object.entries(def.properties)) { + let typeString: string; + if (isJsonSchemaRef(fieldSchema)) { + const nextFieldName = fieldSchema.$ref; + const nextDef = getDef(jsonSchema.$defs, nextFieldName); + typeString = `${getTypeString(jsonSchema.$defs, [nextFieldName, nextDef])}`; + } else { + typeString = `${getAnyOfTypeString(jsonSchema.$defs, fieldSchema.anyOf, true)}`; + } + if (def.required && !def.required.includes(fieldName)) { + typeString = `${typeString} | undefined`; + } + stringifiedEntry += ` ${fieldName}: ${typeString};`; + } + + stringifiedEntry += " }"; + + stringifiedSchema += (stringifiedSchema === "" ? "" : " ") + stringifiedEntry; + } + return stringifiedSchema; +} + +function getTypeString( + defs: Record, + [name, currentDef]: [string, JsonNodeSchema], +): string { + const { _treeNodeSchemaKind } = currentDef; + if (_treeNodeSchemaKind === NodeKind.Leaf) { + return currentDef.type; + } + if (_treeNodeSchemaKind === NodeKind.Object) { + return getFriendlySchemaName(name); + } + if (_treeNodeSchemaKind === NodeKind.Array) { + const items = currentDef.items; + const innerType = isJsonSchemaRef(items) + ? getTypeString(defs, [items.$ref, getDef(defs, items.$ref)]) + : getAnyOfTypeString(defs, items.anyOf); + return `${innerType}[]`; + } + fail("Non-object, non-leaf, non-array schema type."); +} + +function getAnyOfTypeString( + defs: Record, + refList: JsonSchemaRef[], + topLevel = false, +): string { + const typeNames: string[] = []; + for (const ref of refList) { + typeNames.push(getTypeString(defs, [ref.$ref, getDef(defs, ref.$ref)])); + } + const typeString = typeNames.join(" | "); + return topLevel ? typeString : `(${typeString})`; +} + +function isJsonSchemaRef(field: JsonFieldSchema): field is JsonSchemaRef { + return (field as JsonSchemaRef).$ref !== undefined; +} + +function getDef(defs: Record, ref: string): JsonNodeSchema { + // strip the "#/$defs/" prefix + const strippedRef = ref.slice(8); + const nextDef = defs[strippedRef]; + assert(nextDef !== undefined, "Ref not found."); + return nextDef; +} + +/** + * TBD + */ +export function getFriendlySchemaName(schemaName: string): string { + const matches = schemaName.match(/[^.]+$/); + if (matches === null) { + // empty scope + return schemaName; + } + return matches[0]; +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/typeGeneration.ts b/packages/framework/ai-collab/src/explicit-strategy/typeGeneration.ts new file mode 100644 index 000000000000..e488d08a1067 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/typeGeneration.ts @@ -0,0 +1,374 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { assert } from "@fluidframework/core-utils/internal"; +import { FieldKind, NodeKind, ValueSchema } from "@fluidframework/tree/internal"; +import type { + SimpleFieldSchema, + SimpleNodeSchema, + SimpleTreeSchema, +} from "@fluidframework/tree/internal"; +import { z } from "zod"; + +import { objectIdKey, typeField } from "./agentEditTypes.js"; +import { fail, getOrCreate, mapIterable } from "./utils.js"; + +/** + * Zod Object type used to represent & validate the ObjectTarget type within a {@link TreeEdit}. + * @remarks this is used as a component with {@link generateGenericEditTypes} to produce the final zod validation objects. + */ +const objectTarget = z.object({ + target: z + .string() + .describe( + `The id of the object (as specified by the object's ${objectIdKey} property) that is being referenced`, + ), +}); +/** + * Zod Object type used to represent & validate the ObjectPlace type within a {@link TreeEdit}. + * @remarks this is used as a component with {@link generateGenericEditTypes} to produce the final zod validation objects. + */ +const objectPlace = z + .object({ + type: z.enum(["objectPlace"]), + target: z + .string() + .describe( + `The id (${objectIdKey}) of the object that the new/moved object should be placed relative to. This must be the id of an object that already existed in the tree content that was originally supplied.`, + ), + place: z + .enum(["before", "after"]) + .describe( + "Where the new/moved object will be relative to the target object - either just before or just after", + ), + }) + .describe( + "A pointer to a location either just before or just after an object that is in an array", + ); +/** + * Zod Object type used to represent & validate the ArrayPlace type within a {@link TreeEdit}. + * @remarks this is used as a component with {@link generateGenericEditTypes} to produce the final zod validation objects. + */ +const arrayPlace = z + .object({ + type: z.enum(["arrayPlace"]), + parentId: z + .string() + .describe( + `The id (${objectIdKey}) of the parent object of the array. This must be the id of an object that already existed in the tree content that was originally supplied.`, + ), + field: z.string().describe("The key of the array to insert into"), + location: z + .enum(["start", "end"]) + .describe("Where to insert into the array - either the start or the end"), + }) + .describe( + `either the "start" or "end" of an array, as specified by a "parent" ObjectTarget and a "field" name under which the array is stored (useful for prepending or appending)`, + ); +/** + * Zod Object type used to represent & validate the Range type within a {@link TreeEdit}. + * @remarks this is used as a component with {@link generateGenericEditTypes} to produce the final zod validation objects. + */ +const range = z + .object({ + from: objectPlace, + to: objectPlace, + }) + .describe( + 'A range of objects in the same array specified by a "from" and "to" Place. The "to" and "from" objects MUST be in the same array.', + ); +/** + * Cache used to prevent repeatedly generating the same Zod validation objects for the same {@link SimpleTreeSchema} as generate propts for repeated calls to an LLM + */ +const cache = new WeakMap>(); + +/** + * Generates a set of ZOD validation objects for the various types of data that can be put into the provided {@link SimpleTreeSchema} + * and then uses those sets to generate an all-encompassing ZOD object for each type of {@link TreeEdit} that can validate any of the types of data that can be put into the tree. + * + * @returns a Record of schema names to Zod validation objects, and the name of the root schema used to encompass all of the other schemas. + * + * @remarks The return type of this function is designed to work with Typechat's createZodJsonValidator as well as be used as the JSON schema for OpenAi's structured output response format. + */ +export function generateGenericEditTypes( + schema: SimpleTreeSchema, + generateDomainTypes: boolean, +): [Record, root: string] { + return getOrCreate(cache, schema, () => { + const insertSet = new Set(); + const modifyFieldSet = new Set(); + const modifyTypeSet = new Set(); + const typeMap = new Map(); + for (const name of schema.definitions.keys()) { + getOrCreateType( + schema.definitions, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + name, + ); + } + function getType(allowedTypes: ReadonlySet): Zod.ZodTypeAny { + switch (allowedTypes.size) { + case 0: { + return z.never(); + } + case 1: { + return ( + typeMap.get(tryGetSingleton(allowedTypes) ?? fail("Expected singleton")) ?? + fail("Unknown type") + ); + } + default: { + const types = Array.from( + allowedTypes, + (name) => typeMap.get(name) ?? fail("Unknown type"), + ); + assert(hasAtLeastTwo(types), "Expected at least two types"); + return z.union(types); + } + } + } + const insert = z + .object({ + type: z.literal("insert"), + explanation: z.string().describe(editDescription), + content: generateDomainTypes + ? getType(insertSet) + : z.any().describe("Domain-specific content here"), + destination: z.union([arrayPlace, objectPlace]), + }) + .describe("Inserts a new object at a specific Place or ArrayPlace."); + const remove = z + .object({ + type: z.literal("remove"), + explanation: z.string().describe(editDescription), + source: z.union([objectTarget, range]), + }) + .describe("Deletes an object or Range of objects from the tree."); + const move = z + .object({ + type: z.literal("move"), + explanation: z.string().describe(editDescription), + source: z.union([objectTarget, range]), + destination: z.union([arrayPlace, objectPlace]), + }) + .describe("Moves an object or Range of objects to a new Place or ArrayPlace."); + const modify = z + .object({ + type: z.enum(["modify"]), + explanation: z.string().describe(editDescription), + target: objectTarget, + field: z.enum([...modifyFieldSet] as [string, ...string[]]), // Modify with appropriate fields + modification: generateDomainTypes + ? getType(modifyTypeSet) + : z.any().describe("Domain-specific content here"), + }) + .describe("Sets a field on a specific ObjectTarget."); + const editTypes = [insert, remove, move, modify, z.null()] as const; + const editWrapper = z.object({ + edit: z + .union(editTypes) + .describe("The next edit to apply to the tree, or null if the task is complete."), + }); + const typeRecord: Record = { + ObjectTarget: objectTarget, + ObjectPlace: objectPlace, + ArrayPlace: arrayPlace, + Range: range, + Insert: insert, + Remove: remove, + Move: move, + Modify: modify, + EditWrapper: editWrapper, + }; + return [typeRecord, "EditWrapper"]; + }); +} +const editDescription = + "A description of what this edit is meant to accomplish in human readable English"; +function getOrCreateType( + definitionMap: ReadonlyMap, + typeMap: Map, + insertSet: Set, + modifyFieldSet: Set, + modifyTypeSet: Set, + definition: string, +): Zod.ZodTypeAny { + return getOrCreate(typeMap, definition, () => { + const nodeSchema = definitionMap.get(definition) ?? fail("Unexpected definition"); + switch (nodeSchema.kind) { + case NodeKind.Object: { + for (const [key, field] of Object.entries(nodeSchema.fields)) { + // TODO: Remove when AI better + if ( + Array.from( + field.allowedTypes, + (n) => definitionMap.get(n) ?? fail("Unknown definition"), + ).some((n) => n.kind === NodeKind.Array) + ) { + continue; + } + modifyFieldSet.add(key); + for (const type of field.allowedTypes) { + modifyTypeSet.add(type); + } + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const properties = Object.fromEntries( + Object.entries(nodeSchema.fields) + .map(([key, field]) => { + return [ + key, + getOrCreateTypeForField( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + field, + ), + ]; + }) + .filter(([, value]) => value !== undefined), + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + properties[typeField] = z.enum([definition]); + return z.object(properties); + } + case NodeKind.Array: { + for (const [name] of Array.from( + nodeSchema.allowedTypes, + (n): [string, SimpleNodeSchema] => [ + n, + definitionMap.get(n) ?? fail("Unknown definition"), + ], + ).filter( + ([_, schema]) => schema.kind === NodeKind.Object || schema.kind === NodeKind.Leaf, + )) { + insertSet.add(name); + } + return z.array( + getTypeForAllowedTypes( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + nodeSchema.allowedTypes, + ), + ); + } + case NodeKind.Leaf: { + switch (nodeSchema.leafKind) { + case ValueSchema.Boolean: { + return z.boolean(); + } + case ValueSchema.Number: { + return z.number(); + } + case ValueSchema.String: { + return z.string(); + } + case ValueSchema.Null: { + return z.null(); + } + default: { + throw new Error(`Unsupported leaf kind ${NodeKind[nodeSchema.leafKind]}.`); + } + } + } + default: { + throw new Error(`Unsupported node kind ${NodeKind[nodeSchema.kind]}.`); + } + } + }); +} +function getOrCreateTypeForField( + definitionMap: ReadonlyMap, + typeMap: Map, + insertSet: Set, + modifyFieldSet: Set, + modifyTypeSet: Set, + fieldSchema: SimpleFieldSchema, +): Zod.ZodTypeAny | undefined { + switch (fieldSchema.kind) { + case FieldKind.Required: { + return getTypeForAllowedTypes( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + fieldSchema.allowedTypes, + ); + } + case FieldKind.Optional: { + return z.union([ + z.null(), + getTypeForAllowedTypes( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + fieldSchema.allowedTypes, + ), + ]); + } + case FieldKind.Identifier: { + return undefined; + } + default: { + throw new Error(`Unsupported field kind ${NodeKind[fieldSchema.kind]}.`); + } + } +} +function getTypeForAllowedTypes( + definitionMap: ReadonlyMap, + typeMap: Map, + insertSet: Set, + modifyFieldSet: Set, + modifyTypeSet: Set, + allowedTypes: ReadonlySet, +): Zod.ZodTypeAny { + const single = tryGetSingleton(allowedTypes); + if (single === undefined) { + const types = [ + ...mapIterable(allowedTypes, (name) => { + return getOrCreateType( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + name, + ); + }), + ]; + assert(hasAtLeastTwo(types), "Expected at least two types"); + return z.union(types); + } else { + return getOrCreateType( + definitionMap, + typeMap, + insertSet, + modifyFieldSet, + modifyTypeSet, + single, + ); + } +} +function tryGetSingleton(set: ReadonlySet): T | undefined { + if (set.size === 1) { + for (const item of set) { + return item; + } + } +} +function hasAtLeastTwo(array: T[]): array is [T, T, ...T[]] { + return array.length >= 2; +} diff --git a/packages/framework/ai-collab/src/explicit-strategy/utils.ts b/packages/framework/ai-collab/src/explicit-strategy/utils.ts new file mode 100644 index 000000000000..49a230f8a9d0 --- /dev/null +++ b/packages/framework/ai-collab/src/explicit-strategy/utils.ts @@ -0,0 +1,60 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +/** + * Subset of Map interface. + * + * @remarks originally from tree/src/util/utils.ts + */ +export interface MapGetSet { + get(key: K): V | undefined; + set(key: K, value: V): void; +} + +/** + * TBD + */ +export function fail(message: string): never { + throw new Error(message); +} + +/** + * Map one iterable to another by transforming each element one at a time + * @param iterable - the iterable to transform + * @param map - the transformation function to run on each element of the iterable + * @returns a new iterable of elements which have been transformed by the `map` function + * + * @remarks originally from tree/src/util/utils.ts + */ +export function* mapIterable( + iterable: Iterable, + map: (t: T) => U, +): IterableIterator { + for (const t of iterable) { + yield map(t); + } +} + +/** + * Retrieve a value from a map with the given key, or create a new entry if the key is not in the map. + * @param map - The map to query/update + * @param key - The key to lookup in the map + * @param defaultValue - a function which returns a default value. This is called and used to set an initial value for the given key in the map if none exists + * @returns either the existing value for the given key, or the newly-created value (the result of `defaultValue`) + * + * @remarks originally from tree/src/util/utils.ts + */ +export function getOrCreate( + map: MapGetSet, + key: K, + defaultValue: (key: K) => V, +): V { + let value = map.get(key); + if (value === undefined) { + value = defaultValue(key); + map.set(key, value); + } + return value; +} diff --git a/packages/framework/ai-collab/src/shared-tree-diff/README.md b/packages/framework/ai-collab/src/implicit-strategy/README.md similarity index 100% rename from packages/framework/ai-collab/src/shared-tree-diff/README.md rename to packages/framework/ai-collab/src/implicit-strategy/README.md diff --git a/packages/framework/ai-collab/src/shared-tree-diff/index.ts b/packages/framework/ai-collab/src/implicit-strategy/index.ts similarity index 100% rename from packages/framework/ai-collab/src/shared-tree-diff/index.ts rename to packages/framework/ai-collab/src/implicit-strategy/index.ts diff --git a/packages/framework/ai-collab/src/shared-tree-diff/sharedTreeBranchManager.ts b/packages/framework/ai-collab/src/implicit-strategy/sharedTreeBranchManager.ts similarity index 97% rename from packages/framework/ai-collab/src/shared-tree-diff/sharedTreeBranchManager.ts rename to packages/framework/ai-collab/src/implicit-strategy/sharedTreeBranchManager.ts index 1c0ee51d58a3..507add5e0eb1 100644 --- a/packages/framework/ai-collab/src/shared-tree-diff/sharedTreeBranchManager.ts +++ b/packages/framework/ai-collab/src/implicit-strategy/sharedTreeBranchManager.ts @@ -10,8 +10,10 @@ import type { TreeViewConfiguration, } from "@fluidframework/tree"; import { + // TODO: Migrate to newer branching API (`TreeContext`) + // eslint-disable-next-line import/no-deprecated getBranch, - type TreeBranch, + type BranchableTree, type TreeBranchFork, type TreeViewAlpha, // eslint-disable-next-line import/no-internal-modules -- This package depends on the branching APIs in Tree which are currently alpha @@ -97,11 +99,12 @@ export class SharedTreeBranchManager { llmResponse: Record | unknown[], ): { differences: Difference[]; - originalBranch: TreeBranch; + originalBranch: BranchableTree; forkBranch: TreeBranchFork; forkView: TreeViewAlpha; newBranchTargetNode: Record | TreeArrayNode; } { + // eslint-disable-next-line import/no-deprecated const originalBranch = getBranch(treeView); const forkBranch = originalBranch.branch(); const forkView = forkBranch.viewWith(treeViewConfiguration) as TreeViewAlpha; @@ -136,11 +139,12 @@ export class SharedTreeBranchManager { absolutePathToObjectNode: ObjectPath, // differences: Difference[], ): { - originalBranch: TreeBranch; + originalBranch: BranchableTree; forkBranch: TreeBranchFork; forkView: TreeViewAlpha; newBranchTargetNode: Record | TreeArrayNode; } { + // eslint-disable-next-line import/no-deprecated const originalBranch = getBranch(treeView); const forkBranch = originalBranch.branch(); const forkView = forkBranch.viewWith(treeViewConfiguration) as TreeViewAlpha; diff --git a/packages/framework/ai-collab/src/shared-tree-diff/sharedTreeDiff.ts b/packages/framework/ai-collab/src/implicit-strategy/sharedTreeDiff.ts similarity index 99% rename from packages/framework/ai-collab/src/shared-tree-diff/sharedTreeDiff.ts rename to packages/framework/ai-collab/src/implicit-strategy/sharedTreeDiff.ts index 6184da2aef79..b79b8d6fea21 100644 --- a/packages/framework/ai-collab/src/shared-tree-diff/sharedTreeDiff.ts +++ b/packages/framework/ai-collab/src/implicit-strategy/sharedTreeDiff.ts @@ -138,6 +138,7 @@ export function sharedTreeDiff( diffs.push({ type: "REMOVE", path: [path], + objectId: undefined, oldValue: objValue, }); continue; @@ -170,6 +171,7 @@ export function sharedTreeDiff( diffs.push({ type: "REMOVE", path: [path], + objectId, oldValue: objValue, }); continue; @@ -181,6 +183,7 @@ export function sharedTreeDiff( diffs.push({ type: "REMOVE", path: [path], + objectId: undefined, oldValue: objValue, }); continue; diff --git a/packages/framework/ai-collab/src/shared-tree-diff/utils.ts b/packages/framework/ai-collab/src/implicit-strategy/utils.ts similarity index 100% rename from packages/framework/ai-collab/src/shared-tree-diff/utils.ts rename to packages/framework/ai-collab/src/implicit-strategy/utils.ts diff --git a/packages/framework/ai-collab/src/index.ts b/packages/framework/ai-collab/src/index.ts index 409ac84cd355..9e711577abaa 100644 --- a/packages/framework/ai-collab/src/index.ts +++ b/packages/framework/ai-collab/src/index.ts @@ -25,4 +25,15 @@ export { createMergableDiffSeries, SharedTreeBranchManager, sharedTreeTraverse, -} from "./shared-tree-diff/index.js"; +} from "./implicit-strategy/index.js"; + +export { + type AiCollabOptions, + type AiCollabSuccessResponse, + type AiCollabErrorResponse, + type TokenUsage, + type TokenLimits, + type OpenAiClientOptions, +} from "./aiCollabApi.js"; + +export { aiCollab } from "./aiCollab.js"; diff --git a/packages/framework/ai-collab/src/test/ai-collab/jobBoardBenchmark.spec.ts b/packages/framework/ai-collab/src/test/ai-collab/jobBoardBenchmark.spec.ts new file mode 100644 index 000000000000..29f5c8591627 --- /dev/null +++ b/packages/framework/ai-collab/src/test/ai-collab/jobBoardBenchmark.spec.ts @@ -0,0 +1,1031 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + SchemaFactory, + SharedTree, + Tree, + TreeViewConfiguration, + type TreeNode, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; +import { after } from "mocha"; +import { OpenAI } from "openai"; +import * as zod from "zod"; + +import { aiCollab } from "../../index.js"; + +// Define a schema factory that is used to generate classes for the schema +const sf = new SchemaFactory("ef0b8eff-2876-4801-9b6a-973f09aab904"); + +class OnSiteSchedule extends sf.object("OnSiteSchedule", { + day: sf.required(sf.string, { + metadata: { + description: + "The day of the week that the candidate is scheduled for an onsite interview. This field is required. Candidate and interviewers should be available on the day of the onsite interview.", + }, + }), + interviewerIds: sf.required(sf.array(sf.string), { + metadata: { + description: + "The list of interviewerId of interviewers whom are a part of this onsite. This field is required. The default is an empty array. The ids in this array should map to interviewerId field in Interviewer object", + }, + }), + candidateId: sf.required(sf.string, { + metadata: { + description: + "The candidateId of the candidate that is scheduled for an onsite interview. This field is required. The candidateId should map to the id field in the Candidate object", + }, + }), +}) {} + +class Interviewer extends sf.object("Interviewer", { + role: sf.string, + interviewerId: sf.required(sf.string, { + metadata: { + description: + "The unique identifier of the interviewer. This field is required. This field is used to cross identify and reference the interviewer in the OnSiteSchedule", + }, + }), + name: sf.required(sf.string, { + metadata: { + description: "The name of the interviewer. This field is required.", + }, + }), + availability: sf.required(sf.array(sf.string), { + metadata: { + description: + "The availability of the interviewer. This field is required. For this field, the only allowed values are the strings Monday, Tuesday, Wednesday, Thursday, Friday", + }, + }), +}) {} + +class Candidate extends sf.object("Candidate", { + name: sf.string, + candidateId: sf.required(sf.string, { + metadata: { + description: + "The unique identifier of the candidate. This field is required. This field is used to cross identify and reference the candidate in the OnSiteSchedule.", + }, + }), + yearsOfExperience: sf.number, + availability: sf.required(sf.array(sf.string), { + metadata: { + description: + "The availability of the candidate. This field is required. This field is required. For this field, the only allowed values are the strings Monday, Tuesday, Wednesday, Thursday, Friday", + }, + }), +}) {} + +class Job extends sf.object("Job", { + jobId: sf.string, + jobState: sf.required(sf.string, { + metadata: { + description: `The job state of the job. This field is required. For this field, the only allowed values are the strings "open", "closed", "draft". The default is "draft"`, + }, + }), + jobTitle: sf.required(sf.string, { + metadata: { + description: `The title of the job. This field is required. Titles are short and clear`, + }, + }), + jobDescription: sf.required(sf.string, { + metadata: { + description: `The description of the job. This field is required. For this field include a brief description of the job.`, + }, + }), + candidates: sf.required(sf.array(Candidate), { + metadata: { + description: `The candidates who have applied for this job. This field is required. The default is an empty array. The objects of type Candidate are put in arrays here.`, + }, + }), + onSiteSchedule: sf.required(sf.array(OnSiteSchedule), { + metadata: { + description: `The schedule of the onsite interviews. This field is required. The default is an empty array. The objects of type OnSiteSchedule are put in arrays here.`, + }, + }), +}) {} + +class HRData extends sf.object("HRData", { + jobsList: sf.required(sf.array(Job), { + metadata: { + description: `The list of jobs that are available in the HR app. This field is required. The default is an empty array. The objects of type Job are put in arrays here.`, + }, + }), + interviewerPool: sf.required(sf.array(Interviewer), { + metadata: { + description: `The interviewers who have been allowed to interview candidates that have applied to this role. + This field is required. The default is an empty array. The objects of type Interviewer are put in arrays here. + Interviewers should not be removed from this array.`, + }, + }), +}) {} + +const zodAvailabilityEnumSchema = zod.enum([ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", +]); + +const zodCandidateSchema = zod.object({ + name: zod.string(), + candidateId: zod.string(), + yearsOfExperience: zod.number(), + availability: zod.array(zod.enum(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])), +}); + +const zodOnSiteScheduleSchema = zod.object({ + day: zodAvailabilityEnumSchema, + interviewerIds: zod.array(zod.string()), + candidateId: zod.string(), +}); + +const zodJobSchema = zod.object({ + jobId: zod.string(), + jobState: zod.enum(["Open", "Closed", "Draft", "open", "closed", "draft"]), + jobTitle: zod.string(), + jobDescription: zod.string(), + candidates: zod.array(zodCandidateSchema), + onSiteSchedule: zod.array(zodOnSiteScheduleSchema), +}); + +const zodInteviewerSchema = zod.object({ + role: zod.string(), + interviewerId: zod.string(), + name: zod.string(), + availability: zod.array(zodAvailabilityEnumSchema), +}); + +const zodHrAppSchema = zod.object({ + jobsList: zod.array(zodJobSchema), + interviewerPool: zod.array(zodInteviewerSchema), +}); + +const treeNodeValidatorFn = (treeNode: TreeNode): void => { + const schema = Tree.schema(treeNode); + try { + switch (schema.identifier) { + case HRData.identifier: { + zodHrAppSchema.parse(treeNode); + break; + } + case Job.identifier: { + zodJobSchema.parse(treeNode); + break; + } + case Candidate.identifier: { + zodCandidateSchema.parse(treeNode); + break; + } + case Interviewer.identifier: { + zodInteviewerSchema.parse(treeNode); + break; + } + case OnSiteSchedule.identifier: { + zodOnSiteScheduleSchema.parse(treeNode); + break; + } + default: { + throw new Error(`Unknown schema identifier during validation: ${schema.identifier}`); + } + } + } catch (error) { + console.log(error); + throw error; + } +}; + +const factory = SharedTree.getFactory(); +const OPENAI_API_KEY = ""; + +type BenchmarkTask = Record< + string, + { + status: "success" | "partial-failure" | "failure"; + successfulSubTasks: string[]; + failedSubTasks: string[]; + totalSubTasks: number; + executionTimeMs: number; + errorMessage?: string; + } +>; + +describe.skip("AI Job Listings App Benchmark", () => { + const completedTasksBenchmark: BenchmarkTask = {}; + + const SYSTEM_ROLE_CONTEXT = + "You are an assistant that is helping out with a recruitment tool. You help draft job roles and responsibilities. You also help with on site interview plans and schedule." + + "Some important information about the schema that you should be aware -- Each Candidate is uniquely identified by `candidateId` field. Each Interviewer is uniquely identified by `interviewerId` field." + + "Each Job is uniquely identified by `jobId` field. Each job has an OnSiteSchedule array which is list of scheduled onsite interviews. An OnSiteSchedule object has candidateId which indicates the candidate for onsite and interviewerIds array" + + " indicates which interviewers are doing the interviews. These ids help identify the candidate and interviewers uniquely and help map their objects in the app."; + + after(() => { + console.log("AI Job Listings App Benchmark:", completedTasksBenchmark); + const successRate = + // eslint-disable-next-line unicorn/no-array-reduce + Object.values(completedTasksBenchmark).reduce((acc, benchmark) => { + const rate = benchmark.successfulSubTasks.length / benchmark.totalSubTasks; + return acc + rate; + }, 0) / Object.keys(completedTasksBenchmark).length; + + console.log(`Average success rate: ${successRate * 100}%`); + }); + + it("Create a new Job with the title 'QA tester' and add a candidate named 'John Doe', who is only available on mondays and tuesdays, to the job.", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + const taskBencharmarkTitle = + "Create a new Job with the title 'QA tester' and add a candidate named 'John Doe', who is only available on mondays and tuesdays, to the job."; + const createQaTesterJobSubTaskTitle = "Create a new Job with the title 'QA tester'"; + const addJohnDoeCandidateSubTaskTitle = + "Add a candidate named 'John Doe', who is only available on mondays and tuesdays, to the job."; + const johnDoeAvailabilitySubTaskTitle = + "'John Doe' candidates availability is only, 'Monday' and 'Tuesday'."; + + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 3, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 10, + }, + validator: treeNodeValidatorFn, + dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + const createQaTesterJobTaskResult = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + createQaTesterJobSubTaskTitle, + () => { + const foundQaJobs = view.root.jobsList.filter( + (job: Job) => job.jobTitle.toLowerCase() === "qa tester", + ); + const status = foundQaJobs.length === 1; + return { status, data: status ? foundQaJobs[0] : undefined }; + }, + ); + + if (createQaTesterJobTaskResult.status === false) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + addJohnDoeCandidateSubTaskTitle, + johnDoeAvailabilitySubTaskTitle, + ]; + return; + } + + const foundQaJob = createQaTesterJobTaskResult.data as Job; + assert(foundQaJob !== undefined); + + const createJohnDoeCandidateTask = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + addJohnDoeCandidateSubTaskTitle, + () => { + const foundJohnDoeZ = foundQaJob.candidates.filter( + (candidate) => candidate.name.toLowerCase() === "john doe", + ); + const status = foundJohnDoeZ.length === 1; + return { status, data: foundJohnDoeZ[0] }; + }, + ); + + if (createJohnDoeCandidateTask.status === false) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + johnDoeAvailabilitySubTaskTitle, + ]; + return; + } + const foundJohnDoe = createJohnDoeCandidateTask.data; + assert(foundJohnDoe !== undefined); + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + johnDoeAvailabilitySubTaskTitle, + () => { + return { + status: + foundJohnDoe?.availability.includes("Monday") === true && + foundJohnDoe.availability.includes("Tuesday") === true, + }; + }, + ); + }); + + it("Create a job for Project Manager role with a Job Title and Job description. Add an interviewer whose name is James Bond and is the Hiring Manager for the role. James Bond's availability to interview only on Monday and Tuesday.", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + + const taskBencharmarkTitle = + "Create a job for Project Manager role with a Job Title and Job description. Add an interviewer whose name is James Bond and is the Hiring Manager for the role. James Bond's availability to interview only on Monday and Tuesday"; + const createProjectMngrJobSubTaskTitle = + "Create a job for Project Manager role with a Job Title and Job description"; + const addJamesBondInterviewerSubTaskTitle = + "Add an interviewer whose name is James Bond and is the Hiring Manager for the role"; + const jamesBondAvailabilitySubTaskTitle = + "James Bond's availability to interview only on Monday and Tuesday"; + + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 3, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 10, + }, + validator: treeNodeValidatorFn, + // dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + const createQaTesterJobTask = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + createProjectMngrJobSubTaskTitle, + () => { + const foundJob_ = view.root.jobsList.find((job: Job) => + /project\s*manager/i.test(job.jobTitle), + ); + + if (foundJob_ !== undefined) { + return { + status: + foundJob_.jobTitle.length > 0 && + foundJob_.jobDescription.length > 0 && + foundJob_.candidates.length === 0, + data: foundJob_, + }; + } + + return { status: false }; + }, + ); + + if (createQaTesterJobTask.status === false) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + addJamesBondInterviewerSubTaskTitle, + jamesBondAvailabilitySubTaskTitle, + ]; + return; + } + + const foundJob = createQaTesterJobTask.data; + assert(foundJob !== undefined); + + const addJamesBondInterviewerTask = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + addJamesBondInterviewerSubTaskTitle, + () => { + const foundJamesBond_ = view.root.interviewerPool.find( + (interviewer) => interviewer.name.toLowerCase() === "james bond", + ); + return { status: foundJamesBond_ !== undefined, data: foundJamesBond_ }; + }, + ); + + if (addJamesBondInterviewerTask.status === false) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + jamesBondAvailabilitySubTaskTitle, + ]; + return; + } + + const foundJamesBond = addJamesBondInterviewerTask.data; + assert(foundJamesBond !== undefined); + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + jamesBondAvailabilitySubTaskTitle, + () => { + return { + status: + foundJamesBond?.availability.includes("Monday") === true && + foundJamesBond.availability.includes("Tuesday") === true, + }; + }, + ); + }); + + it("Add a new candidate with name 'Will Smith' who is not available on mondays and tuesdays for interviews and is applying for the Project Manager role.", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + view.root.jobsList.insertAtEnd( + new Job({ + jobId: "2", + jobState: "Open", + jobTitle: "Project Manager", + jobDescription: "We are looking for a project manager to join our team.", + candidates: [], + onSiteSchedule: [], + }), + ); + const projectManagerNode = view.root.jobsList.find( + (job: Job) => job.jobTitle === "Project Manager", + ); + + const taskBencharmarkTitle = + "Add a new candidate with name 'Will Smith' who is not available on mondays and tuesdays for interviews and is applying for the Project Manager role."; + const addWillSmithCandidateSubTaskTitle = + "Add a new candidate with name 'Will Smith to the project manager job'"; + const willSmithAvailabilitySubTaskTitle = + "'Will Smith' candidates availability is only, 'Monday' and 'Tuesday'."; + + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 2, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 10, + }, + validator: treeNodeValidatorFn, + // dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + const addWillSmithCandidateTask = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + addWillSmithCandidateSubTaskTitle, + () => { + const foundCandidate = projectManagerNode?.candidates.find( + (candidate) => candidate.name.toLowerCase() === "will smith", + ); + return { status: foundCandidate !== undefined, data: foundCandidate }; + }, + ); + + if (addWillSmithCandidateTask.status === false) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + willSmithAvailabilitySubTaskTitle, + ]; + return; + } + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + willSmithAvailabilitySubTaskTitle, + () => { + const foundWillSmith = addWillSmithCandidateTask.data; + return { + status: + foundWillSmith?.availability.includes("Monday") === false && + foundWillSmith?.availability.includes("Tuesday") === false && + foundWillSmith?.availability.length === 3, + }; + }, + ); + }); + + it("Setup an interview for Will Smith that will take place on Thursday. Select any 2 interviewers and add them to interviewers list", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + view.root.jobsList.insertAtEnd( + new Job({ + jobId: "2", + jobState: "Open", + jobTitle: "Project Manager", + jobDescription: "We are looking for a project manager to join our team.", + candidates: [ + new Candidate({ + candidateId: "2", + name: "Will Smith", + yearsOfExperience: 10, + availability: ["Monday", "Tuesday", "Wednesday", "Thursday"], + }), + ], + onSiteSchedule: [], + }), + ); + const jobNode = view.root.jobsList.find((job: Job) => job.jobTitle === "Project Manager"); + + const taskBencharmarkTitle = + "Setup an interview for Will Smith that will take place on Thursday. Select any 2 interviewers and add them to interviewers list"; + const onsiteInterviewCreatedSubTaskTitle = + "Setup an interview for Will Smith that will take place on Thursday."; + const addInterviewersSubTaskTitle = + "Select any 2 interviewers and add them to interviewers list"; + const interviewersAvailablilitySubTaskTitle = "Interviewers are available on Thursday"; + + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 3, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 10, + }, + validator: treeNodeValidatorFn, + // dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + const subtask1Result = measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + onsiteInterviewCreatedSubTaskTitle, + () => { + const foundWillSmith = jobNode?.candidates.find( + (candidate) => candidate.name.toLowerCase() === "will smith", + ); + const foundOnsiteInterview = jobNode?.onSiteSchedule.find( + (onsite) => onsite.candidateId === foundWillSmith?.candidateId, + ); + return { + status: + foundOnsiteInterview !== undefined && foundOnsiteInterview.day === "Thursday", + data: foundOnsiteInterview, + }; + }, + ); + + if (subtask1Result.data === undefined) { + completedTasksBenchmark[taskBencharmarkTitle].failedSubTasks = [ + addInterviewersSubTaskTitle, + ]; + return; + } + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + addInterviewersSubTaskTitle, + () => { + const foundOnsiteInterview = subtask1Result.data; + return { status: foundOnsiteInterview?.interviewerIds.length === 2 }; + }, + ); + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + interviewersAvailablilitySubTaskTitle, + () => { + const foundOnsiteInterview = subtask1Result.data; + if (foundOnsiteInterview) { + for (const interviewerId of foundOnsiteInterview.interviewerIds) { + const matchedInterviewer = view.root.interviewerPool.find( + (interviewer) => interviewer.interviewerId === interviewerId, + ); + if (matchedInterviewer?.availability.includes("Thursday") === false) { + return { status: false }; + } + } + return { status: true }; + } + return { status: false }; + }, + ); + }); + + // TODO: AI will try to use 'modify' to add or remove the inteviewer id's from the onsite 'inteviewIds' array. It also tries to use a field called 'interviewerId' instead of 'interviewerIds' -- why? One thought it because the edits need a better explanation. insert is for arrays, perhaps it should be insertArray or 'array.insert'? + // - When it fails the modify and re attempts, it just trys to adjust the value being inserted. The failure feedback could be improved significantly, e.g. "this failed because the field doesnt exist" + it.skip("Add Alice Johnson and Charlie Brown to list of interviewers for Will Smith's onsite.", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + const targetJobId = "2"; + view.root.jobsList.insertAtEnd( + new Job({ + jobId: targetJobId, + jobState: "Open", + jobTitle: "Project Manager", + jobDescription: "We are looking for a project manager to join our team.", + candidates: [ + new Candidate({ + candidateId: "2", + name: "Will Smith", + yearsOfExperience: 10, + availability: ["Monday", "Tuesday", "Wednesday", "Thursday"], + }), + ], + onSiteSchedule: [ + new OnSiteSchedule({ day: "Thursday", interviewerIds: [], candidateId: "2" }), + ], + }), + ); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const jobNode = view.root.jobsList.find((job: Job) => job.jobId === targetJobId)!; + + const taskBencharmarkTitle = + "Add Alice Johnson and Charlie Brown to list of interviewers for Will Smith's onsite."; + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 1, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 5, + }, + dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + taskBencharmarkTitle, + () => { + const interviewIds = jobNode.onSiteSchedule[0]?.interviewerIds; + const foundInterviewers = view.root.interviewerPool.filter((interviewer) => + interviewIds?.includes(interviewer.interviewerId), + ); + return { + status: + foundInterviewers.length === 2 && + foundInterviewers.every( + (interviewer) => + interviewer.name === "Alice Johnson" || interviewer.name === "Charlie Brown", + ), + }; + }, + ); + + const jsonTree: unknown = JSON.parse(JSON.stringify(view.root)); + console.log(jsonTree); + }); + + // This fails similarly to the above test where it trys to modify an arary from ["10"] to [] instead of removing a node. + it.skip("Remove Alice Johnson from Will Smith onsite interview schedule", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: HRData })); + view.initialize(createTestData()); + const targetJobId = "2"; + view.root.jobsList.insertAtEnd( + new Job({ + jobId: targetJobId, + jobState: "Open", + jobTitle: "Project Manager", + jobDescription: "We are looking for a project manager to join our team.", + candidates: [ + new Candidate({ + candidateId: "2", + name: "Will Smith", + yearsOfExperience: 10, + availability: ["Monday", "Tuesday", "Wednesday", "Thursday"], + }), + ], + onSiteSchedule: [ + // Note alice johnson's id is 10 and included in the initial data from createTestData() + new OnSiteSchedule({ day: "Thursday", interviewerIds: ["10"], candidateId: "2" }), + ], + }), + ); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const jobNode = view.root.jobsList.find((job: Job) => job.jobId === targetJobId)!; + + const taskBencharmarkTitle = + "Remove Alice Johnson from Will Smith onsite interview schedule."; + completedTasksBenchmark[taskBencharmarkTitle] = { + status: "success", + successfulSubTasks: [], + failedSubTasks: [], + executionTimeMs: 0, + totalSubTasks: 1, + }; + + const startTime = Date.now(); + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: SYSTEM_ROLE_CONTEXT, + userAsk: taskBencharmarkTitle, + }, + limiters: { + maxModelCalls: 5, + }, + dumpDebugLog: true, + }); + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + } catch (error) { + let errorMessage: string | undefined; + if (error instanceof Error) { + errorMessage = error.message; + } + + completedTasksBenchmark[taskBencharmarkTitle].status = "failure"; + completedTasksBenchmark[taskBencharmarkTitle].errorMessage = errorMessage; + completedTasksBenchmark[taskBencharmarkTitle].executionTimeMs = Date.now() - startTime; + return; + } + + measureSubTaskBenchmark( + completedTasksBenchmark, + taskBencharmarkTitle, + taskBencharmarkTitle, + () => { + return { + status: jobNode.onSiteSchedule[0]?.interviewerIds.length === 0, + }; + }, + ); + }); +}); + +interface SubTaskMeasurementResult { + status: boolean; + data?: TestData; +} + +function measureSubTaskBenchmark( + benchmark: BenchmarkTask, + taskTitle: string, + subTaskTitle: string, + measurement: () => SubTaskMeasurementResult, +): { status: boolean; data?: TestData } { + const measurementResult = measurement(); + const benchmarkTask = benchmark[taskTitle]; + if (benchmarkTask) { + if (measurement().status) { + benchmarkTask.successfulSubTasks.push(subTaskTitle); + } else { + benchmarkTask.failedSubTasks.push(subTaskTitle); + } + + // Recalculate the status of the benchmark task based on sucessful and failed sub tasks. + benchmarkTask.status = + benchmarkTask.successfulSubTasks.length > 0 && benchmarkTask.failedSubTasks.length === 0 + ? "success" + : benchmarkTask.successfulSubTasks.length === 0 + ? "failure" + : "partial-failure"; + } else { + throw new Error(`Benchmark taskTitle not found ${taskTitle}`); + } + + return measurementResult; +} + +function createTestData(): HRData { + const interviewers = [ + new Interviewer({ + interviewerId: "10", + name: "Alice Johnson", + role: "Technical Lead", + availability: ["Monday", "Tuesday", "Wednesday"], + }), + new Interviewer({ + interviewerId: "20", + name: "Bob Smith", + role: "HR Manager", + availability: ["Monday", "Tuesday", "Wednesday"], + }), + new Interviewer({ + interviewerId: "30", + name: "Charlie Brown", + role: "Senior Developer", + availability: ["Thursday", "Friday"], + }), + new Interviewer({ + interviewerId: "40", + name: "Diana Prince", + role: "Project Manager", + availability: ["Thursday", "Friday"], + }), + new Interviewer({ + interviewerId: "50", + name: "Ethan Hunt", + role: "QA Engineer", + availability: ["Thursday", "Friday"], + }), + new Interviewer({ + interviewerId: "60", + name: "Fiona Gallagher", + role: "DevOps Engineer", + availability: ["Friday"], + }), + new Interviewer({ + interviewerId: "70", + name: "George Martin", + role: "Product Owner", + availability: ["Monday", "Tuesday", "Thursday", "Friday"], + }), + ]; + + const job = createTestJob(); + + const hrData = new HRData({ + jobsList: [job], + interviewerPool: interviewers, + }); + return hrData; +} + +function createTestJob(): Job { + const candidates = [ + new Candidate({ + candidateId: "1", + name: "John Doe", + yearsOfExperience: 5, + availability: createFullyAvailable(), + }), + ]; + + const onSiteSchedule = new OnSiteSchedule({ + day: "Monday", + interviewerIds: ["10", "20", "40"], + candidateId: "1", + }); + + const job = new Job({ + jobId: "1", + jobState: "Open", + jobTitle: "Software Engineer", + jobDescription: "We are looking for a software engineer to join our team.", + candidates, + onSiteSchedule: [onSiteSchedule], + }); + + return job; +} + +function createFullyAvailable(): string[] { + return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; +} diff --git a/packages/framework/ai-collab/src/test/ai-collab/taskPlanner.spec.ts b/packages/framework/ai-collab/src/test/ai-collab/taskPlanner.spec.ts new file mode 100644 index 000000000000..15858a3b47ca --- /dev/null +++ b/packages/framework/ai-collab/src/test/ai-collab/taskPlanner.spec.ts @@ -0,0 +1,371 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + SchemaFactory, + SharedTree, + TreeViewConfiguration, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; +import { APIError, OpenAI } from "openai"; + +import { aiCollab } from "../../index.js"; + +const sf = new SchemaFactory("ai-collab-sample-application"); + +class SharedTreeTask extends sf.object("Task", { + title: sf.string, + id: sf.identifier, + description: sf.string, + priority: sf.string, + complexity: sf.number, + status: sf.string, + assignee: sf.string, +}) {} + +class SharedTreeTaskList extends sf.array("TaskList", SharedTreeTask) {} + +class SharedTreeEngineer extends sf.object("Engineer", { + name: sf.string, + id: sf.identifier, + skills: sf.string, + maxCapacity: sf.number, +}) {} + +class SharedTreeEngineerList extends sf.array("EngineerList", SharedTreeEngineer) {} + +class SharedTreeTaskGroup extends sf.object("TaskGroup", { + description: sf.string, + id: sf.identifier, + title: sf.string, + tasks: SharedTreeTaskList, + engineers: SharedTreeEngineerList, +}) {} + +class SharedTreeTaskGroupList extends sf.array("TaskGroupList", SharedTreeTaskGroup) {} + +class SharedTreeAppState extends sf.object("AppState", { + taskGroups: SharedTreeTaskGroupList, +}) {} + +const INITIAL_APP_STATE = { + taskGroups: [ + { + title: "My First Task Group", + description: "Placeholder for first task group", + tasks: [ + { + assignee: "Alice", + title: "Task #1", + description: + "This is the first task. Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "low", + complexity: 1, + status: "todo", + }, + { + assignee: "Bob", + title: "Task #2", + description: + "This is the second task. Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "medium", + complexity: 2, + status: "in-progress", + }, + { + assignee: "Charlie", + title: "Task #3", + description: + "This is the third task! Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "high", + complexity: 3, + status: "done", + }, + ], + engineers: [ + { + name: "Alice", + maxCapacity: 15, + skills: + "Senior engineer capable of handling complex tasks. Versed in most languages", + }, + { + name: "Bob", + maxCapacity: 12, + skills: + "Mid-level engineer capable of handling medium complexity tasks. Versed in React, Node.JS", + }, + { + name: "Charlie", + maxCapacity: 7, + skills: "Junior engineer capable of handling simple tasks. Versed in Node.JS", + }, + ], + }, + { + title: "My Second Task Group", + description: "Placeholder for second task group", + tasks: [ + { + assignee: "Alice", + title: "Task #1", + description: + "This is the first task. Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "low", + complexity: 1, + status: "todo", + }, + { + assignee: "Bob", + title: "Task #2", + description: + "This is the second task. Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "medium", + complexity: 2, + status: "in-progress", + }, + { + assignee: "Charlie", + title: "Task #3", + description: + "This is the third task! Blah Blah blah Blah Blah blahBlah Blah blahBlah Blah blahBlah Blah blah", + priority: "high", + complexity: 3, + status: "done", + }, + ], + engineers: [ + { + name: "Alice", + maxCapacity: 15, + skills: + "Senior engineer capable of handling complex tasks. Versed in most languages", + }, + { + name: "Bob", + maxCapacity: 12, + skills: + "Mid-level engineer capable of handling medium complexity tasks. Versed in React, Node.JS", + }, + { + name: "Charlie", + maxCapacity: 7, + skills: "Junior engineer capable of handling simple tasks. Versed in Node.JS", + }, + ], + }, + ], +} as const; + +const factory = SharedTree.getFactory(); + +const OPENAI_API_KEY = ""; + +describe.skip("Ai Planner App", () => { + it("should be able to change the priority of a task", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: SharedTreeAppState })); + view.initialize(INITIAL_APP_STATE); + + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + treeNode: view.root.taskGroups[0]!, + prompt: { + systemRoleContext: + "You are a manager that is helping out with a project management tool. You have been asked to edit a group of tasks.", + userAsk: "Change the priority of the first task from low to high", + }, + planningStep: true, + finalReviewStep: true, + dumpDebugLog: true, + }); + + assert(view.root.taskGroups[0]?.tasks[0]?.priority === "high"); + }); + + it.skip("BUG: Invalid json schema produced when schema has no arrays at all", async () => { + class TestAppSchema extends sf.object("PrioritySpecification", { + priority: sf.string, + }) {} + + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestAppSchema })); + view.initialize({ priority: "low" }); + + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You are a managing objects with a priority field.", + userAsk: "Change the priority from low to high", + }, + planningStep: true, + finalReviewStep: true, + }); + } catch (error) { + assert(error instanceof APIError); + assert(error.status === 400); + assert(error.type === "invalid_request_error"); + assert( + error.message === + "400 Invalid schema for response_format 'SharedTreeAI': In context=('properties', 'edit', 'anyOf', '1', 'properties', 'content', 'not'), schema must have a 'type' key.", + ); + } + }); + + it.skip("BUG: Invalid json schema produced when schema has multiple keys with the same name and order", async () => { + class TaskList extends sf.array("taskList", sf.string) {} + + class TestInnerAppSchema extends sf.object("TestInnerAppSchema", { + title: sf.string, + }) {} + + class TestAppSchema extends sf.object("TestAppSchema", { + title: sf.string, + taskList: TaskList, + appData: TestInnerAppSchema, + }) {} + + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestAppSchema })); + view.initialize({ + title: "Sample Title", + taskList: [], + appData: { title: "Inner App Data" }, + }); + + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You are a managing json objects", + userAsk: "Change the `title` field of the outer object to 'Hello World'", + }, + planningStep: true, + finalReviewStep: true, + }); + } catch (error) { + assert(error instanceof APIError); + assert(error.status === 400); + assert(error.type === "invalid_request_error"); + assert( + error.message === + "400 Invalid schema for response_format 'SharedTreeAI'. Please ensure it is a valid JSON Schema.", + ); + } + }); + + it.skip("BUG: OpenAI structured output fails when json schema with psuedo optional field is used in response format", async () => { + class TaskList extends sf.array("taskList", sf.string) {} + + class TestAppSchemaWithOptionalProp extends sf.object("TestAppSchemaWithOptionalProp", { + nonOptionalProp: sf.string, + taskList: TaskList, + optionalProp: sf.optional(sf.string), + }) {} + + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith( + new TreeViewConfiguration({ schema: TestAppSchemaWithOptionalProp }), + ); + view.initialize({ nonOptionalProp: "Hello", taskList: [] }); + + try { + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You are a managing json objects", + userAsk: "Change the `optionalProp` field to 'Hello World'", + }, + planningStep: true, + finalReviewStep: true, + }); + } catch (error) { + assert(error instanceof APIError); + assert(error.status === 400); + assert(error.type === "invalid_request_error"); + assert( + error.message === + "400 Invalid schema for response_format 'SharedTreeAI'. Please ensure it is a valid JSON Schema.", + ); + } + + class TestAppSchemaWithoutOptionalProp extends sf.object( + "TestAppSchemaWithoutOptionalProp", + { + nonOptionalProp: sf.string, + }, + ) {} + + const tree2 = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree2", + ); + const view2 = tree2.viewWith( + new TreeViewConfiguration({ schema: TestAppSchemaWithoutOptionalProp }), + ); + view2.initialize({ nonOptionalProp: "Hello" }); + + await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You are a managing json objects", + userAsk: "Change the `optionalProp` field to 'Hello World'", + }, + planningStep: true, + finalReviewStep: true, + }); + + assert.equal(view.root.nonOptionalProp, "Hello World"); + }); +}); diff --git a/packages/framework/ai-collab/src/test/ai-collab/tokenLimits.spec.ts b/packages/framework/ai-collab/src/test/ai-collab/tokenLimits.spec.ts new file mode 100644 index 000000000000..7d57e67f232e --- /dev/null +++ b/packages/framework/ai-collab/src/test/ai-collab/tokenLimits.spec.ts @@ -0,0 +1,130 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + SharedTree, + SchemaFactory, + TreeViewConfiguration, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; +import { OpenAI } from "openai"; + +import { aiCollab } from "../../aiCollab.js"; + +const sf = new SchemaFactory("TestApp"); +class TestAppSchema extends sf.object("TestAppSchema", { + title: sf.string, + tasks: sf.array( + sf.object("Task", { + title: sf.string, + description: sf.string, + }), + ), +}) {} + +const factory = SharedTree.getFactory(); + +const OPENAI_API_KEY = ""; // DON'T COMMIT THIS + +describe.skip("Token limits work as expected", () => { + it("Should not allow more than allowed input token limit", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestAppSchema })); + view.initialize({ + title: "This is a group of tasks", + tasks: [ + { + title: "Task 1", + description: "This is the first task", + }, + { + title: "Task 2", + description: "This is the second task", + }, + ], + }); + + const inputTokenLimit = 500; + const response = await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You're a helpful AI assistant", + userAsk: + "Change the title to 'Hello World', remove the existing tasks and then create two new sample tasks", + }, + limiters: { + maxModelCalls: 10, + tokenLimits: { + inputTokens: inputTokenLimit, + }, + }, + }); + assert.strictEqual(response.status, "partial-failure"); + assert.strictEqual(response.errorMessage, "tokenLimitExceeded"); + assert.strictEqual(response.tokensUsed.inputTokens >= inputTokenLimit, true); + }).timeout(20000); + + it("Should not allow more than allowed output token limit", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestAppSchema })); + view.initialize({ + title: "This is a group of tasks", + tasks: [ + { + title: "Task 1", + description: "This is the first task", + }, + { + title: "Task 2", + description: "This is the second task", + }, + ], + }); + + const outputTokenLimit = 100; + + const response = await aiCollab({ + openAI: { + client: new OpenAI({ + apiKey: OPENAI_API_KEY, + }), + modelName: "gpt-4o", + }, + treeNode: view.root, + prompt: { + systemRoleContext: "You're a helpful AI assistant", + userAsk: + "Change the title to 'Hello World', remove the existing tasks and then create two new sample tasks", + }, + limiters: { + maxModelCalls: 10, + tokenLimits: { + outputTokens: outputTokenLimit, + }, + }, + }); + assert.strictEqual(response.status, "partial-failure"); + assert.strictEqual(response.errorMessage, "tokenLimitExceeded"); + assert.strictEqual(response.tokensUsed.outputTokens >= outputTokenLimit, true); + }).timeout(20000); +}); diff --git a/packages/framework/ai-collab/src/test/explicit-strategy/agentEditing.spec.ts b/packages/framework/ai-collab/src/test/explicit-strategy/agentEditing.spec.ts new file mode 100644 index 000000000000..39198d761592 --- /dev/null +++ b/packages/framework/ai-collab/src/test/explicit-strategy/agentEditing.spec.ts @@ -0,0 +1,191 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + SchemaFactory, + getJsonSchema, + SharedTree, + TreeViewConfiguration, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; + +// eslint-disable-next-line import/no-internal-modules +import { objectIdKey } from "../../explicit-strategy/agentEditTypes.js"; +// eslint-disable-next-line import/no-internal-modules +import { IdGenerator } from "../../explicit-strategy/idGenerator.js"; +import { + getPromptFriendlyTreeSchema, + toDecoratedJson, + // eslint-disable-next-line import/no-internal-modules +} from "../../explicit-strategy/promptGeneration.js"; + +const demoSf = new SchemaFactory("agentSchema"); + +class Vector extends demoSf.object("Vector", { + x: demoSf.number, + y: demoSf.number, + z: demoSf.optional(demoSf.number), +}) {} + +class RootObject extends demoSf.object("RootObject", { + str: demoSf.string, + vectors: demoSf.array(Vector), + bools: demoSf.array(demoSf.boolean), +}) {} + +const factory = SharedTree.getFactory(); + +describe("toDecoratedJson", () => { + let idGenerator: IdGenerator; + beforeEach(() => { + idGenerator = new IdGenerator(); + }); + + it("adds ID fields", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: Vector })); + view.initialize({ x: 1, y: 2 }); + + assert.equal( + toDecoratedJson(idGenerator, view.root), + JSON.stringify({ + [objectIdKey]: "Vector1", + x: 1, + y: 2, + }), + ); + }); + + it("handles nested objects", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: RootObject })); + view.initialize({ str: "hello", vectors: [{ x: 1, y: 2, z: 3 }], bools: [true] }); + + assert.equal( + toDecoratedJson(idGenerator, view.root), + JSON.stringify({ + [objectIdKey]: "RootObject1", + str: "hello", + vectors: [ + { + [objectIdKey]: "Vector1", + x: 1, + y: 2, + z: 3, + }, + ], + bools: [true], + }), + ); + + assert.equal(idGenerator.getNode("RootObject1"), view.root); + assert.equal(idGenerator.getNode("Vector1"), view.root.vectors.at(0)); + }); + + it("handles non-POJO mode arrays", () => { + const sf = new SchemaFactory("testSchema"); + class NamedArray extends sf.array("Vector", sf.number) {} + class Root extends sf.object("Root", { + arr: NamedArray, + }) {} + + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: Root })); + view.initialize({ arr: [1, 2, 3] }); + + assert.equal( + toDecoratedJson(idGenerator, view.root), + JSON.stringify({ [objectIdKey]: "Root1", arr: [1, 2, 3] }), + ); + }); +}); + +describe("Makes TS type strings from schema", () => { + it("for objects with primitive fields", () => { + const testSf = new SchemaFactory("test"); + class Foo extends testSf.object("Foo", { + x: testSf.number, + y: testSf.string, + z: testSf.optional(testSf.null), + }) {} + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(Foo)), + "interface Foo { x: number; y: string; z: null | undefined; }", + ); + }); + + // This test fails due to the fact that identifier fields are incorrectly set as optional in the JSON Schema + it.skip("for objects with identifier fields", () => { + const testSf = new SchemaFactory("test"); + class Foo extends testSf.object("Foo", { + y: testSf.identifier, + }) {} + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(Foo)), + "interface Foo { y: string; }", + ); + }); + + it("for objects with polymorphic fields", () => { + const testSf = new SchemaFactory("test"); + class Bar extends testSf.object("Bar", { + z: testSf.number, + }) {} + class Foo extends testSf.object("Foo", { + y: demoSf.required([demoSf.number, demoSf.string, Bar]), + }) {} + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(Foo)), + "interface Foo { y: number | string | Bar; } interface Bar { z: number; }", + ); + }); + + it("for objects with array fields", () => { + const testSf = new SchemaFactory("test"); + class Foo extends testSf.object("Foo", { + y: demoSf.array(demoSf.number), + }) {} + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(Foo)), + "interface Foo { y: number[]; }", + ); + }); + + it("for objects with nested array fields", () => { + const testSf = new SchemaFactory("test"); + class Foo extends testSf.object("Foo", { + y: demoSf.array([ + demoSf.number, + demoSf.array([demoSf.number, demoSf.array(demoSf.string)]), + ]), + }) {} + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(Foo)), + "interface Foo { y: (number | (number | string[])[])[]; }", + ); + }); + + it("for objects in the demo schema", () => { + assert.equal( + getPromptFriendlyTreeSchema(getJsonSchema(RootObject)), + "interface RootObject { str: string; vectors: Vector[]; bools: boolean[]; } interface Vector { x: number; y: number; z: number | undefined; }", + ); + }); +}); diff --git a/packages/framework/ai-collab/src/test/explicit-strategy/agentEditingReducer.spec.ts b/packages/framework/ai-collab/src/test/explicit-strategy/agentEditingReducer.spec.ts new file mode 100644 index 000000000000..a3840a2619ec --- /dev/null +++ b/packages/framework/ai-collab/src/test/explicit-strategy/agentEditingReducer.spec.ts @@ -0,0 +1,1291 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert, fail } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + getSimpleSchema, + SchemaFactory, + TreeViewConfiguration, + SharedTree, + type TreeNode, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; + +import { + applyAgentEdit, + // eslint-disable-next-line import/no-internal-modules +} from "../../explicit-strategy/agentEditReducer.js"; +import { + typeField, + // eslint-disable-next-line import/no-internal-modules +} from "../../explicit-strategy/agentEditTypes.js"; +import type { + TreeEdit, + // eslint-disable-next-line import/no-internal-modules +} from "../../explicit-strategy/agentEditTypes.js"; +// eslint-disable-next-line import/no-internal-modules +import { IdGenerator } from "../../explicit-strategy/idGenerator.js"; + +import { validateUsageError } from "./utils.js"; + +const sf = new SchemaFactory("agentSchema"); + +class Vector extends sf.object("Vector", { + id: sf.identifier, // will be omitted from the generated JSON schema + x: sf.number, + y: sf.number, + z: sf.optional(sf.number), +}) {} + +class Vector2 extends sf.object("Vector2", { + id: sf.identifier, // will be omitted from the generated JSON schema + x2: sf.number, + y2: sf.number, + z2: sf.optional(sf.number), +}) {} + +class RootObjectPolymorphic extends sf.object("RootObjectPolymorphic", { + str: sf.string, + // Two different vector types to handle the polymorphic case + vectors: sf.array([Vector, Vector2]), + bools: sf.array(sf.boolean), +}) {} + +class RootObject extends sf.object("RootObject", { + str: sf.string, + // Two different vector types to handle the polymorphic case + vectors: sf.array([Vector]), + bools: sf.array(sf.boolean), +}) {} + +class RootObjectWithMultipleVectorArrays extends sf.object( + "RootObjectWithMultipleVectorArrays", + { + str: sf.string, + // Two different vector types to handle the polymorphic case + vectors: sf.array([Vector]), + vectors2: sf.array([Vector]), + bools: sf.array(sf.boolean), + }, +) {} + +class RootObjectWithDifferentVectorArrayTypes extends sf.object( + "RootObjectWithDifferentVectorArrayTypes", + { + str: sf.string, + // Two different vector types to handle the polymorphic case + vectors: sf.array([Vector]), + vectors2: sf.array([Vector2]), + bools: sf.array(sf.boolean), + }, +) {} + +class RootObjectWithNonArrayVectorField extends sf.object( + "RootObjectWithNonArrayVectorField", + { + singleVector: sf.optional(Vector), + // Two different vector types to handle the polymorphic case + vectors: sf.array([Vector]), + bools: sf.array(sf.boolean), + }, +) {} + +class RootObjectWithSubtree extends sf.object("RootObjectWithSubtree", { + innerObject: sf.object("InnerObject", { + str: sf.string, + vectors: sf.array([Vector]), + bools: sf.array(sf.boolean), + singleVector: sf.optional(Vector), + }), +}) {} + +const factory = SharedTree.getFactory(); + +describe("applyAgentEdit", () => { + let idGenerator: IdGenerator; + beforeEach(() => { + idGenerator = new IdGenerator(); + }); + + describe("insert edits", () => { + it("inner polymorphic tree node insert edits", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: RootObjectPolymorphic })); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + idGenerator.assignIds(view.root); + const vectorId = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, y: 3, z: 4 }, + destination: { + type: "objectPlace", + target: vectorId, + place: "after", + }, + }; + applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions); + + const insertEdit2: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector2.identifier, x2: 3, y2: 4, z2: 5 }, + destination: { + type: "objectPlace", + target: vectorId, + place: "after", + }, + }; + applyAgentEdit(insertEdit2, idGenerator, simpleSchema.definitions); + + const identifier1 = (view.root.vectors[0] as Vector).id; + const identifier2 = (view.root.vectors[1] as Vector).id; + const identifier3 = (view.root.vectors[2] as Vector).id; + + const expected = { + "str": "testStr", + "vectors": [ + { + "id": identifier1, + "x": 1, + "y": 2, + "z": 3, + }, + { + "id": identifier2, + "x2": 3, + "y2": 4, + "z2": 5, + }, + { + "id": identifier3, + "x": 2, + "y": 3, + "z": 4, + }, + ], + "bools": [true], + }; + + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("non polymorphic insert edits", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const config2 = new TreeViewConfiguration({ schema: RootObject }); + const view = tree.viewWith(config2); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + const vectorId = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, y: 3, z: 4 }, + destination: { + type: "objectPlace", + target: vectorId, + place: "after", + }, + }; + applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier1 = view.root.vectors[0]!.id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier2 = view.root.vectors[1]!.id; + + const expected = { + "str": "testStr", + "vectors": [ + { + "id": identifier1, + "x": 1, + "y": 2, + "z": 3, + }, + { + "id": identifier2, + "x": 2, + "y": 3, + "z": 4, + }, + ], + "bools": [true], + }; + + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("insert edit into an empty array", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const config2 = new TreeViewConfiguration({ schema: RootObject }); + const view = tree.viewWith(config2); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [], + bools: [true], + }); + + idGenerator.assignIds(view.root); + const vectorId = idGenerator.getId(view.root) ?? fail("ID expected."); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, y: 3, z: 4 }, + destination: { + type: "arrayPlace", + parentId: vectorId, + field: "vectors", + location: "start", + }, + }; + applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier1 = view.root.vectors[0]!.id; + + const expected = { + "str": "testStr", + "vectors": [ + { + "id": identifier1, + "x": 2, + "y": 3, + "z": 4, + }, + ], + "bools": [true], + }; + + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("fails for invalid content for schema type", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const config2 = new TreeViewConfiguration({ schema: RootObject }); + const view = tree.viewWith(config2); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + const vectorId = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, nonVectorField: "invalid", z: 4 }, + destination: { + type: "objectPlace", + target: vectorId, + place: "after", + }, + }; + + assert.throws( + () => applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/provided data is incompatible/), + ); + }); + + it("inserting node into an non array node fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const config2 = new TreeViewConfiguration({ schema: RootObjectWithNonArrayVectorField }); + const view = tree.viewWith(config2); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + singleVector: new Vector({ x: 1, y: 2, z: 3 }), + vectors: [new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + assert(view.root.singleVector !== undefined); + const vectorId = idGenerator.getId(view.root.singleVector) ?? fail("ID expected."); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 3, y: 4, z: 5 }, + destination: { + type: "objectPlace", + target: vectorId, + place: "before", + }, + }; + assert.throws( + () => applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/Expected child to be in an array node/), + ); + }); + }); + + it("modify edits", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const config = new TreeViewConfiguration({ schema: RootObjectPolymorphic }); + const view = tree.viewWith(config); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + const vectorId = idGenerator.getId(view.root as TreeNode) ?? fail("ID expected."); + + const modifyEdit: TreeEdit = { + explanation: "Modify a vector", + type: "modify", + target: { target: vectorId }, + field: "vectors", + modification: [ + { [typeField]: Vector.identifier, x: 2, y: 3, z: 4 }, + { [typeField]: Vector2.identifier, x2: 3, y2: 4, z2: 5 }, + ], + }; + applyAgentEdit(modifyEdit, idGenerator, simpleSchema.definitions); + + const modifyEdit2: TreeEdit = { + explanation: "Modify a vector", + type: "modify", + target: { target: vectorId }, + field: "bools", + modification: [false], + }; + applyAgentEdit(modifyEdit2, idGenerator, simpleSchema.definitions); + + idGenerator.assignIds(view.root); + const vectorId2 = + idGenerator.getId(view.root.vectors[0] as Vector) ?? fail("ID expected."); + + const modifyEdit3: TreeEdit = { + explanation: "Modify a vector", + type: "modify", + target: { target: vectorId2 }, + field: "x", + modification: 111, + }; + applyAgentEdit(modifyEdit3, idGenerator, simpleSchema.definitions); + + const identifier = (view.root.vectors[0] as Vector).id; + const identifier2 = (view.root.vectors[1] as Vector2).id; + + const expected = { + "str": "testStr", + "vectors": [ + { + "id": identifier, + "x": 111, + "y": 3, + "z": 4, + }, + { + "id": identifier2, + "x2": 3, + "y2": 4, + "z2": 5, + }, + ], + "bools": [false], + }; + + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + describe("remove edits", () => { + it("removes a single item in an array", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObject], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { target: vectorId1 }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "str": "testStr", + "vectors": [], + "bools": [true], + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("removes a single item in a subtree's array", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithSubtree], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + innerObject: { + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }, + }); + + idGenerator.assignIds(view.root); + + const vectorId1 = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.innerObject.vectors[0]!) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { target: vectorId1 }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "innerObject": { + "str": "testStr", + "vectors": [], + "bools": [true], + }, + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("removes an item in a non array field", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithNonArrayVectorField], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + singleVector: new Vector({ x: 1, y: 2, z: 3 }), + vectors: [], + bools: [true], + }); + + idGenerator.assignIds(view.root); + assert(view.root.singleVector !== undefined); + + const singleVectorId = idGenerator.getId(view.root.singleVector) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { target: singleVectorId }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "vectors": [], + "bools": [true], + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("removes an item in a subtree's non array field", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithSubtree], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + innerObject: { + str: "testStr", + vectors: [], + bools: [true], + singleVector: new Vector({ x: 1, y: 2, z: 3 }), + }, + }); + + idGenerator.assignIds(view.root); + assert(view.root.innerObject.singleVector !== undefined); + + const singleVectorId = + idGenerator.getId(view.root.innerObject.singleVector) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { target: singleVectorId }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "innerObject": { + "str": "testStr", + "vectors": [], + "bools": [true], + }, + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("removing a required root fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObject], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + const rootId = idGenerator.getId(view.root) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove the root", + type: "remove", + source: { target: rootId }, + }; + + assert.throws( + () => applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions), + + validateUsageError( + /The root is required, and cannot be removed. Please use modify edit instead./, + ), + ); + }); + + it("removes a range of items", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObject], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId2 = idGenerator.getId(view.root.vectors[1]!) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "str": "testStr", + "vectors": [], + "bools": [true], + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("removes a subtree's array range of items", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithSubtree], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + innerObject: { + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }, + }); + + idGenerator.assignIds(view.root); + + const vectorId1 = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.innerObject.vectors[0]!) ?? fail("ID expected."); + + const vectorId2 = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + idGenerator.getId(view.root.innerObject.vectors[1]!) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + }; + applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions); + + const expected = { + "innerObject": { + "str": "testStr", + "vectors": [], + "bools": [true], + }, + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("invalid range of items fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithMultipleVectorArrays], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + vectors2: [new Vector({ x: 3, y: 4, z: 5 }), new Vector({ x: 4, y: 5, z: 6 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId2 = idGenerator.getId(view.root.vectors2[0]!) ?? fail("ID expected."); + + const removeEdit: TreeEdit = { + explanation: "remove a vector", + type: "remove", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + }; + + assert.throws( + () => applyAgentEdit(removeEdit, idGenerator, simpleSchema.definitions), + validateUsageError( + /The "from" node and "to" nodes of the range must be in the same parent array./, + ), + ); + }); + }); + + describe("Move Edits", () => { + it("move a single item", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithMultipleVectorArrays], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 })], + vectors2: [new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + const vectorId2 = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + explanation: "Move a vector", + type: "move", + source: { target: vectorId1 }, + destination: { + type: "arrayPlace", + parentId: vectorId2, + field: "vectors2", + location: "start", + }, + }; + applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier = view.root.vectors2[0]!.id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier2 = view.root.vectors2[1]!.id; + + const expected = { + "str": "testStr", + "vectors": [], + "vectors2": [ + { + "id": identifier, + "x": 1, + "y": 2, + "z": 3, + }, + { + "id": identifier2, + "x": 2, + "y": 3, + "z": 4, + }, + ], + "bools": [true], + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("move range of items", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithMultipleVectorArrays], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + vectors2: [new Vector({ x: 3, y: 4, z: 5 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId2 = idGenerator.getId(view.root.vectors[1]!) ?? fail("ID expected."); + const vectorId3 = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + explanation: "Move a vector", + type: "move", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + destination: { + type: "arrayPlace", + parentId: vectorId3, + field: "vectors2", + location: "start", + }, + }; + applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier = view.root.vectors2[0]!.id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier2 = view.root.vectors2[1]!.id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const identifier3 = view.root.vectors2[2]!.id; + + const expected = { + "str": "testStr", + "vectors": [], + "vectors2": [ + { + "id": identifier, + "x": 1, + "y": 2, + "z": 3, + }, + { + "id": identifier2, + "x": 2, + "y": 3, + "z": 4, + }, + { + "id": identifier3, + "x": 3, + "y": 4, + "z": 5, + }, + ], + "bools": [true], + }; + assert.deepEqual( + JSON.stringify(view.root, undefined, 2), + JSON.stringify(expected, undefined, 2), + ); + }); + + it("moving invalid types fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithDifferentVectorArrayTypes], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + vectors2: [new Vector2({ x2: 3, y2: 4, z2: 5 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId2 = idGenerator.getId(view.root.vectors[1]!) ?? fail("ID expected."); + const vectorId3 = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + destination: { + type: "arrayPlace", + parentId: vectorId3, + field: "vectors2", + location: "start", + }, + }; + assert.throws( + () => applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/Illegal node type in destination array/), + ); + }); + + it("moving invalid range fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithMultipleVectorArrays], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + vectors2: [new Vector({ x: 3, y: 4, z: 5 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId1 = idGenerator.getId(view.root.vectors[0]!) ?? fail("ID expected."); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const vectorId2 = idGenerator.getId(view.root.vectors2[0]!) ?? fail("ID expected."); + const vectorId3 = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + from: { + target: vectorId1, + type: "objectPlace", + place: "before", + }, + to: { + target: vectorId2, + type: "objectPlace", + place: "after", + }, + }, + destination: { + type: "arrayPlace", + parentId: vectorId3, + field: "vectors2", + location: "start", + }, + }; + + assert.throws( + () => applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions), + validateUsageError( + /The "from" node and "to" nodes of the range must be in the same parent array./, + ), + ); + }); + + it("moving elements which aren't under an array fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithNonArrayVectorField], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + singleVector: new Vector({ x: 1, y: 2, z: 3 }), + vectors: [new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + assert(view.root.singleVector !== undefined); + + const strId = idGenerator.getId(view.root.singleVector) ?? fail("ID expected."); + const vectorId = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + target: strId, + }, + destination: { + type: "arrayPlace", + parentId: vectorId, + field: "vectors", + location: "start", + }, + }; + + assert.throws( + () => applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/the source node must be within an arrayNode/), + ); + }); + + it("providing arrayPlace with non-existant field fails", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithNonArrayVectorField], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + singleVector: new Vector({ x: 1, y: 2, z: 3 }), + vectors: [new Vector({ x: 2, y: 3, z: 4 })], + bools: [true], + }); + + idGenerator.assignIds(view.root); + assert(view.root.singleVector !== undefined); + + const strId = idGenerator.getId(view.root.singleVector) ?? fail("ID expected."); + const vectorId = idGenerator.getId(view.root) ?? fail("ID expected."); + + const moveEdit: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + target: strId, + }, + destination: { + type: "arrayPlace", + parentId: vectorId, + field: "nonExistantField", + location: "start", + }, + }; + + assert.throws( + () => applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/No child under field field/), + ); + }); + }); + + it("treeEdits with object ids that don't exist", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const configWithMultipleVectors = new TreeViewConfiguration({ + schema: [RootObjectWithMultipleVectorArrays], + }); + const view = tree.viewWith(configWithMultipleVectors); + const simpleSchema = getSimpleSchema(view.schema); + + view.initialize({ + str: "testStr", + vectors: [new Vector({ x: 1, y: 2, z: 3 }), new Vector({ x: 2, y: 3, z: 4 })], + vectors2: [new Vector({ x: 3, y: 4, z: 5 })], + bools: [true], + }); + + const insertEdit: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, nonVectorField: "invalid", z: 4 }, + destination: { + type: "objectPlace", + target: "testObjectId", + place: "after", + }, + }; + + assert.throws( + () => applyAgentEdit(insertEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/objectIdKey testObjectId does not exist/), + ); + + const insertEdit2: TreeEdit = { + explanation: "Insert a vector", + type: "insert", + content: { [typeField]: Vector.identifier, x: 2, nonVectorField: "invalid", z: 4 }, + destination: { + type: "arrayPlace", + parentId: "testObjectId", + field: "vectors", + location: "start", + }, + }; + + assert.throws( + () => applyAgentEdit(insertEdit2, idGenerator, simpleSchema.definitions), + validateUsageError(/objectIdKey testObjectId does not exist/), + ); + + const moveEdit: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + from: { + target: "testObjectId1", + type: "objectPlace", + place: "before", + }, + to: { + target: "testObjectId2", + type: "objectPlace", + place: "after", + }, + }, + destination: { + type: "arrayPlace", + parentId: "testObjectId3", + field: "vectors2", + location: "start", + }, + }; + const objectIdKeys = ["testObjectId1", "testObjectId2", "testObjectId3"]; + const errorMessage = `objectIdKeys [${objectIdKeys.join(",")}] does not exist`; + assert.throws( + () => applyAgentEdit(moveEdit, idGenerator, simpleSchema.definitions), + validateUsageError(errorMessage), + ); + + const moveEdit2: TreeEdit = { + type: "move", + explanation: "Move a vector", + source: { + target: "testObjectId1", + }, + destination: { + type: "objectPlace", + target: "testObjectId2", + place: "before", + }, + }; + + const objectIdKeys2 = ["testObjectId1", "testObjectId2"]; + const errorMessage2 = `objectIdKeys [${objectIdKeys2.join(",")}] does not exist`; + assert.throws( + () => applyAgentEdit(moveEdit2, idGenerator, simpleSchema.definitions), + validateUsageError(errorMessage2), + ); + + const modifyEdit: TreeEdit = { + explanation: "Modify a vector", + type: "modify", + target: { target: "testObjectId" }, + field: "x", + modification: 111, + }; + + assert.throws( + () => applyAgentEdit(modifyEdit, idGenerator, simpleSchema.definitions), + validateUsageError(/objectIdKey testObjectId does not exist/), + ); + }); +}); diff --git a/packages/framework/ai-collab/src/test/explicit-strategy/integration/conference.spec.ts b/packages/framework/ai-collab/src/test/explicit-strategy/integration/conference.spec.ts new file mode 100644 index 000000000000..30d379ba9abd --- /dev/null +++ b/packages/framework/ai-collab/src/test/explicit-strategy/integration/conference.spec.ts @@ -0,0 +1,183 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + SchemaFactory, + SharedTree, + TreeViewConfiguration, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; + +import { generateTreeEdits } from "../../../explicit-strategy/index.js"; +import { initializeOpenAIClient } from "../utils.js"; + +const sf = new SchemaFactory("Planner"); + +// eslint-disable-next-line jsdoc/require-jsdoc +export class Session extends sf.object("Session", { + id: sf.identifier, + title: sf.string, + abstract: sf.string, + sessionType: sf.required(sf.string, { + metadata: { + description: + "This is one of four possible strings: 'Session', 'Workshop', 'Panel', or 'Keynote'", + }, + }), + created: sf.required(sf.number, { + metadata: { + // llmDefault: () => Date.now(), TODO: Add this back when we have a defaulting value solution + }, + }), + lastChanged: sf.required(sf.number, { + metadata: { + // llmDefault: () => Date.now(), TODO: Add this back when we have a defaulting value solution + }, + }), +}) {} + +const SessionType = { + session: "Session", + workshop: "Workshop", + panel: "Panel", + keynote: "Keynote", +}; + +// eslint-disable-next-line jsdoc/require-jsdoc +export class Sessions extends sf.array("Sessions", Session) {} + +// eslint-disable-next-line jsdoc/require-jsdoc +export class Day extends sf.object("Day", { + sessions: sf.required(Sessions, { + metadata: { + description: "The sessions scheduled on this day.", + }, + }), +}) {} +// eslint-disable-next-line jsdoc/require-jsdoc +export class Days extends sf.array("Days", Day) {} + +// eslint-disable-next-line jsdoc/require-jsdoc +export class Conference extends sf.object("Conference", { + name: sf.string, + sessions: sf.required(Sessions, { + metadata: { + description: + "These sessions are not scheduled yet. The user (or AI agent) can move them to a specific day.", + }, + }), + days: Days, +}) {} + +const factory = SharedTree.getFactory(); + +const TEST_MODEL_NAME = "gpt-4o"; + +describe.skip("Agent Editing Integration", () => { + process.env.OPENAI_API_KEY = ""; // DON'T COMMIT THIS + process.env.AZURE_OPENAI_API_KEY = "TODO "; // DON'T COMMIT THIS + process.env.AZURE_OPENAI_ENDPOINT = "TODO "; + process.env.AZURE_OPENAI_DEPLOYMENT = "gpt-4o"; + + it("Roblox Test", async () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: Conference })); + + view.initialize({ + name: "Roblox Creator x Investor Conference", + sessions: [], + days: [ + { + sessions: [ + { + title: "Can Roblox achieve 120 hz?", + abstract: + "With the latest advancements in the G transformation, we may achieve up to 120 hz and still have time for lunch.", + sessionType: SessionType.session, + created: Date.now(), + lastChanged: Date.now(), + }, + { + title: "Roblox in VR", + abstract: + "Grab your VR headset and discover the latest in Roblox VR technology. Attendees of this lecture will receive a free VR headset.", + sessionType: SessionType.workshop, + created: Date.now(), + lastChanged: Date.now(), + }, + { + title: "What about fun?", + abstract: "Can profit and the delightful smiles of the children coexist?", + sessionType: SessionType.keynote, + created: Date.now(), + lastChanged: Date.now(), + }, + { + title: "Combat in Roblox", + abstract: + "Get the latest tips and tricks for fighting your friends in roblox. Bonus: learn how to make your own sword!", + sessionType: SessionType.panel, + created: Date.now(), + lastChanged: Date.now(), + }, + ], + }, + { + sessions: [ + { + title: "Monetizing Children", + abstract: "Maximize those Robux.", + sessionType: SessionType.session, + created: Date.now(), + lastChanged: Date.now(), + }, + { + title: "Racecars!", + abstract: + "Find out how to build the fastest racecar in Roblox. Then, challenge your friends!", + sessionType: SessionType.workshop, + created: Date.now(), + lastChanged: Date.now(), + }, + { + title: + "The Gentrification of Roblox City's Downtown (and why that's a good thing)", + abstract: + "Real estate prices in Robloxia are skyrocketing, moving cash into the hands of those who can use it most wisely.", + sessionType: SessionType.session, + created: Date.now(), + lastChanged: Date.now(), + }, + ], + }, + ], + }); + const openAIClient = initializeOpenAIClient("openai"); + const abortController = new AbortController(); + await generateTreeEdits({ + openAI: { client: openAIClient, modelName: TEST_MODEL_NAME }, + treeNode: view.root, + prompt: { + userAsk: "Please alphabetize the sessions.", + systemRoleContext: "", + }, + limiters: { + abortController, + maxModelCalls: 15, + }, + finalReviewStep: true, + }); + + const stringified = JSON.stringify(view.root, undefined, 2); + console.log(stringified); + }); +}); diff --git a/packages/framework/ai-collab/src/test/explicit-strategy/promptGeneration.spec.ts b/packages/framework/ai-collab/src/test/explicit-strategy/promptGeneration.spec.ts new file mode 100644 index 000000000000..36b4438b30f7 --- /dev/null +++ b/packages/framework/ai-collab/src/test/explicit-strategy/promptGeneration.spec.ts @@ -0,0 +1,390 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { createIdCompressor } from "@fluidframework/id-compressor/internal"; +// eslint-disable-next-line import/no-internal-modules +import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils/internal"; +import { + getSimpleSchema, + SchemaFactory, + SharedTree, + Tree, + TreeViewConfiguration, + type TreeNode, + // eslint-disable-next-line import/no-internal-modules +} from "@fluidframework/tree/internal"; + +// eslint-disable-next-line import/no-internal-modules +import { applyAgentEdit } from "../../explicit-strategy/agentEditReducer.js"; +// eslint-disable-next-line import/no-internal-modules +import { IdGenerator } from "../../explicit-strategy/idGenerator.js"; +import { + createEditListHistoryPrompt, + getEditingSystemPrompt, + getPlanningSystemPrompt, + getReviewSystemPrompt, + toDecoratedJson, + type EditLog, + // eslint-disable-next-line import/no-internal-modules +} from "../../explicit-strategy/promptGeneration.js"; + +const factory = SharedTree.getFactory(); +const sf = new SchemaFactory("test"); + +class Todo extends sf.object("Todo", { + title: sf.required(sf.string, { + metadata: { description: "The title of the todo" }, + }), + completed: sf.required(sf.boolean, { + metadata: { description: "Whether the todo is completed" }, + }), +}) {} + +class TestTodoAppSchema extends sf.object("TestTodoAppSchema", { + title: sf.required(sf.string, { + metadata: { description: "The title of the group of todos" }, + }), + description: sf.required(sf.string, { + metadata: { description: "The description of the group of todos" }, + }), + todos: sf.required(sf.array(Todo), { + metadata: { description: "The list of todos" }, + }), +}) {} + +const initialAppState = { + title: "My First Todo List", + description: "This is a list of todos", + todos: [ + { + title: "Task 1", + completed: true, + }, + { + title: "Task 2", + completed: true, + }, + ], +}; + +// The following test suite checks the current state of prompt generated versus a snapshot of the last known expected prompt. +// If the prompt changes, these test will break and the snapshot should be updated. This suite effectively gives us a guard against unexpected prompt changes +// which are imperitive to be caught as even small changes can lead to incorrect LLM behavior. +describe("Prompt Generation Regression Tests", () => { + let idGenerator: IdGenerator; + beforeEach(() => { + idGenerator = new IdGenerator(); + }); + + const userAsk = "Change the completed to false for the first task and create a new edit"; + const systemRoleContext = "You're a helpful AI assistant"; + const plan = + "Change the completed field to false for the todo at index 0 in the list of todos"; + + const getExpectedEditingSystemPrompt = (params: { + plan?: string; + userAsk: string; + editLog: EditLog; + treeNode: TreeNode; + }): string[] => { + return [ + "", + "\tYou are a collaborative agent who interacts with a JSON tree by performing edits to achieve a user-specified goal.", + "\t\t\tThe application that owns the JSON tree has the following guidance about your role: You're a helpful AI assistant", + "\tEdits are JSON objects that conform to the following schema.", + '\tThe top level object you produce is an "EditWrapper" object which contains one of "Insert", "Modify", "Remove", "Move", or null.', + "\tinterface ObjectTarget {", + " target: string; // The id of the object (as specified by the object's __fluid_objectId property) that is being referenced", + "}", + "", + "// A pointer to a location either just before or just after an object that is in an array", + "interface ObjectPlace {", + ' type: "objectPlace";', + " target: string; // The id (__fluid_objectId) of the object that the new/moved object should be placed relative to. This must be the id of an object that already existed in the tree content that was originally supplied.", + ' place: "before" | "after"; // Where the new/moved object will be relative to the target object - either just before or just after', + "}", + "", + '// either the "start" or "end" of an array, as specified by a "parent" ObjectTarget and a "field" name under which the array is stored (useful for prepending or appending)', + "interface ArrayPlace {", + ' type: "arrayPlace";', + " parentId: string; // The id (__fluid_objectId) of the parent object of the array. This must be the id of an object that already existed in the tree content that was originally supplied.", + " field: string; // The key of the array to insert into", + ' location: "start" | "end"; // Where to insert into the array - either the start or the end', + "}", + "", + '// A range of objects in the same array specified by a "from" and "to" Place. The "to" and "from" objects MUST be in the same array.', + "interface Range {", + " from: ObjectPlace; // A pointer to a location either just before or just after an object that is in an array", + " to: ObjectPlace; // A pointer to a location either just before or just after an object that is in an array", + "}", + "", + "// Inserts a new object at a specific Place or ArrayPlace.", + "interface Insert {", + ' type: "insert";', + " explanation: string; // A description of what this edit is meant to accomplish in human readable English", + " content: any; // Domain-specific content here", + " destination: ArrayPlace | ObjectPlace;", + "}", + "", + "// Deletes an object or Range of objects from the tree.", + "interface Remove {", + ' type: "remove";', + " explanation: string; // A description of what this edit is meant to accomplish in human readable English", + " source: ObjectTarget | Range;", + "}", + "", + "// Moves an object or Range of objects to a new Place or ArrayPlace.", + "interface Move {", + ' type: "move";', + " explanation: string; // A description of what this edit is meant to accomplish in human readable English", + " source: ObjectTarget | Range;", + " destination: ArrayPlace | ObjectPlace;", + "}", + "", + "// Sets a field on a specific ObjectTarget.", + "interface Modify {", + ' type: "modify";', + " explanation: string; // A description of what this edit is meant to accomplish in human readable English", + " target: ObjectTarget;", + ' field: "title" | "description" | "completed";', + " modification: any; // Domain-specific content here", + "}", + "", + "interface EditWrapper {", + " edit: Insert | Remove | Move | Modify | null; // The next edit to apply to the tree, or null if the task is complete.", + "}", + "", + "\tThe tree is a JSON object with the following schema: interface TestTodoAppSchema { title: string; description: string; todos: Todo[]; } interface Todo { title: string; completed: boolean; }", + params.plan === undefined + ? "\t" + : `\tYou have made a plan to accomplish the user's goal. The plan is: "${params.plan}". You will perform one or more edits that correspond to that plan to accomplish the goal.`, + ...(params.editLog?.length === 0 + ? ["\t"] + : [ + `\tYou have already performed the following edits:`, + `\t\t\t${createEditListHistoryPrompt(params.editLog).split("\n")[0]}`, + ...(params.editLog.length > 1 + ? createEditListHistoryPrompt(params.editLog).split("\n").slice(1) + : []), + `\t\t\tThis means that the current state of the tree reflects these changes.`, + ]), + + `\tThe current state of the tree is: ${toDecoratedJson(idGenerator, params.treeNode)}.`, + `${params.editLog.length > 0 ? "\tBefore you made the above edits t" : "\tT"}he user requested you accomplish the following goal:`, + `\t"${params.userAsk}"`, + "\tIf the goal is now completed or is impossible, you should return null.", + '\tOtherwise, you should create an edit that makes progress towards the goal. It should have an English description ("explanation") of which edit to perform (specifying one of the allowed edit types).', + ]; + }; + + it("Planning Prompt has no regression", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestTodoAppSchema })); + view.initialize(initialAppState); + + const actualPrompt = getPlanningSystemPrompt(view.root, userAsk, systemRoleContext).split( + "\n", + ); + + const expectedPrompt = [ + "", + "\tI'm an agent who makes plans for another agent to achieve a user-specified goal to update the state of an application.", + "\t\t\tThe other agent follows this guidance: You're a helpful AI assistant", + "\tThe application state tree is a JSON object with the following schema: interface TestTodoAppSchema { title: string; description: string; todos: Todo[]; } interface Todo { title: string; completed: boolean; }", + '\tThe current state is: {"title":"My First Todo List","description":"This is a list of todos","todos":[{"title":"Task 1","completed":true},{"title":"Task 2","completed":true}]}.', + "\tThe user requested that I accomplish the following goal:", + `\t"${userAsk}"`, + "\tI've made a plan to accomplish this goal by doing a sequence of edits to the tree.", + "\tEdits can include setting the root, inserting, modifying, removing, or moving elements in the tree.", + "\tHere is my plan:", + ]; + + assert.deepStrictEqual(actualPrompt, expectedPrompt); + }); + + it("Editing System Prompt with no plan and empty edit log has no regression", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestTodoAppSchema })); + view.initialize(initialAppState); + + idGenerator.assignIds(view.root); + + const actualPromptWithEmptyEditLogAndNoPlan = getEditingSystemPrompt( + userAsk, + idGenerator, + view.root, + [], + systemRoleContext, + ).split("\n"); + + const expectedPromptWithEmptyEditLogAndNoPlan = getExpectedEditingSystemPrompt({ + plan: undefined, + userAsk, + editLog: [], + treeNode: view.root, + }); + assert.deepStrictEqual( + actualPromptWithEmptyEditLogAndNoPlan, + expectedPromptWithEmptyEditLogAndNoPlan, + ); + }); + + it("Editing System Prompt with plan and empty edit log has no regression", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestTodoAppSchema })); + view.initialize(initialAppState); + + idGenerator.assignIds(view.root); + const actualPromptWithEmptyEditLogAndPlan = getEditingSystemPrompt( + userAsk, + idGenerator, + view.root, + [], + systemRoleContext, + plan, + ).split("\n"); + const expectedPromptWithEmptyEditLogAndPlan = getExpectedEditingSystemPrompt({ + plan, + userAsk, + editLog: [], + treeNode: view.root, + }); + + assert.deepStrictEqual( + actualPromptWithEmptyEditLogAndPlan, + expectedPromptWithEmptyEditLogAndPlan, + ); + }); + + it("Editing System Prompt with plan and populated edit log has no regression", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestTodoAppSchema })); + view.initialize(initialAppState); + + idGenerator.assignIds(view.root); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const todo1Id = idGenerator.getId(view.root.todos[0]!)!; + + const editLog: EditLog = [ + // We expect an error for this edit because the field is 'completed' not 'complete' + { + edit: { + type: "modify", + explanation: + "Change the completed field to false for the todo at index 0 in the list of todos", + target: { target: todo1Id }, + field: "complete", + modification: false, + }, + }, + { + edit: { + type: "modify", + explanation: + "Change the completed field to false for the todo at index 0 in the list of todos", + target: { target: todo1Id }, + field: "completed", + modification: false, + }, + }, + ]; + const simpleSchema = getSimpleSchema(Tree.schema(view.root)); + for (const editLogEntry of editLog) { + try { + applyAgentEdit(editLogEntry.edit, idGenerator, simpleSchema.definitions); + } catch (error) { + assert(error instanceof Error); + editLogEntry.error = error.message; + } + } + + const actualPromptWithPlanAndEditLog = getEditingSystemPrompt( + userAsk, + idGenerator, + view.root, + editLog, + systemRoleContext, + plan, + ).split("\n"); + + const expectedPromptWithPlanAndEditLog = getExpectedEditingSystemPrompt({ + plan, + userAsk, + editLog, + treeNode: view.root, + }); + assert.deepStrictEqual(actualPromptWithPlanAndEditLog, expectedPromptWithPlanAndEditLog); + }); + + it("Review System Prompt has no regression", () => { + const tree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "tree", + ); + const view = tree.viewWith(new TreeViewConfiguration({ schema: TestTodoAppSchema })); + view.initialize(initialAppState); + + idGenerator.assignIds(view.root); + + const originalDecoratedJson = toDecoratedJson(idGenerator, view.root); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const todo1Id = idGenerator.getId(view.root.todos[0]!)!; + const simpleSchema = getSimpleSchema(Tree.schema(view.root)); + applyAgentEdit( + { + type: "modify", + explanation: + "Change the completed field to false for the todo at index 0 in the list of todos", + target: { target: todo1Id }, + field: "completed", + modification: false, + }, + idGenerator, + simpleSchema.definitions, + ); + + const actualReviewSystemPrompt = getReviewSystemPrompt( + userAsk, + idGenerator, + view.root, + originalDecoratedJson, + systemRoleContext, + ).split("\n"); + + const modifiedDecoratedJson = toDecoratedJson(idGenerator, view.root); + const expectedReviewSystemPrompt = [ + "", + "\tYou are a collaborative agent who interacts with a JSON tree by performing edits to achieve a user-specified goal.", + "\t\t\tThe application that owns the JSON tree has the following guidance: You're a helpful AI assistant", + "\tYou have performed a number of actions already to accomplish a user request.", + "\tYou must review the resulting state to determine if the actions you performed successfully accomplished the user's goal.", + "\tThe tree is a JSON object with the following schema: interface TestTodoAppSchema { title: string; description: string; todos: Todo[]; } interface Todo { title: string; completed: boolean; }", + `\tThe state of the tree BEFORE changes was: ${originalDecoratedJson}.`, + `\tThe state of the tree AFTER changes is: ${modifiedDecoratedJson}.`, + "\tThe user requested that the following goal should be accomplished:", + `\t${userAsk}`, + "\tWas the goal accomplished?", + ]; + + assert.deepStrictEqual(actualReviewSystemPrompt, expectedReviewSystemPrompt); + }); +}); diff --git a/packages/framework/ai-collab/src/test/explicit-strategy/utils.ts b/packages/framework/ai-collab/src/test/explicit-strategy/utils.ts new file mode 100644 index 000000000000..f880589da3b0 --- /dev/null +++ b/packages/framework/ai-collab/src/test/explicit-strategy/utils.ts @@ -0,0 +1,78 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +// eslint-disable-next-line import/no-internal-modules +import { UsageError } from "@fluidframework/telemetry-utils/internal"; +import { OpenAI, AzureOpenAI } from "openai"; + +/** + * Validates that the error is a UsageError with the expected error message. + */ +export function validateUsageError(expectedErrorMsg: string | RegExp): (error: Error) => true { + return (error: Error) => { + assert(error instanceof UsageError); + if ( + typeof expectedErrorMsg === "string" + ? error.message !== expectedErrorMsg + : !expectedErrorMsg.test(error.message) + ) { + throw new Error( + `Unexpected assertion thrown\nActual: ${error.message}\nExpected: ${expectedErrorMsg}`, + ); + } + return true; + }; +} + +/** + * Creates an OpenAI Client session. + * Depends on the following environment variables: + * + * If using the OpenAI API: + * - OPENAI_API_KEY + * + * If using the Azure OpenAI API: + * - AZURE_OPENAI_API_KEY + * - AZURE_OPENAI_ENDPOINT + * - AZURE_OPENAI_DEPLOYMENT + * + */ +export function initializeOpenAIClient(service: "openai" | "azure"): OpenAI { + if (service === "azure") { + const apiKey = process.env.AZURE_OPENAI_API_KEY; + if (apiKey === null || apiKey === undefined) { + throw new Error("AZURE_OPENAI_API_KEY environment variable not set"); + } + + const endpoint = process.env.AZURE_OPENAI_ENDPOINT; + if (endpoint === null || endpoint === undefined) { + throw new Error("AZURE_OPENAI_ENDPOINT environment variable not set"); + } + + const deployment = process.env.AZURE_OPENAI_DEPLOYMENT; + if (deployment === null || deployment === undefined) { + throw new Error("AZURE_OPENAI_DEPLOYMENT environment variable not set"); + } + + const client = new AzureOpenAI({ + endpoint, + deployment, + apiKey, + apiVersion: "2024-08-01-preview", + timeout: 2500000, + }); + return client; + } else { + const apiKey = process.env.OPENAI_API_KEY; + if (apiKey === null || apiKey === undefined) { + throw new Error("OPENAI_API_KEY environment variable not set"); + } + + const client = new OpenAI({ apiKey }); + return client; + } +} diff --git a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManager.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManager.spec.ts similarity index 99% rename from packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManager.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManager.spec.ts index d2053b06fce3..021f7e22150a 100644 --- a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManager.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManager.spec.ts @@ -8,7 +8,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; import * as z from "zod"; -import { SharedTreeBranchManager } from "../../shared-tree-diff/index.js"; +import { SharedTreeBranchManager } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeNodeTest"); diff --git a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManagerMergeDiff.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManagerMergeDiff.spec.ts similarity index 99% rename from packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManagerMergeDiff.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManagerMergeDiff.spec.ts index bdbe54288819..16d17f17d19f 100644 --- a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeBranchManagerMergeDiff.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeBranchManagerMergeDiff.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; -import { SharedTreeBranchManager } from "../../shared-tree-diff/index.js"; +import { SharedTreeBranchManager } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeNodeTest"); diff --git a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffArrays.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffArrays.spec.ts similarity index 87% rename from packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffArrays.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffArrays.spec.ts index b070f844f444..1bf1e7089fcb 100644 --- a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffArrays.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffArrays.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; -import { createMergableIdDiffSeries, sharedTreeDiff } from "../../shared-tree-diff/index.js"; +import { createMergableIdDiffSeries, sharedTreeDiff } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeNodeTest"); @@ -24,6 +24,7 @@ describe("sharedTreeDiff() - arrays", () => { { type: "REMOVE", path: [1], + objectId: undefined, oldValue: "testing", }, ]); @@ -68,6 +69,7 @@ describe("sharedTreeDiff() - arrays", () => { { type: "CHANGE", path: ["state", 1, "test"], + objectId: undefined, value: false, oldValue: true, }, @@ -76,7 +78,13 @@ describe("sharedTreeDiff() - arrays", () => { it("array to object", () => { assert.deepStrictEqual(sharedTreeDiff({ data: [] }, { data: { val: "test" } }), [ - { type: "CHANGE", path: ["data"], value: { val: "test" }, oldValue: [] }, + { + type: "CHANGE", + path: ["data"], + objectId: undefined, + value: { val: "test" }, + oldValue: [], + }, ]); }); }); @@ -110,12 +118,14 @@ describe("sharedTreeDiff() - arrays with object ID strategy", () => { { type: "CHANGE", path: ["state", 0], + objectId: undefined, value: { id: "1", test: true }, oldValue: "test", }, { type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 0, value: treeNode.state[1], }, @@ -148,12 +158,14 @@ describe("sharedTreeDiff() - arrays with object ID strategy", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, { type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, value: treeNode.state[1], newIndex: 0, }, @@ -187,12 +199,14 @@ describe("sharedTreeDiff() - arrays with object ID strategy", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, { type: "REMOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, oldValue: treeNode.state[1], }, { @@ -230,18 +244,21 @@ describe("sharedTreeDiff() - arrays with object ID strategy", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, { type: "CHANGE", path: ["state", 0, "test"], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, oldValue: true, value: false, }, { type: "REMOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, oldValue: treeNode.state[1], }, { @@ -279,18 +296,21 @@ describe("sharedTreeDiff() - arrays with object ID strategy", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, { type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, value: treeNode.state[1], newIndex: 0, }, { type: "CHANGE", path: ["state", 1, "test"], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, oldValue: true, value: false, }, @@ -337,6 +357,7 @@ describe("createMergableIdDiffSeries()", () => { // good [1, 2] -> [2, 1] type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, @@ -344,6 +365,7 @@ describe("createMergableIdDiffSeries()", () => { // should be removed, as it is redundant due to a swap. type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, value: treeNode.state[1], newIndex: 0, }, @@ -354,6 +376,7 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, value: treeNode.state[0], newIndex: 1, }, @@ -377,12 +400,14 @@ describe("createMergableIdDiffSeries()", () => { // good [1, 2,] -> [2] type: "REMOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, oldValue: treeNode.state[0], }, { // Should be removed, 2 will be in the right position after the removal of 1 type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 0, value: treeNode.state[1], }, @@ -404,6 +429,7 @@ describe("createMergableIdDiffSeries()", () => { // [1, 2] -> [2] type: "REMOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, oldValue: treeNode.state[0], }, ]); @@ -440,12 +466,14 @@ describe("createMergableIdDiffSeries()", () => { { type: "REMOVE", path: ["state", 2], + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, oldValue: treeNode.state[2], }, { // expected to have the path index shifted back due to prior remove. type: "MOVE", path: ["state", 3], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 4, value: treeNode.state[3], }, @@ -463,6 +491,7 @@ describe("createMergableIdDiffSeries()", () => { // expected to be removed TODO: Potential bug - Why does this diff even get created? type: "MOVE", path: ["state", 4], + objectId: "4", newIndex: 4, value: { id: "4", test: true }, }, @@ -475,6 +504,7 @@ describe("createMergableIdDiffSeries()", () => { // [1, 2, 3, 4] -> [1, 2, 4] type: "REMOVE", path: ["state", 2], + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, oldValue: treeNode.state[2], }, { @@ -493,6 +523,7 @@ describe("createMergableIdDiffSeries()", () => { // [1, 2, 4, 6, 5] -> [1, 2, 4, 6, 5, 4] type: "MOVE", path: ["state", 2], // Note the index was shifted back because of the prior remove + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 4, value: treeNode.state[3], }, @@ -529,24 +560,28 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, newIndex: 2, value: treeNode.state[0], }, { type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 0, value: treeNode.state[1], }, { type: "MOVE", path: ["state", 2], + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[2], }, { type: "MOVE", path: ["state", 3], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 1, value: treeNode.state[3], }, @@ -561,6 +596,7 @@ describe("createMergableIdDiffSeries()", () => { // obj at index 0 moves to index 2 so move everything it jumped over, back type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, newIndex: 2, value: treeNode.state[0], }, @@ -570,6 +606,7 @@ describe("createMergableIdDiffSeries()", () => { // obj at index 1 moves to index 3, so move everything < index 3 back. (only applies to index moved over) type: "MOVE", path: ["state", 1], // source index shifted backwards + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[2], }, @@ -577,6 +614,7 @@ describe("createMergableIdDiffSeries()", () => { // [2, 1, 4, 3] -> [2, 4, 1, 3] type: "MOVE", path: ["state", 2], // source index shifted backwards + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 1, value: treeNode.state[3], // keep in mind we are referencing node locations for eqaulity prior to the moves }, @@ -617,12 +655,14 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, newIndex: 2, value: treeNode.state[0], }, { // expected to be reordered to the beginning. path: ["state", 0, "test"], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, type: "CHANGE", value: false, oldValue: true, @@ -631,24 +671,28 @@ describe("createMergableIdDiffSeries()", () => { // expected to be removed due to other moves placing this in the correct pos. type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 0, value: treeNode.state[1], }, { type: "MOVE", path: ["state", 2], + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[2], }, { type: "MOVE", path: ["state", 3], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 1, value: treeNode.state[3], }, { // expected to be reordered to the beginning. path: ["state", 3, "test"], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, type: "CHANGE", value: false, oldValue: true, @@ -661,6 +705,7 @@ describe("createMergableIdDiffSeries()", () => { { // reordered to the beginning path: ["state", 0, "test"], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, type: "CHANGE", value: false, oldValue: true, @@ -668,6 +713,7 @@ describe("createMergableIdDiffSeries()", () => { { // reordered to the beginning path: ["state", 3, "test"], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, type: "CHANGE", value: false, oldValue: true, @@ -676,6 +722,7 @@ describe("createMergableIdDiffSeries()", () => { // [1, 2, 3, 4] -> [2, 3, 1, 4] type: "MOVE", path: ["state", 0], + objectId: (treeNode.state[0] as SimpleObjectTreeNode).id, newIndex: 2, value: treeNode.state[0], }, @@ -683,6 +730,7 @@ describe("createMergableIdDiffSeries()", () => { // [2, 3, 1, 4] -> [2, 1, 4, 3] type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[2] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[2], }, @@ -690,6 +738,7 @@ describe("createMergableIdDiffSeries()", () => { // [2, 1, 4, 3] -> [2, 4, 1, 3] type: "MOVE", path: ["state", 2], + objectId: (treeNode.state[3] as SimpleObjectTreeNode).id, newIndex: 1, value: treeNode.state[3], }, @@ -725,6 +774,7 @@ describe("createMergableIdDiffSeries()", () => { // expected to be reordered to to the end of the array. type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[1], }, @@ -742,57 +792,11 @@ describe("createMergableIdDiffSeries()", () => { // Expected to be removed TODO: Potential BUG - Why does this diff even get created? type: "MOVE", path: ["state", 3], + objectId: "4", newIndex: 3, value: { id: "4", test: true }, // also records this value as pojo instead of the tree node? }, ]); - // { - // // good [1, 2, 3, 4] -> [2, 1, 3, 4] - // type: "MOVE", - // path: ["state", 0], - // newIndex: 1, - // value: treeNode.state[0], - // }, - // { - // // expected to be removed, unecessary due to swap - // type: "MOVE", - // path: ["state", 1], - // newIndex: 0, - // value: treeNode.state[1], - // }, - // { - // // good [2, 1, 3, 4] -> [2, 1, 4] - // type: "REMOVE", - // path: ["state", 2], - // oldValue: treeNode.state[2], - // }, - // { - // // expected to be reordered to the end [2, 1, 4, 6, 5] -> [2, 1, 4, 6, 5, 4] - // type: "MOVE", - // path: ["state", 3], - // newIndex: 4, - // value: treeNode.state[3], - // }, - // { - // // good [2, 1, 4] -> [2, 1, 4, 6] - // type: "CREATE", - // path: ["state", 2], - // value: { id: "6", test: true }, - // }, - // { - // // good [2, 1, 4, 6] -> [2, 1, 4, 6, 5] - // type: "CREATE", - // path: ["state", 3], - // value: { id: "5", test: true }, - // }, - // { - // // expected to be removed TODO: Potential bug - Why does this diff even get created? - // type: "MOVE", - // path: ["state", 4], - // newIndex: 4, - // value: { id: "4", test: true }, - // }, - // ]); const minimalDiffs = createMergableIdDiffSeries(treeNode, diffs, "id"); @@ -813,6 +817,7 @@ describe("createMergableIdDiffSeries()", () => { // [1, 4, 6, 5] -> [1, 6, 5, 4] type: "MOVE", path: ["state", 1], + objectId: (treeNode.state[1] as SimpleObjectTreeNode).id, newIndex: 3, value: treeNode.state[1], }, @@ -916,12 +921,14 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["state", 0], + objectId: treeNode.state[0]?.id, newIndex: 1, value: treeNode.state[0], }, { type: "MOVE", path: ["state", 0, "innerArray", 0], + objectId: treeNode.state[0]?.innerArray[0]?.id, newIndex: 1, value: treeNode.state[0]?.innerArray[0], }, @@ -933,11 +940,13 @@ describe("createMergableIdDiffSeries()", () => { { type: "REMOVE", path: ["state", 2], + objectId: (treeNode.state[2] as SimpleObjectTreeNodeWithObjectArray).id, oldValue: treeNode.state[2], }, { type: "MOVE", path: ["stateArrayTwo", 0, "innerArray", 0], + objectId: treeNode.stateArrayTwo[0]?.innerArray[0]?.id, newIndex: 1, value: treeNode.stateArrayTwo[0]?.innerArray[0], }, @@ -1023,6 +1032,7 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["state", 0, "innerArray", 0], + objectId: treeNode.state[0]?.innerArray[0]?.id, newIndex: 1, value: treeNode.state[0]?.innerArray[0], }, @@ -1037,6 +1047,7 @@ describe("createMergableIdDiffSeries()", () => { { type: "MOVE", path: ["stateArrayTwo", 0, "innerArray", 0], + objectId: treeNode.stateArrayTwo[0]?.innerArray[0]?.id, newIndex: 1, value: treeNode.stateArrayTwo[0]?.innerArray[0], }, diff --git a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffMaps.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffMaps.spec.ts similarity index 93% rename from packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffMaps.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffMaps.spec.ts index 3fc6792f2998..351a5c7c485b 100644 --- a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffMaps.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffMaps.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; -import { sharedTreeDiff } from "../../shared-tree-diff/index.js"; +import { sharedTreeDiff } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeNodeTest"); @@ -43,6 +43,7 @@ describe("sharedTreeDiff() - Maps - Change Diffs", () => { { type: "CHANGE", path: ["stringKey"], + objectId: undefined, oldValue: "test", value: "true", }, @@ -61,6 +62,7 @@ describe("sharedTreeDiff() - Maps - Change Diffs", () => { { type: "CHANGE", path: ["booleanKey"], + objectId: undefined, oldValue: true, value: false, }, @@ -79,6 +81,7 @@ describe("sharedTreeDiff() - Maps - Change Diffs", () => { { type: "CHANGE", path: ["numberKey"], + objectId: undefined, oldValue: 0, value: 1, }, @@ -105,6 +108,7 @@ describe("sharedTreeDiff() - Maps - Change Diffs", () => { { type: "CHANGE", path: ["objectKey", "stringKey"], + objectId: undefined, oldValue: "test", value: "SomethingDifferent", }, @@ -122,6 +126,7 @@ describe("sharedTreeDiff() - Maps - Change Diffs", () => { { type: "CHANGE", path: ["arrayKey"], + objectId: undefined, oldValue: arrayNode, value: undefined, }, diff --git a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffObjects.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffObjects.spec.ts similarity index 95% rename from packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffObjects.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffObjects.spec.ts index fb7242bd3ee0..f42366520640 100644 --- a/packages/framework/ai-collab/src/test/shared-tree-diff/sharedTreeDiffObjects.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/sharedTreeDiffObjects.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; -import { sharedTreeDiff } from "../../shared-tree-diff/index.js"; +import { sharedTreeDiff } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeNodeTest"); @@ -57,6 +57,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["requiredString"], + objectId: undefined, oldValue: "test", value: "true", }, @@ -75,6 +76,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["requiredBoolean"], + objectId: undefined, oldValue: true, value: false, }, @@ -93,6 +95,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["requiredNumber"], + objectId: undefined, oldValue: 0, value: 1, }, @@ -119,6 +122,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["requiredObject", "requiredString"], + objectId: undefined, oldValue: "test", value: "SomethingDifferent", }, @@ -135,6 +139,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["optionalBoolean"], + objectId: undefined, oldValue: true, value: undefined, }, @@ -152,6 +157,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["optionalString"], + objectId: undefined, oldValue: "true", value: undefined, }, @@ -169,6 +175,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["optionalNumber"], + objectId: undefined, oldValue: 1, value: undefined, }, @@ -189,6 +196,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["optionalObject"], + objectId: undefined, oldValue: { requiredString: "test" }, value: undefined, }, @@ -211,6 +219,7 @@ describe("sharedTreeDiff() - Object - Change Diffs", () => { { type: "CHANGE", path: ["optionalArray"], + objectId: undefined, oldValue: arrayNode, value: undefined, }, @@ -298,6 +307,7 @@ describe("sharedTreeDiff() - Object - Remove Diffs", () => { assert.deepStrictEqual(diffs, [ { type: "REMOVE", + objectId: undefined, path: ["optionalBoolean"], oldValue: true, }, @@ -311,6 +321,7 @@ describe("sharedTreeDiff() - Object - Remove Diffs", () => { { type: "REMOVE", path: ["optionalString"], + objectId: undefined, oldValue: "true", }, ]); @@ -323,6 +334,7 @@ describe("sharedTreeDiff() - Object - Remove Diffs", () => { { type: "REMOVE", path: ["optionalNumber"], + objectId: undefined, oldValue: 1, }, ]); @@ -340,6 +352,7 @@ describe("sharedTreeDiff() - Object - Remove Diffs", () => { { type: "REMOVE", path: ["optionalArray"], + objectId: undefined, oldValue: arrayNode, }, ]); @@ -354,6 +367,7 @@ describe("sharedTreeDiff() - Object - Remove Diffs", () => { { type: "REMOVE", path: ["optionalObject"], + objectId: undefined, oldValue: { requiredString: "test" }, }, ]); diff --git a/packages/framework/ai-collab/src/test/utils.spec.ts b/packages/framework/ai-collab/src/test/implicit-strategy/utils.spec.ts similarity index 98% rename from packages/framework/ai-collab/src/test/utils.spec.ts rename to packages/framework/ai-collab/src/test/implicit-strategy/utils.spec.ts index a581f36eccdc..3fc1b04c8fd5 100644 --- a/packages/framework/ai-collab/src/test/utils.spec.ts +++ b/packages/framework/ai-collab/src/test/implicit-strategy/utils.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "node:assert"; import { SchemaFactory } from "@fluidframework/tree"; -import { sharedTreeTraverse } from "../shared-tree-diff/index.js"; +import { sharedTreeTraverse } from "../../implicit-strategy/index.js"; const schemaFactory = new SchemaFactory("TreeTraversalTest"); diff --git a/packages/framework/aqueduct/CHANGELOG.md b/packages/framework/aqueduct/CHANGELOG.md index 1c0712f2a05e..d6df4f781a67 100644 --- a/packages/framework/aqueduct/CHANGELOG.md +++ b/packages/framework/aqueduct/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/aqueduct +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/aqueduct/package.json b/packages/framework/aqueduct/package.json index 2e377063823d..511228d46324 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/aqueduct", - "version": "2.5.0", + "version": "2.10.0", "description": "A set of implementations for Fluid Framework interfaces.", "homepage": "https://fluidframework.com", "repository": { @@ -135,10 +135,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", - "@fluidframework/aqueduct-previous": "npm:@fluidframework/aqueduct@~2.4.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluidframework/aqueduct-previous": "npm:@fluidframework/aqueduct@~2.5.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", @@ -156,7 +156,11 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IDataObjectProps": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts index ca578b870142..8684174e0234 100644 --- a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts +++ b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts @@ -247,4 +247,5 @@ declare type old_as_current_for_Interface_IDataObjectProps = requireAssignableTo * typeValidation.broken: * "Interface_IDataObjectProps": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IDataObjectProps = requireAssignableTo, TypeOnly> diff --git a/packages/framework/attributor/CHANGELOG.md b/packages/framework/attributor/CHANGELOG.md index 27771c646a07..10b803979f9a 100644 --- a/packages/framework/attributor/CHANGELOG.md +++ b/packages/framework/attributor/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/attributor +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/attributor/package.json b/packages/framework/attributor/package.json index 5252f63e4be9..e83faa972dc7 100644 --- a/packages/framework/attributor/package.json +++ b/packages/framework/attributor/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/attributor", - "version": "2.5.0", + "version": "2.10.0", "description": "Operation attributor", "homepage": "https://fluidframework.com", "repository": { @@ -104,9 +104,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/stochastic-test-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/merge-tree": "workspace:~", "@fluidframework/sequence": "workspace:~", diff --git a/packages/framework/client-logger/app-insights-logger/CHANGELOG.md b/packages/framework/client-logger/app-insights-logger/CHANGELOG.md index 19fb5eca395f..3e19e2b9ba8c 100644 --- a/packages/framework/client-logger/app-insights-logger/CHANGELOG.md +++ b/packages/framework/client-logger/app-insights-logger/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/app-insights-logger +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/client-logger/app-insights-logger/package.json b/packages/framework/client-logger/app-insights-logger/package.json index a30cb568b683..bbc1c9850341 100644 --- a/packages/framework/client-logger/app-insights-logger/package.json +++ b/packages/framework/client-logger/app-insights-logger/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/app-insights-logger", - "version": "2.5.0", + "version": "2.10.0", "description": "Contains a Fluid logging client that sends telemetry events to Azure App Insights", "homepage": "https://fluidframework.com", "repository": { @@ -94,9 +94,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/framework/client-logger/fluid-telemetry/CHANGELOG.md b/packages/framework/client-logger/fluid-telemetry/CHANGELOG.md index ba2a760d30dd..4337fa090db1 100644 --- a/packages/framework/client-logger/fluid-telemetry/CHANGELOG.md +++ b/packages/framework/client-logger/fluid-telemetry/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/fluid-telemetry +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/client-logger/fluid-telemetry/package.json b/packages/framework/client-logger/fluid-telemetry/package.json index 34689cf7ab76..74d7eaa050ae 100644 --- a/packages/framework/client-logger/fluid-telemetry/package.json +++ b/packages/framework/client-logger/fluid-telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/fluid-telemetry", - "version": "2.5.0", + "version": "2.10.0", "description": "Customer facing Fluid telemetry types and classes for both producing and consuming said telemetry", "homepage": "https://fluidframework.com", "repository": { @@ -106,8 +106,8 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/client-utils": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", - "@fluidframework/build-tools": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/test-utils": "workspace:~", "@fluidframework/tinylicious-client": "workspace:~", "@fluidframework/tree": "workspace:~", diff --git a/packages/framework/data-object-base/CHANGELOG.md b/packages/framework/data-object-base/CHANGELOG.md index 00095fca62a6..a6f4dce97171 100644 --- a/packages/framework/data-object-base/CHANGELOG.md +++ b/packages/framework/data-object-base/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/data-object-base +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/data-object-base/package.json b/packages/framework/data-object-base/package.json index 4ff0c356b3d8..49ba7e00cf16 100644 --- a/packages/framework/data-object-base/package.json +++ b/packages/framework/data-object-base/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/data-object-base", - "version": "2.5.0", + "version": "2.10.0", "description": "Data object base for synchronously and lazily loaded object scenarios", "homepage": "https://fluidframework.com", "repository": { @@ -78,9 +78,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/framework/dds-interceptions/CHANGELOG.md b/packages/framework/dds-interceptions/CHANGELOG.md index ff2c873f27f2..58471d153880 100644 --- a/packages/framework/dds-interceptions/CHANGELOG.md +++ b/packages/framework/dds-interceptions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/dds-interceptions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/dds-interceptions/package.json b/packages/framework/dds-interceptions/package.json index 0b837b98860b..5a150b1a84ac 100644 --- a/packages/framework/dds-interceptions/package.json +++ b/packages/framework/dds-interceptions/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/dds-interceptions", - "version": "2.5.0", + "version": "2.10.0", "description": "Distributed Data Structures that support an interception callback", "homepage": "https://fluidframework.com", "repository": { @@ -96,9 +96,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/packages/framework/fluid-framework/CHANGELOG.md b/packages/framework/fluid-framework/CHANGELOG.md index 4e191703fe54..1a4ae74cca21 100644 --- a/packages/framework/fluid-framework/CHANGELOG.md +++ b/packages/framework/fluid-framework/CHANGELOG.md @@ -1,5 +1,306 @@ # fluid-framework +## 2.5.0 + +### Minor Changes + +- ✨ New! Alpha APIs for tree data import and export ([#22566](https://github.com/microsoft/FluidFramework/pull/22566)) [18a23e8816](https://github.com/microsoft/FluidFramework/commit/18a23e8816467f2ed0c9d6d8637b70d99aa48b7a) + + A collection of new `@alpha` APIs for importing and exporting tree content and schema from SharedTrees has been added to `TreeAlpha`. + These include import and export APIs for `VerboseTree`, `ConciseTree` and compressed tree formats. + + `TreeAlpha.create` is also added to allow constructing trees with a more general API instead of having to use the schema constructor directly (since that doesn't handle polymorphic roots, or non-schema aware code). + + The function `independentInitializedView` has been added to provide a way to combine data from the existing `extractPersistedSchema` and new `TreeAlpha.exportCompressed` back into a `TreeView` in a way which can support safely importing data which could have been exported with a different schema. + This allows replicating the schema evolution process for Fluid documents stored in a service, but entirely locally without involving any collaboration services. + `independentView` has also been added, which is similar but handles the case of creating a new view without an existing schema or tree. + + Together these APIs address several use-cases: + + 1. Using SharedTree as an in-memory non-collaborative datastore. + 2. Importing and exporting data from a SharedTree to and from other services or storage locations (such as locally saved files). + 3. Testing various scenarios without relying on a service. + 4. Using SharedTree libraries for just the schema system and encode/decode support. + +- Compilation no longer fails when building with TypeScript's libCheck option ([#22923](https://github.com/microsoft/FluidFramework/pull/22923)) [a1b4cdd45e](https://github.com/microsoft/FluidFramework/commit/a1b4cdd45ee9812e2598ab8d2854333d26a06eb4) + + When compiling code using Fluid Framework with TypeScript's `libCheck` (meaning without [skipLibCheck](https://www.typescriptlang.org/tsconfig/#skipLibCheck)), two compile errors can be encountered: + + ``` + > tsc + + node_modules/@fluidframework/merge-tree/lib/client.d.ts:124:18 - error TS2368: Type parameter name cannot be 'undefined'. + + 124 walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; + ~~~~~~~~~ + + node_modules/@fluidframework/tree/lib/util/utils.d.ts:5:29 - error TS7016: Could not find a declaration file for module '@ungap/structured-clone'. 'node_modules/@ungap/structured-clone/esm/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/ungap__structured-clone` if it exists or add a new declaration (.d.ts) file containing `declare module '@ungap/structured-clone';` + + 5 import structuredClone from "@ungap/structured-clone"; + ~~~~~~~~~~~~~~~~~~~~~~~~~ + ``` + + The first error impacts projects using TypeScript 5.5 or greater and either of the `fluid-framework` or `@fluidframework/merge-tree` packages. + The second error impacts projects using the `noImplicitAny` tsconfig setting and the `fluid-framework` or `@fluidframework/tree` packages. + + Both errors have been fixed. + + This should allow `libCheck` to be reenabled in any impacted projects. + +- A `.schema` member has been added to the alpha enum schema APIs ([#22874](https://github.com/microsoft/FluidFramework/pull/22874)) [645b9ed695](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + + The return value from `@alpha` APIs `enumFromStrings` and `adaptEnum` now has a property named `schema` which can be used to include it in a parent schema. + This replaces the use of `typedObjectValues` which has been removed. + + Use of these APIs now look like: + + ```typescript + const schemaFactory = new SchemaFactory("com.myApp"); + const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); + type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; + class Parent extends schemaFactory.object("Parent", { mode: Mode.schema }) {} + ``` + + Previously, the last two lines would have been: + + ```typescript + type Mode = NodeFromSchema<(typeof Mode)[keyof typeof Mode]>; // This no longer works + class Parent extends schemaFactory.object("Parent", { mode: typedObjectValues(Mode) }) {} // This no longer works + ``` + +- TreeNodeSchemaClass now specifies its TNode as TreeNode ([#22938](https://github.com/microsoft/FluidFramework/pull/22938)) [b669a6efdb](https://github.com/microsoft/FluidFramework/commit/b669a6efdba685c71897cade4f907304f1a73910) + + `TreeNodeSchemaClass`'s `TNode` parameter was formerly `unknown` and has been improved to be the more specific `TreeNode | TreeLeafValue`. + This change further narrows this to `TreeNode`. + + `TreeNodeSchema`, which is more commonly used, still permits `TNode` of `TreeNode | TreeLeafValue`, so this change should have little impact on most code, but in some edge cases it can result in slightly more specific typing. + +- Array and Map nodes can now be explicitly constructed with undefined or no argument ([#22946](https://github.com/microsoft/FluidFramework/pull/22946)) [176335ce88](https://github.com/microsoft/FluidFramework/commit/176335ce88d005159819c559b445a1655ec429d5) + + The input parameter to the constructor and `create` methods of Array and Map nodes is now optional. When the optional parameter is omitted, an empty map or array will be created. + + #### Examples + + ```typescript + class Schema extends schemaFactory.array("x", schemaFactory.number) {} + + // Existing support + const _fromIterable: Schema = new Schema([]); + + // New + const _fromUndefined: Schema = new Schema(undefined); + const _fromNothing: Schema = new Schema(); + ``` + + ```typescript + class Schema extends schemaFactory.map("x", schemaFactory.number) {} + + // Existing support + const _fromIterable: Schema = new Schema([]); + const _fromObject: Schema = new Schema({}); + + // New + const _fromUndefined: Schema = new Schema(undefined); + const _fromNothing: Schema = new Schema(); + ``` + + ```typescript + const Schema = schemaFactory.array(schemaFactory.number); + type Schema = NodeFromSchema; + + // Existing support + const _fromIterable: Schema = Schema.create([]); + + // New + const _fromUndefined: Schema = Schema.create(undefined); + const _fromNothing: Schema = Schema.create(); + ``` + + ```typescript + const Schema = schemaFactory.map(schemaFactory.number); + type Schema = NodeFromSchema; + // Existing support + const _fromIterable: Schema = Schema.create([]); + const _fromObject: Schema = Schema.create({}); + + // New + const _fromUndefined: Schema = Schema.create(undefined); + const _fromNothing: Schema = Schema.create(); + ``` + +- Typing has been improved when an exact TypeScript type for a schema is not provided ([#22763](https://github.com/microsoft/FluidFramework/pull/22763)) [05197d6d3f](https://github.com/microsoft/FluidFramework/commit/05197d6d3f0189ecd61fd74ec55f6836e6797249) + + The Tree APIs are designed to be used in a strongly typed way, with the full TypeScript type for the schema always being provided. + Due to limitations of the TypeScript language, there was no practical way to prevent less descriptive types, like `TreeNodeSchema` or `ImplicitFieldSchema`, from being used where the type of a specific schema was intended. + Code which does this will encounter several issues with tree APIs, and this change fixes some of those issues. + This change mainly fixes that `NodeFromSchema` used to return `unknown` and now returns `TreeNode | TreeLeafValue`. + + This change by itself seems mostly harmless, as it just improves the precision of the typing in this one edge case. + Unfortunately, there are other typing bugs which complicate the situation, causing APIs for inserting data into the tree to also behave poorly when given non-specific types like `TreeNodeSchema`. + These APIs include cases like `TreeView.initialize`. + + This incorrectly allowed some usage like taking a type-erased schema and initial tree pair, creating a view of type `TreeView`, then initializing it. + With the typing being partly fixed, some unsafe inputs are still allowed when trying to initialize such a view, but some are now prevented. + + This use-case of modifying trees in code not that is not strongly typed by the exact schema was not intended to be supported. + Despite this, it did mostly work in some cases, and has some real use-cases (like tests looping over test data consisting of pairs of schema and initial trees). + To help mitigate the impact of this change, some experimental `@alpha` APIs have been introduced to help address these previously unsupported but somewhat working use-cases. + + Before this change: + + ```typescript + import { TinyliciousClient } from "@fluidframework/tinylicious-client"; + import { + SchemaFactory, + SharedTree, + TreeViewConfiguration, + type TreeNodeSchema, + } from "fluid-framework"; + + // Create a ITree instance + const tinyliciousClient = new TinyliciousClient(); + const { container } = await tinyliciousClient.createContainer({ initialObjects: {} }, "2"); + const tree = await container.create(SharedTree); + + const schemaFactory = new SchemaFactory("demo"); + + // Bad: This loses the schema aware type information. `: TreeNodeSchema` should be omitted to preserve strong typing. + const schema: TreeNodeSchema = schemaFactory.array(schemaFactory.number); + const config = new TreeViewConfiguration({ schema }); + + // This view is typed as `TreeView`, which does not work well since it's missing the actual schema type information. + const view = tree.viewWith(config); + // Root is typed as `unknown` allowing invalid assignment operations. + view.root = "invalid"; + view.root = {}; + // Since all assignments are allowed, valid ones still work: + view.root = []; + ``` + + After this change: + + ```typescript + // Root is now typed as `TreeNode | TreeLeafValue`, still allowing some invalid assignment operations. + // In the future this should be prevented as well, since the type of the setter in this case should be `never`. + view.root = "invalid"; + // This no longer compiles: + view.root = {}; + // This also no longer compiles despite being valid at runtime: + view.root = []; + ``` + + For code that wants to continue using an unsafe API, which can result in runtime errors if the data does not follow the schema, a new alternative has been added to address this use-case. A special type `UnsafeUnknownSchema` can now be used to opt into allowing all valid trees to be provided. + Note that this leaves ensuring the data is in schema up to the user. + For now these adjusted APIs can be accessed by casting the view to `TreeViewAlpha`. + If stabilized, this option will be added to `TreeView` directly. + + ```typescript + const viewAlpha = view as TreeViewAlpha; + viewAlpha.initialize([]); + viewAlpha.root = []; + ``` + + Additionally, this seems to have negatively impacted co-recursive schema which declare a co-recursive array as the first schema in the co-recursive cycle. + Like the TypeScript language our schema system is built on, we don't guarantee exactly which recursive type will compile, but will do our best to ensure useful recursive schema can be created easily. + In this case a slight change may be required to some recursive schema to get them to compile again: + + For example this schema used to compile: + + ```typescript + class A extends sf.arrayRecursive("A", [() => B]) {} + { + type _check = ValidateRecursiveSchema; + } + // Used to work, but breaks in this update. + class B extends sf.object("B", { x: A }) {} + ``` + + But now you must use the recursive functions like `objectRecursive` for types which are co-recursive with an array in some cases. + In our example, it can be fixed as follows: + + ```typescript + class A extends sf.arrayRecursive("A", [() => B]) {} + { + type _check = ValidateRecursiveSchema; + } + // Fixed corecursive type, using "Recursive" method variant to declare schema. + class B extends sf.objectRecursive("B", { x: A }) {} + { + type _check = ValidateRecursiveSchema; + } + ``` + + Note: while the following pattern may still compile, we recommend using the previous pattern instead since the one below may break in the future. + + ```typescript + class B extends sf.objectRecursive("B", { x: [() => A] }) {} + { + type _check = ValidateRecursiveSchema; + } + // Works, for now, but not recommended. + class A extends sf.array("A", B) {} + ``` + +- The strictness of input tree types when inexact schemas are provided has been improved ([#22874](https://github.com/microsoft/FluidFramework/pull/22874)) [645b9ed695](https://github.com/microsoft/FluidFramework/commit/645b9ed69540338843ad14f1144ff4d1f80d6f09) + + Consider the following code where the type of the schema is not exactly specified: + + ```typescript + const schemaFactory = new SchemaFactory("com.myApp"); + class A extends schemaFactory.object("A", {}) {} + class B extends schemaFactory.array("B", schemaFactory.number) {} + + // Gives imprecise type (typeof A | typeof B)[]. The desired precise type here is [typeof A, typeof B]. + const schema = [A, B]; + + const config = new TreeViewConfiguration({ schema }); + const view = sharedTree.viewWith(config); + + // Does not compile since setter for root is typed `never` due to imprecise schema. + view.root = []; + ``` + + The assignment of `view.root` is disallowed since a schema with type `(typeof A | typeof B)[]` could be any of: + + ```typescript + const schema: (typeof A | typeof B)[] = [A]; + ``` + + ```typescript + const schema: (typeof A | typeof B)[] = [B]; + ``` + + ```typescript + const schema: (typeof A | typeof B)[] = [A, B]; + ``` + + The attempted assignment is not compatible with all of these (specifically it is incompatible with the first one) so performing this assignment could make the tree out of schema and is thus disallowed. + + To avoid this ambiguity and capture the precise type of `[typeof A, typeof B]`, use one of the following patterns: + + ```typescript + const schema = [A, B] as const; + const config = new TreeViewConfiguration({ schema }); + ``` + + ```typescript + const config = new TreeViewConfiguration({ schema: [A, B] }); + ``` + + To help update existing code which accidentally depended on this bug, an `@alpha` API `unsafeArrayToTuple` has been added. + Many usages of this API will produce incorrectly typed outputs. + However, when given `AllowedTypes` arrays which should not contain any unions, but that were accidentally flattened to a single union, it can fix them: + + ```typescript + // Gives imprecise type (typeof A | typeof B)[] + const schemaBad = [A, B]; + // Fixes the type to be [typeof A, typeof B] + const schema = unsafeArrayToTuple(schemaBad); + + const config = new TreeViewConfiguration({ schema }); + ``` + ## 2.4.0 ### Minor Changes diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md index b3abe4bfc5d7..9401b0d79a74 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md @@ -33,6 +33,9 @@ type ApplyKindInput(view: TreeView): TreeViewAlpha; + // @public export enum AttachState { Attached = "Attached", @@ -40,6 +43,14 @@ export enum AttachState { Detached = "Detached" } +// @alpha @sealed +export interface BranchableTree extends ViewableTree { + branch(): TreeBranchFork; + merge(branch: TreeBranchFork): void; + merge(branch: TreeBranchFork, disposeMerged: boolean): void; + rebase(branch: TreeBranchFork): void; +} + // @public export enum CommitKind { Default = 0, @@ -56,6 +67,11 @@ export interface CommitMetadata { // @alpha export function comparePersistedSchema(persisted: JsonCompatible, view: JsonCompatible, options: ICodecOptions, canInitialize: boolean): SchemaCompatibilityStatus; +// @alpha +export type ConciseTree = Exclude | THandle | ConciseTree[] | { + [key: string]: ConciseTree; +}; + // @alpha export function configuredSharedTree(options: SharedTreeOptions): SharedObjectKind; @@ -91,6 +107,12 @@ export interface ContainerSchema { interface DefaultProvider extends ErasedType<"@fluidframework/tree.FieldProvider"> { } +// @alpha +export interface EncodeOptions { + readonly useStoredKeys?: boolean; + valueConverter(data: IFluidHandle): TCustom; +} + // @alpha export function enumFromStrings(factory: SchemaFactory, members: Members): ((value: TValue) => TreeNode & { readonly value: TValue; @@ -180,6 +202,14 @@ type FlexList = readonly LazyItem[]; // @public type FlexListToUnion = ExtractItemType; +// @alpha +export enum FluidClientVersion { + v2_0 = "v2_0", + v2_1 = "v2_1", + v2_2 = "v2_2", + v2_3 = "v2_3" +} + // @public export type FluidObject = { [P in FluidObjectProviderKeys]?: T[P]; @@ -200,11 +230,11 @@ export enum ForestType { Reference = 0 } -// @alpha -export function getBranch(tree: ITree): TreeBranch; +// @alpha @deprecated +export function getBranch(tree: ITree): BranchableTree; -// @alpha -export function getBranch(view: TreeViewAlpha): TreeBranch; +// @alpha @deprecated +export function getBranch(view: TreeViewAlpha): BranchableTree; // @alpha export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema; @@ -473,6 +503,14 @@ export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema; // @public export type ImplicitFieldSchema = FieldSchema | ImplicitAllowedTypes; +// @alpha +export function independentInitializedView(config: TreeViewConfiguration, options: ForestOptions & ICodecOptions, content: ViewContent): TreeViewAlpha; + +// @alpha +export function independentView(config: TreeViewConfiguration, options: ForestOptions & { + idCompressor?: IIdCompressor_2 | undefined; +}): TreeViewAlpha; + // @public export type InitialObjects = { [K in keyof T["initialObjects"]]: T["initialObjects"][K] extends SharedObjectKind ? TChannel : never; @@ -553,7 +591,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -646,11 +683,11 @@ export interface JsonArrayNodeSchema extends JsonNodeSchemaBase = string | number | boolean | null | JsonCompatible[] | JsonCompatibleObject | TExtra; // @alpha -export type JsonCompatibleObject = { - [P in string]?: JsonCompatible; +export type JsonCompatibleObject = { + [P in string]?: JsonCompatible; }; // @alpha @sealed @@ -720,6 +757,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -792,11 +830,17 @@ type ObjectFromSchemaRecordUnsafe void; +// @alpha +export interface ParseOptions { + readonly useStoredKeys?: boolean; + valueConverter(data: VerboseTree): TreeLeafValue | VerboseTreeNode; +} + // @alpha export type PopUnion void : never>> = AsOverloadedFunction extends (a: infer First) => void ? First : never; // @alpha -export type ReadableField = TSchema extends ImplicitFieldSchema ? TreeFieldFromImplicitField : TreeLeafValue | TreeNode; +export type ReadableField = TreeFieldFromImplicitField>; // @public @sealed export interface ReadonlyArrayNode extends ReadonlyArray, Awaited> { @@ -887,7 +931,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -903,6 +948,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -912,7 +959,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -977,6 +1023,26 @@ export type TransformedEvent = (event: E, listener: ( // @public export const Tree: TreeApi; +// @alpha @sealed +export const TreeAlpha: { + branch(node: TreeNode): TreeBranch | undefined; + create(schema: UnsafeUnknownSchema extends TSchema ? ImplicitFieldSchema : TSchema & ImplicitFieldSchema, data: InsertableField): Unhydrated : TreeNode | TreeLeafValue | undefined>; + importConcise(schema: UnsafeUnknownSchema extends TSchema ? ImplicitFieldSchema : TSchema & ImplicitFieldSchema, data: ConciseTree | undefined): Unhydrated : TreeNode | TreeLeafValue | undefined>; + importVerbose(schema: TSchema, data: VerboseTree | undefined, options?: Partial>): Unhydrated>; + importVerbose(schema: TSchema, data: VerboseTree | undefined, options: ParseOptions): Unhydrated>; + exportConcise(node: TreeNode | TreeLeafValue, options?: Partial>): ConciseTree; + exportConcise(node: TreeNode | TreeLeafValue, options: EncodeOptions): ConciseTree; + exportVerbose(node: TreeNode | TreeLeafValue, options?: Partial>): VerboseTree; + exportVerbose(node: TreeNode | TreeLeafValue, options: EncodeOptions): VerboseTree; + exportCompressed(tree: TreeNode | TreeLeafValue, options: { + oldestCompatibleClient: FluidClientVersion; + idCompressor?: IIdCompressor; + }): JsonCompatible; + importCompressed(schema: TSchema, compressedData: JsonCompatible, options: { + idCompressor?: IIdCompressor; + } & ICodecOptions): Unhydrated>; +}; + // @public @sealed interface TreeApi extends TreeNodeApi { contains(node: TreeNode, other: TreeNode): boolean; @@ -984,16 +1050,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -1014,8 +1071,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @beta @sealed @@ -1025,16 +1087,25 @@ export const TreeBeta: { }; // @alpha @sealed -export interface TreeBranch extends ViewableTree { - branch(): TreeBranchFork; - merge(branch: TreeBranchFork): void; - merge(branch: TreeBranchFork, disposeMerged: boolean): void; - rebase(branch: TreeBranchFork): void; +export interface TreeBranch extends IDisposable { + dispose(error?: Error): void; + readonly events: Listenable; + fork(): TreeBranch; + hasRootSchema(schema: TSchema): this is TreeViewAlpha; + merge(branch: TreeBranch, disposeMerged?: boolean): void; + rebaseOnto(branch: TreeBranch): void; } // @alpha @sealed -export interface TreeBranchFork extends TreeBranch, IDisposable { - rebaseOnto(branch: TreeBranch): void; +export interface TreeBranchEvents { + changed(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void; + schemaChanged(): void; +} + +// @alpha @sealed +export interface TreeBranchFork extends BranchableTree, IDisposable { + rebaseOnto(branch: BranchableTree): void; } // @public @sealed @@ -1175,8 +1246,12 @@ export interface TreeView extends ID upgradeSchema(): void; } -// @alpha -export interface TreeViewAlpha extends Omit>, "root" | "initialize"> { +// @alpha @sealed +export interface TreeViewAlpha extends Omit>, "root" | "initialize">, TreeBranch { + // (undocumented) + readonly events: Listenable; + // (undocumented) + fork(): ReturnType & TreeViewAlpha; // (undocumented) initialize(content: InsertableField): void; // (undocumented) @@ -1239,11 +1314,29 @@ export type ValidateRecursiveSchema> = true; +// @alpha +export type VerboseTree = VerboseTreeNode | Exclude | THandle; + +// @alpha +export interface VerboseTreeNode { + fields: VerboseTree[] | { + [key: string]: VerboseTree; + }; + type: string; +} + // @public @sealed export interface ViewableTree { viewWith(config: TreeViewConfiguration): TreeView; } +// @alpha +export interface ViewContent { + readonly idCompressor: IIdCompressor_2; + readonly schema: JsonCompatible; + readonly tree: JsonCompatible; +} + // @public @sealed export interface WithType { // @deprecated diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md index 6e3c5d7b9da9..3cfe7df79d96 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.beta.api.md @@ -474,7 +474,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -563,6 +562,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -716,7 +716,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -732,6 +733,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -741,7 +744,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -784,16 +786,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -814,8 +807,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @beta @sealed diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md index f1c1af011c1b..f4fdf9a7bbed 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.alpha.api.md @@ -576,7 +576,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -668,12 +667,8 @@ export interface ISequenceDeltaRange = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -1017,7 +1013,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -1033,6 +1030,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -1042,7 +1041,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -1051,72 +1049,55 @@ export class SchemaFactory = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`; // @alpha -export class SequenceDeltaEvent extends SequenceEvent { - // @deprecated - constructor(opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, mergeTreeClient: Client); +export interface SequenceDeltaEvent extends SequenceEvent { readonly isLocal: boolean; // (undocumented) readonly opArgs: IMergeTreeDeltaOpArgs; } // @alpha -export abstract class SequenceEvent { - // @deprecated - constructor( - deltaArgs: IMergeTreeDeltaCallbackArgs, mergeTreeClient: Client); - get clientId(): string | undefined; +export interface SequenceEvent { + readonly clientId: string | undefined; + // (undocumented) readonly deltaArgs: IMergeTreeDeltaCallbackArgs; // (undocumented) readonly deltaOperation: TOperation; - get first(): Readonly>; - get last(): Readonly>; - get ranges(): readonly Readonly>[]; + readonly first: Readonly>; + readonly last: Readonly>; + readonly ranges: readonly Readonly>[]; } // @alpha -export class SequenceInterval implements ISerializableInterval { - // @deprecated - constructor(client: Client, - start: LocalReferencePosition, - end: LocalReferencePosition, intervalType: IntervalType, props?: PropertySet, startSide?: Side, endSide?: Side); +export interface SequenceInterval extends ISerializableInterval { addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void; // (undocumented) - addProperties(newProps: PropertySet, collab?: boolean, seq?: number): PropertySet | undefined; - // (undocumented) clone(): SequenceInterval; compare(b: SequenceInterval): number; compareEnd(b: SequenceInterval): number; compareStart(b: SequenceInterval): number; - end: LocalReferencePosition; + readonly end: LocalReferencePosition; // (undocumented) readonly endSide: Side; - getIntervalId(): string; // (undocumented) - intervalType: IntervalType; - modify(label: string, start: SequencePlace | undefined, end: SequencePlace | undefined, op?: ISequencedDocumentMessage, localSeq?: number, useNewSlidingBehavior?: boolean): SequenceInterval; + readonly intervalType: IntervalType; + modify(label: string, start: SequencePlace | undefined, end: SequencePlace | undefined, op?: ISequencedDocumentMessage, localSeq?: number, useNewSlidingBehavior?: boolean): SequenceInterval | undefined; // (undocumented) overlaps(b: SequenceInterval): boolean; // (undocumented) overlapsPos(bstart: number, bend: number): boolean; - properties: PropertySet; - // (undocumented) - propertyManager: PropertiesManager; removePositionChangeListeners(): void; // (undocumented) - serialize(): ISerializedInterval; - start: LocalReferencePosition; + readonly start: LocalReferencePosition; // (undocumented) readonly startSide: Side; // (undocumented) - get stickiness(): IntervalStickiness; + readonly stickiness: IntervalStickiness; union(b: SequenceInterval): SequenceInterval; } // @alpha -export class SequenceMaintenanceEvent extends SequenceEvent { - // @deprecated - constructor( - opArgs: IMergeTreeDeltaOpArgs | undefined, deltaArgs: IMergeTreeMaintenanceCallbackArgs, mergeTreeClient: Client); +export interface SequenceMaintenanceEvent extends SequenceEvent { + // (undocumented) readonly opArgs: IMergeTreeDeltaOpArgs | undefined; } @@ -1180,16 +1161,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -1210,8 +1182,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md index 6f2cc94232e2..36e092feaeef 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.legacy.public.api.md @@ -504,7 +504,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -599,6 +598,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -747,7 +747,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -763,6 +764,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -772,7 +775,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -819,16 +821,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -849,8 +842,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md index 8492627b2989..7e6c11f20de8 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.public.api.md @@ -474,7 +474,6 @@ declare namespace InternalTypes { ApplyKindInput, NodeBuilderData, FieldHasDefault, - TreeArrayNodeBase, ScopedSchemaName, DefaultProvider, typeNameSymbol, @@ -563,6 +562,7 @@ export type LazyItem = Item | (() => Item); // @public @sealed export interface Listenable { + off>(eventName: K, listener: TListeners[K]): void; on>(eventName: K, listener: TListeners[K]): Off; } @@ -711,7 +711,8 @@ export interface SchemaCompatibilityStatus { // @public @sealed export class SchemaFactory { - constructor(scope: TScope); + constructor( + scope: TScope); array(allowedTypes: T): TreeNodeSchemaNonClass`>, NodeKind.Array, TreeArrayNode & WithType`>, NodeKind.Array>, Iterable>, true, T, undefined>; array(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNode & WithType, NodeKind.Array>, Iterable>, true, T, undefined>; arrayRecursive>(name: Name, allowedTypes: T): TreeNodeSchemaClass, NodeKind.Array, TreeArrayNodeUnsafe & WithType, NodeKind.Array, unknown>, { @@ -727,6 +728,8 @@ export class SchemaFactory ]>; + } | { + readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe; }, false, T, undefined>; readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; readonly number: TreeNodeSchemaNonClass<"com.fluidframework.leaf.number", NodeKind.Leaf, number, number, true, unknown, never>; @@ -736,7 +739,6 @@ export class SchemaFactory>(t: T, props?: Omit): FieldSchemaUnsafe; required(t: T, props?: Omit, "defaultProvider">): FieldSchema; requiredRecursive>(t: T, props?: Omit): FieldSchemaUnsafe; - // (undocumented) readonly scope: TScope; readonly string: TreeNodeSchemaNonClass<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string, true, unknown, never>; } @@ -779,16 +781,7 @@ interface TreeApi extends TreeNodeApi { } // @public @sealed -export interface TreeArrayNode extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypes> { -} - -// @public -export const TreeArrayNode: { - readonly spread: (content: Iterable) => IterableTreeArrayContent; -}; - -// @public @sealed -interface TreeArrayNodeBase extends ReadonlyArrayNode { +export interface TreeArrayNode = ImplicitAllowedTypes, out T = [TAllowedTypes] extends [ImplicitAllowedTypes] ? TreeNodeFromImplicitAllowedTypes : TreeNodeFromImplicitAllowedTypes, in TNew = [TAllowedTypes] extends [ImplicitAllowedTypes] ? InsertableTreeNodeFromImplicitAllowedTypes : InsertableTreeNodeFromImplicitAllowedTypes, in TMoveFrom = ReadonlyArrayNode> extends ReadonlyArrayNode { insertAt(index: number, ...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtEnd(...value: readonly (TNew | IterableTreeArrayContent)[]): void; insertAtStart(...value: readonly (TNew | IterableTreeArrayContent)[]): void; @@ -809,8 +802,13 @@ interface TreeArrayNodeBase ex values(): IterableIterator; } +// @public +export const TreeArrayNode: { + readonly spread: (content: Iterable) => IterableTreeArrayContent; +}; + // @public @sealed -export interface TreeArrayNodeUnsafe> extends TreeArrayNodeBase, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { +export interface TreeArrayNodeUnsafe> extends TreeArrayNode, InsertableTreeNodeFromImplicitAllowedTypesUnsafe> { } // @public @sealed diff --git a/packages/framework/fluid-framework/package.json b/packages/framework/fluid-framework/package.json index 7cb8afeb656a..e419107e6abd 100644 --- a/packages/framework/fluid-framework/package.json +++ b/packages/framework/fluid-framework/package.json @@ -1,6 +1,6 @@ { "name": "fluid-framework", - "version": "2.5.0", + "version": "2.10.0", "description": "The main entry point into Fluid Framework public packages", "homepage": "https://fluidframework.com", "repository": { @@ -113,9 +113,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/framework/fluid-framework/src/index.ts b/packages/framework/fluid-framework/src/index.ts index d93b8ce4c529..cfbcdee5b30d 100644 --- a/packages/framework/fluid-framework/src/index.ts +++ b/packages/framework/fluid-framework/src/index.ts @@ -152,15 +152,16 @@ export type { ISharedSegmentSequence, } from "@fluidframework/sequence/internal"; -export { +export type { IntervalType, SequenceDeltaEvent, SequenceEvent, SequenceInterval, SequenceMaintenanceEvent, - SharedString, } from "@fluidframework/sequence/internal"; +export { SharedString } from "@fluidframework/sequence/internal"; + export type { ISharedObject, ISharedObjectEvents, diff --git a/packages/framework/fluid-static/CHANGELOG.md b/packages/framework/fluid-static/CHANGELOG.md index 243eddd86f7c..c1af68c1773a 100644 --- a/packages/framework/fluid-static/CHANGELOG.md +++ b/packages/framework/fluid-static/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/fluid-static +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/fluid-static/package.json b/packages/framework/fluid-static/package.json index f7772e250756..0f418d03260d 100644 --- a/packages/framework/fluid-static/package.json +++ b/packages/framework/fluid-static/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/fluid-static", - "version": "2.5.0", + "version": "2.10.0", "description": "A tool to enable consumption of Fluid Data Objects without requiring custom container code.", "homepage": "https://fluidframework.com", "repository": { @@ -117,11 +117,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/fluid-static-previous": "npm:@fluidframework/fluid-static@~2.4.0", + "@fluidframework/fluid-static-previous": "npm:@fluidframework/fluid-static@2.5.0", "@fluidframework/map": "workspace:~", "@fluidframework/sequence": "workspace:~", "@microsoft/api-extractor": "7.47.8", @@ -140,7 +140,11 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IFluidContainerInternal": { + "backCompat": false + } + }, "entrypoint": "public" } } diff --git a/packages/framework/oldest-client-observer/CHANGELOG.md b/packages/framework/oldest-client-observer/CHANGELOG.md index 8b34acd90f27..45a79797c0b3 100644 --- a/packages/framework/oldest-client-observer/CHANGELOG.md +++ b/packages/framework/oldest-client-observer/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/oldest-client-observer +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/oldest-client-observer/package.json b/packages/framework/oldest-client-observer/package.json index 6535d8684f5b..76285f782ed2 100644 --- a/packages/framework/oldest-client-observer/package.json +++ b/packages/framework/oldest-client-observer/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/oldest-client-observer", - "version": "2.5.0", + "version": "2.10.0", "description": "Data object to determine if the local client is the oldest amongst connected clients", "homepage": "https://fluidframework.com", "repository": { @@ -117,9 +117,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-private/test-dds-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/framework/presence/.eslintrc.cjs b/packages/framework/presence/.eslintrc.cjs index d916e07a60c7..581c639418c6 100644 --- a/packages/framework/presence/.eslintrc.cjs +++ b/packages/framework/presence/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { // TODO: Reenable no-explicit-any once need with ValueDirectoryOrState is // understood. If `any` is still needed disable is on a per line basis. "@typescript-eslint/no-explicit-any": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/framework/presence/CHANGELOG.md b/packages/framework/presence/CHANGELOG.md index ca0813e24fe1..4eb2ac493ec3 100644 --- a/packages/framework/presence/CHANGELOG.md +++ b/packages/framework/presence/CHANGELOG.md @@ -1,5 +1,15 @@ # @fluid-experimental/presence +## 2.5.0 + +### Minor Changes + +- ISessionClient now exposes connectivity information ([#22973](https://github.com/microsoft/FluidFramework/pull/22973)) [6096657620](https://github.com/microsoft/FluidFramework/commit/609665762050b5f3baf737d752fc87ef962b3a21) + + 1. `ISessionClient` has a new method, `getConnectionStatus()`, with two possible states: `Connected` and `Disconnected`. + 2. `ISessionClient`'s `connectionId()` member has been renamed to `getConnectionId()` for consistency. + 3. `IPresence` event `attendeeDisconnected` is now implemented. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/presence/api-report/presence.alpha.api.md b/packages/framework/presence/api-report/presence.alpha.api.md index 46d10df4ce81..6f610ab2920b 100644 --- a/packages/framework/presence/api-report/presence.alpha.api.md +++ b/packages/framework/presence/api-report/presence.alpha.api.md @@ -37,9 +37,8 @@ export interface IPresence { // @alpha @sealed export interface ISessionClient { - connectionId(): ClientConnectionId; - getStatus(): SessionClientStatus; - // (undocumented) + getConnectionId(): ClientConnectionId; + getConnectionStatus(): SessionClientStatus; readonly sessionId: SpecificSessionClientId; } @@ -188,24 +187,22 @@ export interface PresenceNotificationsSchema { } // @alpha @sealed -export type PresenceStates = PresenceStatesEntries & PresenceStatesMethods; +export interface PresenceStates { + add, TManager extends TManagerConstraints>(key: TKey, manager: InternalTypes.ManagerFactory): asserts this is PresenceStates>, TManagerConstraints>; + readonly props: PresenceStatesEntries; +} // @alpha @sealed -export type PresenceStatesEntries = { +export type PresenceStatesEntries = { /** * Registered `Value Manager`s */ - readonly [Key in Exclude>]: ReturnType["manager"] extends InternalTypes.StateValue ? TManager : never; + readonly [Key in keyof TSchema]: ReturnType["manager"] extends InternalTypes.StateValue ? TManager : never; }; // @alpha export type PresenceStatesEntry, TManager = unknown> = InternalTypes.ManagerFactory; -// @alpha @sealed -export interface PresenceStatesMethods { - add, TManager extends TManagerRestrictions>(key: TKey, manager: InternalTypes.ManagerFactory): asserts this is PresenceStates>>; -} - // @alpha export interface PresenceStatesSchema { // (undocumented) diff --git a/packages/framework/presence/package.json b/packages/framework/presence/package.json index 55b3cfe873a6..d71b3ebdc211 100644 --- a/packages/framework/presence/package.json +++ b/packages/framework/presence/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/presence", - "version": "2.5.0", + "version": "2.10.0", "description": "A component for lightweight data sharing within a single session", "homepage": "https://fluidframework.com", "repository": { @@ -134,9 +134,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/driver-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", diff --git a/packages/framework/presence/src/baseTypes.ts b/packages/framework/presence/src/baseTypes.ts index 3f2586f3d6af..6af16c338573 100644 --- a/packages/framework/presence/src/baseTypes.ts +++ b/packages/framework/presence/src/baseTypes.ts @@ -10,7 +10,7 @@ * Each client connection is given a unique identifier for the duration of the * connection. If a client disconnects and reconnects, it will be given a new * identifier. Prefer use of {@link ISessionClient} as a way to identify clients - * in a session. {@link ISessionClient.connectionId} will provide the current + * in a session. {@link ISessionClient.getConnectionId} will provide the current * connection identifier for a logical session client. * * @privateRemarks diff --git a/packages/framework/presence/src/index.ts b/packages/framework/presence/src/index.ts index 59dbe9fee352..7412ec04f414 100644 --- a/packages/framework/presence/src/index.ts +++ b/packages/framework/presence/src/index.ts @@ -33,16 +33,15 @@ export type { PresenceStates, PresenceStatesEntries, PresenceStatesEntry, - PresenceStatesMethods, PresenceStatesSchema, PresenceWorkspaceAddress, } from "./types.js"; -export type { - ClientSessionId, - IPresence, - ISessionClient, - PresenceEvents, +export { + type ClientSessionId, + type IPresence, + type ISessionClient, + type PresenceEvents, SessionClientStatus, } from "./presence.js"; diff --git a/packages/framework/presence/src/presence.ts b/packages/framework/presence/src/presence.ts index 7e89213bb26c..fb0fd8e63c53 100644 --- a/packages/framework/presence/src/presence.ts +++ b/packages/framework/presence/src/presence.ts @@ -24,7 +24,7 @@ import type { ISubscribable } from "@fluid-experimental/presence/internal/events * duration of the session. If a client disconnects and reconnects, it will * retain its identifier. Prefer use of {@link ISessionClient} as a way to * identify clients in a session. {@link ISessionClient.sessionId} will provide - * the session id. + * the session ID. * * @alpha */ @@ -36,12 +36,26 @@ export type ClientSessionId = SessionId & { readonly ClientSessionId: "ClientSes * @alpha */ export const SessionClientStatus = { + /** + * The session client is connected to the Fluid service. + */ Connected: "Connected", + + /** + * The session client is not connected to the Fluid service. + */ Disconnected: "Disconnected", } as const; /** - * Type for the connection status of the {@link ISessionClient}. + * Represents the connection status of an {@link ISessionClient}. + * + * This type can be either `'Connected'` or `'Disconnected'`, indicating whether + * the session client is currently connected to the Fluid service. + * + * When `'Disconnected'`: + * - State changes are kept locally and communicated to others upon reconnect. + * - Notification requests are discarded (silently). * * @alpha */ @@ -56,8 +70,8 @@ export type SessionClientStatus = * * `ISessionClient` should be used as key to distinguish between different * clients as they join, rejoin, and disconnect from a session. While a - * client's {@link ClientConnectionId} may change over time `ISessionClient` - * will be fixed. + * client's {@link ClientConnectionId} from {@link ISessionClient.getConnectionStatus} + * may change over time, `ISessionClient` will be fixed. * * @privateRemarks * As this is evolved, pay attention to how this relates to Audience, Service @@ -69,32 +83,35 @@ export type SessionClientStatus = export interface ISessionClient< SpecificSessionClientId extends ClientSessionId = ClientSessionId, > { + /** + * The session ID of the client that is stable over all connections. + */ readonly sessionId: SpecificSessionClientId; /** - * Get current client connection id. + * Get current client connection ID. * - * @returns Current client connection id. + * @returns Current client connection ID. * * @remarks - * Connection id will change on reconnect. + * Connection ID will change on reconnect. * - * If {@link ISessionClient.getStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection id. + * If {@link ISessionClient.getConnectionStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection ID. */ - connectionId(): ClientConnectionId; + getConnectionId(): ClientConnectionId; /** - * Get status of session client. + * Get connection status of session client. * - * @returns Status of session client. + * @returns Connection status of session client. * */ - getStatus(): SessionClientStatus; + getConnectionStatus(): SessionClientStatus; } /** * Utility type limiting to a specific session client. (A session client with - * a specific session id - not just any session id.) + * a specific session ID - not just any session ID.) * * @internal */ @@ -162,7 +179,7 @@ export interface IPresence { /** * Lookup a specific attendee in the session. * - * @param clientId - Client connection or session id + * @param clientId - Client connection or session ID */ getAttendee(clientId: ClientConnectionId | ClientSessionId): ISessionClient; diff --git a/packages/framework/presence/src/presenceManager.ts b/packages/framework/presence/src/presenceManager.ts index 4cd2938a2715..92bcb3eaa6b6 100644 --- a/packages/framework/presence/src/presenceManager.ts +++ b/packages/framework/presence/src/presenceManager.ts @@ -49,9 +49,9 @@ export type PresenceExtensionInterface = Required< */ export interface ClientConnectionManager { /** - * Remove the current client connection id from the corresponding disconnected attendee. + * Remove the current client connection ID from the corresponding disconnected attendee. * - * @param clientConnectionId - The current client connection id to be removed. + * @param clientConnectionId - The current client connection ID to be removed. */ removeClientConnectionId(clientConnectionId: ClientConnectionId): void; } diff --git a/packages/framework/presence/src/presenceStates.ts b/packages/framework/presence/src/presenceStates.ts index 51cfa784bc2a..a15c9236d1c7 100644 --- a/packages/framework/presence/src/presenceStates.ts +++ b/packages/framework/presence/src/presenceStates.ts @@ -11,7 +11,7 @@ import type { ClientRecord } from "./internalTypes.js"; import { brandedObjectEntries } from "./internalTypes.js"; import type { ClientSessionId, ISessionClient } from "./presence.js"; import { handleFromDatastore, type StateDatastore } from "./stateDatastore.js"; -import type { PresenceStates, PresenceStatesMethods, PresenceStatesSchema } from "./types.js"; +import type { PresenceStates, PresenceStatesSchema } from "./types.js"; import { unbrandIVM } from "./valueManager.js"; /** @@ -196,13 +196,14 @@ export function mergeUntrackedDatastore( class PresenceStatesImpl implements PresenceStatesInternal, - PresenceStatesMethods, + PresenceStates, StateDatastore< keyof TSchema & string, MapSchemaElement > { - public readonly nodes: MapEntries; + private readonly nodes: MapEntries; + public readonly props: PresenceStates["props"]; public constructor( private readonly runtime: PresenceRuntime, @@ -235,6 +236,11 @@ class PresenceStatesImpl }, ); this.nodes = initial.nodes; + // props is the public view of nodes that limits the entries types to + // the public interface of the value manager with an additional type + // filter that beguiles the type system. So just reinterpret cast. + this.props = this.nodes as unknown as PresenceStates["props"]; + if (anyInitialValues) { this.runtime.localUpdate(initial.newValues, false); } @@ -344,29 +350,10 @@ export function createPresenceStates( datastore: ValueElementMap, initialContent: TSchema, ): { public: PresenceStates; internal: PresenceStatesInternal } { - const impl = new PresenceStatesImpl(runtime, datastore, initialContent); - - // Capture the top level "public" map. Both the map implementation and - // the wrapper object reference this object. - const nodes = impl.nodes; - - // Create a wrapper object that has just the public interface methods and nothing more. - const wrapper = { - add: impl.add.bind(impl), - }; + const impl = new PresenceStatesImpl(runtime, datastore, initialContent); return { - public: new Proxy(wrapper as PresenceStates, { - get(target, p, receiver): unknown { - if (typeof p === "string") { - return target[p] ?? nodes[p]; - } - return Reflect.get(target, p, receiver); - }, - set(_target, _p, _newValue, _receiver): false { - return false; - }, - }), + public: impl, internal: impl, }; } diff --git a/packages/framework/presence/src/systemWorkspace.ts b/packages/framework/presence/src/systemWorkspace.ts index 32d63aea9d92..01a693e81cad 100644 --- a/packages/framework/presence/src/systemWorkspace.ts +++ b/packages/framework/presence/src/systemWorkspace.ts @@ -7,13 +7,13 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { ClientConnectionId } from "./baseTypes.js"; import type { InternalTypes } from "./exposedInternalTypes.js"; -import { - SessionClientStatus, - type ClientSessionId, - type IPresence, - type ISessionClient, - type PresenceEvents, +import type { + ClientSessionId, + IPresence, + ISessionClient, + PresenceEvents, } from "./presence.js"; +import { SessionClientStatus } from "./presence.js"; import type { PresenceStatesInternal } from "./presenceStates.js"; import type { PresenceStates, PresenceStatesSchema } from "./types.js"; @@ -30,20 +30,49 @@ export interface SystemWorkspaceDatastore { }; } -/** - * There is no implementation class for this interface. - * It is a simple structure. Most complicated aspect is that - * `connectionId()` member is replaced with a new - * function when a more recent connection is added. - * - * See {@link SystemWorkspaceImpl.ensureAttendee}. - */ -interface SessionClient extends ISessionClient { +class SessionClient implements ISessionClient { /** * Order is used to track the most recent client connection * during a session. */ - order: number; + public order: number = 0; + + private connectionStatus: SessionClientStatus; + + public constructor( + public readonly sessionId: ClientSessionId, + private connectionId: ClientConnectionId | undefined = undefined, + ) { + this.connectionStatus = + connectionId === undefined + ? SessionClientStatus.Disconnected + : SessionClientStatus.Connected; + } + + public getConnectionId(): ClientConnectionId { + if (this.connectionId === undefined) { + throw new Error("Client has never been connected"); + } + return this.connectionId; + } + + public getConnectionStatus(): SessionClientStatus { + return this.connectionStatus; + } + + public setConnectionId( + connectionId: ClientConnectionId, + updateStatus: boolean = true, + ): void { + this.connectionId = connectionId; + if (updateStatus) { + this.connectionStatus = SessionClientStatus.Connected; + } + } + + public setDisconnected(): void { + this.connectionStatus = SessionClientStatus.Disconnected; + } } /** @@ -56,14 +85,14 @@ export interface SystemWorkspace /** * Must be called when the current client acquires a new connection. * - * @param clientConnectionId - The new client connection id. + * @param clientConnectionId - The new client connection ID. */ onConnectionAdded(clientConnectionId: ClientConnectionId): void; /** - * Removes the client connection id from the system workspace. + * Removes the client connection ID from the system workspace. * - * @param clientConnectionId - The client connection id to remove. + * @param clientConnectionId - The client connection ID to remove. */ removeClientConnectionId(clientConnectionId: ClientConnectionId): void; } @@ -75,25 +104,18 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace { * session. The map covers entries for both session ids and connection * ids, which are never expected to collide, but if they did for same * client that would be fine. - * An entry is for session id if the value's `sessionId` matches the key. + * An entry is for session ID if the value's `sessionId` matches the key. */ private readonly attendees = new Map(); public constructor( clientSessionId: ClientSessionId, private readonly datastore: SystemWorkspaceDatastore, - public readonly events: IEmitter< + private readonly events: IEmitter< Pick >, ) { - this.selfAttendee = { - sessionId: clientSessionId, - order: 0, - connectionId: () => { - throw new Error("Client has never been connected"); - }, - getStatus: () => SessionClientStatus.Disconnected, - }; + this.selfAttendee = new SessionClient(clientSessionId); this.attendees.set(clientSessionId, this.selfAttendee); } @@ -150,8 +172,7 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace { value: this.selfAttendee.sessionId, }; - this.selfAttendee.connectionId = () => clientConnectionId; - this.selfAttendee.getStatus = () => SessionClientStatus.Connected; + this.selfAttendee.setConnectionId(clientConnectionId); this.attendees.set(clientConnectionId, this.selfAttendee); } @@ -161,11 +182,11 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace { return; } - // If the last known connectionID is different from the connection id being removed, the attendee has reconnected, + // If the last known connectionID is different from the connection ID being removed, the attendee has reconnected, // therefore we should not change the attendee connection status or emit a disconnect event. - const attendeeReconnected = attendee.connectionId() !== clientConnectionId; + const attendeeReconnected = attendee.getConnectionId() !== clientConnectionId; if (!attendeeReconnected) { - attendee.getStatus = () => SessionClientStatus.Disconnected; + attendee.setDisconnected(); this.events.emit("attendeeDisconnected", attendee); } } @@ -192,36 +213,33 @@ class SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace { } /** - * Make sure the given client session and connection id pair are represented + * Make sure the given client session and connection ID pair are represented * in the attendee map. If not present, SessionClient is created and added - * to map. If present, make sure the current connection id is updated. + * to map. If present, make sure the current connection ID is updated. */ private ensureAttendee( clientSessionId: ClientSessionId, clientConnectionId: ClientConnectionId, order: number, ): { attendee: SessionClient; isNew: boolean } { - const connectionId = (): ClientConnectionId => clientConnectionId; let attendee = this.attendees.get(clientSessionId); let isNew = false; + // TODO #22616: Check for a current connection to determine best status. + // For now, always leave existing state as was last determined and + // assume new client is connected. if (attendee === undefined) { - // New attendee. Create SessionClient and add session id based + // New attendee. Create SessionClient and add session ID based // entry to map. - attendee = { - sessionId: clientSessionId, - order, - connectionId, - getStatus: () => SessionClientStatus.Connected, - }; + attendee = new SessionClient(clientSessionId, clientConnectionId); this.attendees.set(clientSessionId, attendee); isNew = true; } else if (order > attendee.order) { // The given association is newer than the one we have. - // Update the order and current connection id. + // Update the order and current connection ID. attendee.order = order; - attendee.connectionId = connectionId; + attendee.setConnectionId(clientConnectionId, /* updateStatus */ false); } - // Always update entry for the connection id. (Okay if already set.) + // Always update entry for the connection ID. (Okay if already set.) this.attendees.set(clientConnectionId, attendee); return { attendee, isNew }; } diff --git a/packages/framework/presence/src/test/latestMapValueManager.spec.ts b/packages/framework/presence/src/test/latestMapValueManager.spec.ts index acc54d8f3005..55ec27358ee9 100644 --- a/packages/framework/presence/src/test/latestMapValueManager.spec.ts +++ b/packages/framework/presence/src/test/latestMapValueManager.spec.ts @@ -28,23 +28,24 @@ export function checkCompiles(): void { fixedMap: LatestMap({ key1: { x: 0, y: 0 }, key2: { ref: "default", someId: 0 } }), }); // Workaround ts(2775): Assertions require every name in the call target to be declared with an explicit type annotation. - const map: typeof statesWorkspace = statesWorkspace; + const workspace: typeof statesWorkspace = statesWorkspace; + const props = workspace.props; - map.fixedMap.local.get("key1"); + props.fixedMap.local.get("key1"); // @ts-expect-error with inferred keys only those named it init are accessible - map.fixedMap.local.get("key3"); + props.fixedMap.local.get("key3"); - map.fixedMap.local.set("key2", { x: 0, y: 2 }); - map.fixedMap.local.set("key2", { ref: "string", someId: -1 }); + props.fixedMap.local.set("key2", { x: 0, y: 2 }); + props.fixedMap.local.set("key2", { ref: "string", someId: -1 }); // @ts-expect-error with inferred type `undefined` optional values are errors - map.fixedMap.local.set("key2", { x: undefined, y: undefined, ref: "string", someId: -1 }); + props.fixedMap.local.set("key2", { x: undefined, y: undefined, ref: "string", someId: -1 }); // @ts-expect-error with inferred type partial values are errors - map.fixedMap.local.set("key2", { x: 0 }); + props.fixedMap.local.set("key2", { x: 0 }); // @ts-expect-error with inferred heterogenous type mixed type values are errors - map.fixedMap.local.set("key2", { x: 0, y: 2, ref: "a", someId: 3 }); + props.fixedMap.local.set("key2", { x: 0, y: 2, ref: "a", someId: 3 }); - for (const key of map.fixedMap.local.keys()) { - const value = map.fixedMap.local.get(key); + for (const key of props.fixedMap.local.keys()) { + const value = props.fixedMap.local.get(key); console.log(key, value); } @@ -55,9 +56,9 @@ export function checkCompiles(): void { tilt?: number; } - map.add("pointers", LatestMap({})); + workspace.add("pointers", LatestMap({})); - const pointers = map.pointers; + const pointers = workspace.props.pointers; const localPointers = pointers.local; function logClientValue({ diff --git a/packages/framework/presence/src/test/latestValueManager.spec.ts b/packages/framework/presence/src/test/latestValueManager.spec.ts index 815cede40380..37bc5225372f 100644 --- a/packages/framework/presence/src/test/latestValueManager.spec.ts +++ b/packages/framework/presence/src/test/latestValueManager.spec.ts @@ -29,15 +29,17 @@ export function checkCompiles(): void { camera: Latest({ x: 0, y: 0, z: 0 }), }); // Workaround ts(2775): Assertions require every name in the call target to be declared with an explicit type annotation. - const map: typeof statesWorkspace = statesWorkspace; + const workspace: typeof statesWorkspace = statesWorkspace; + const props = workspace.props; - map.add("caret", Latest({ id: "", pos: 0 })); + workspace.add("caret", Latest({ id: "", pos: 0 })); - const fakeAdd = map.caret.local.pos + map.camera.local.z + map.cursor.local.x; + const fakeAdd = + workspace.props.caret.local.pos + props.camera.local.z + props.cursor.local.x; console.log(fakeAdd); // @ts-expect-error local may be set wholly, but partially it is readonly - map.caret.local.pos = 0; + workspace.props.caret.local.pos = 0; function logClientValue< T /* following extends should not be required: */ extends Record, @@ -46,7 +48,7 @@ export function checkCompiles(): void { } // Create new cursor state - const cursor = map.cursor; + const cursor = props.cursor; // Update our cursor position cursor.local = { x: 1, y: 2 }; diff --git a/packages/framework/presence/src/test/notificationsManager.spec.ts b/packages/framework/presence/src/test/notificationsManager.spec.ts index 3e1e1a764af1..b0a593220ede 100644 --- a/packages/framework/presence/src/test/notificationsManager.spec.ts +++ b/packages/framework/presence/src/test/notificationsManager.spec.ts @@ -69,7 +69,7 @@ export function checkCompiles(): void { ); } - const chat = notifications.chat; + const chat = notifications.props.chat; chat.emit.broadcast("msg", "howdy"); diff --git a/packages/framework/presence/src/test/presenceManager.spec.ts b/packages/framework/presence/src/test/presenceManager.spec.ts index ccf5821761cf..5fb8c4c62b2f 100644 --- a/packages/framework/presence/src/test/presenceManager.spec.ts +++ b/packages/framework/presence/src/test/presenceManager.spec.ts @@ -125,7 +125,7 @@ describe("Presence", () => { "Attendee has wrong session id", ); assert.equal( - newAttendee.connectionId(), + newAttendee.getConnectionId(), initialAttendeeConnectionId, "Attendee has wrong client connection id", ); @@ -163,7 +163,7 @@ describe("Presence", () => { "Disconnected attendee has wrong session id", ); assert.equal( - disconnectedAttendee.connectionId(), + disconnectedAttendee.getConnectionId(), initialAttendeeConnectionId, "Disconnected attendee has wrong client connection id", ); @@ -176,7 +176,7 @@ describe("Presence", () => { "No attendee was disconnected in beforeEach", ); assert.equal( - disconnectedAttendee.getStatus(), + disconnectedAttendee.getConnectionStatus(), SessionClientStatus.Disconnected, "Disconnected attendee has wrong status", ); @@ -251,7 +251,7 @@ describe("Presence", () => { ); // Current connection id is updated assert( - newAttendee.connectionId() === updatedClientConnectionId, + newAttendee.getConnectionId() === updatedClientConnectionId, "Attendee does not have updated client connection id", ); // Attendee is available via new connection id diff --git a/packages/framework/presence/src/test/presenceStates.spec.ts b/packages/framework/presence/src/test/presenceStates.spec.ts index 4f43b3115ae3..759348e43dfa 100644 --- a/packages/framework/presence/src/test/presenceStates.spec.ts +++ b/packages/framework/presence/src/test/presenceStates.spec.ts @@ -55,8 +55,9 @@ export function checkCompiles(): void { const initialCaret = { id: "", pos: 0 }; states.add("caret", createValueManager(initialCaret)); + const statesProps = states.props; - const fakeAdd = states.camera.z + states.cursor.x + states.caret.pos; + const fakeAdd = statesProps.camera.z + statesProps.cursor.x + statesProps.caret.pos; console.log(fakeAdd); // @ts-expect-error should error on typo detection diff --git a/packages/framework/presence/src/types.ts b/packages/framework/presence/src/types.ts index d1e095eccbf8..1792cce772ba 100644 --- a/packages/framework/presence/src/types.ts +++ b/packages/framework/presence/src/types.ts @@ -55,30 +55,30 @@ export interface PresenceStatesSchema { * @sealed * @alpha */ -export type PresenceStatesEntries< - TSchema extends PresenceStatesSchema, - TManagerRestrictions, -> = { +export type PresenceStatesEntries = { /** * Registered `Value Manager`s */ - readonly [Key in Exclude< - keyof TSchema, - keyof PresenceStatesMethods - >]: ReturnType["manager"] extends InternalTypes.StateValue + readonly [Key in keyof TSchema]: ReturnType< + TSchema[Key] + >["manager"] extends InternalTypes.StateValue ? TManager : never; }; /** - * Provides methods for managing `Value Manager`s in {@link PresenceStates}. + * `PresenceStates` maintains a registry of `Value Manager`s that all share and provide access to + * presence state values across client members in a session. + * + * `Value Manager`s offer variations on how to manage states, but all share same principle that + * each client's state is independent and may only be updated by originating client. * * @sealed * @alpha */ -export interface PresenceStatesMethods< +export interface PresenceStates< TSchema extends PresenceStatesSchema, - TManagerRestrictions, + TManagerConstraints = unknown, > { /** * Registers a new `Value Manager` with the {@link PresenceStates}. @@ -88,30 +88,20 @@ export interface PresenceStatesMethods< add< TKey extends string, TValue extends InternalTypes.ValueDirectoryOrState, - TManager extends TManagerRestrictions, + TManager extends TManagerConstraints, >( key: TKey, manager: InternalTypes.ManagerFactory, ): asserts this is PresenceStates< - TSchema & Record> + TSchema & Record>, + TManagerConstraints >; -} -/** - * `PresenceStates` maintains a registry of `Value Manager`s that all share and provide access to - * presence state values across client members in a session. - * - * `Value Manager`s offer variations on how to manage states, but all share same principle that - * each client's state is independent and may only be updated by originating client. - * - * @sealed - * @alpha - */ -export type PresenceStates< - TSchema extends PresenceStatesSchema, - TManagerRestrictions = unknown, -> = PresenceStatesEntries & - PresenceStatesMethods; + /** + * Registry of `Value Manager`s. + */ + readonly props: PresenceStatesEntries; +} // #endregion PresenceStates diff --git a/packages/framework/request-handler/CHANGELOG.md b/packages/framework/request-handler/CHANGELOG.md index 06c7371d59c2..41fb3d5ef4c1 100644 --- a/packages/framework/request-handler/CHANGELOG.md +++ b/packages/framework/request-handler/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/request-handler +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/request-handler/package.json b/packages/framework/request-handler/package.json index fb575cafaf41..3234714b915d 100644 --- a/packages/framework/request-handler/package.json +++ b/packages/framework/request-handler/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/request-handler", - "version": "2.5.0", + "version": "2.10.0", "description": "A simple request handling library for Fluid Framework", "homepage": "https://fluidframework.com", "repository": { @@ -126,11 +126,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/request-handler-previous": "npm:@fluidframework/request-handler@~2.4.0", + "@fluidframework/request-handler-previous": "npm:@fluidframework/request-handler@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/diff": "^3.5.1", "@types/mocha": "^9.1.1", diff --git a/packages/framework/synthesize/CHANGELOG.md b/packages/framework/synthesize/CHANGELOG.md index 78726b106fc7..13c35794701d 100644 --- a/packages/framework/synthesize/CHANGELOG.md +++ b/packages/framework/synthesize/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/synthesize +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/synthesize/package.json b/packages/framework/synthesize/package.json index e8be65966639..4e3c5c2984e5 100644 --- a/packages/framework/synthesize/package.json +++ b/packages/framework/synthesize/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/synthesize", - "version": "2.5.0", + "version": "2.10.0", "description": "A library for synthesizing scope objects.", "homepage": "https://fluidframework.com", "repository": { @@ -122,14 +122,14 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/core-interfaces": "workspace:~", "@fluidframework/datastore": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/runtime-utils": "workspace:~", - "@fluidframework/synthesize-previous": "npm:@fluidframework/synthesize@~2.4.0", + "@fluidframework/synthesize-previous": "npm:@fluidframework/synthesize@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/framework/undo-redo/CHANGELOG.md b/packages/framework/undo-redo/CHANGELOG.md index 6cb7fbd9188b..aa989fa97931 100644 --- a/packages/framework/undo-redo/CHANGELOG.md +++ b/packages/framework/undo-redo/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/undo-redo +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index c6ffa5c15f0e..922f7142ea89 100644 --- a/packages/framework/undo-redo/package.json +++ b/packages/framework/undo-redo/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/undo-redo", - "version": "2.5.0", + "version": "2.10.0", "description": "Undo Redo", "homepage": "https://fluidframework.com", "repository": { @@ -108,12 +108,12 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", - "@fluidframework/undo-redo-previous": "npm:@fluidframework/undo-redo@~2.4.0", + "@fluidframework/undo-redo-previous": "npm:@fluidframework/undo-redo@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/diff": "^3.5.1", "@types/mocha": "^9.1.1", diff --git a/packages/loader/container-loader/.eslintrc.cjs b/packages/loader/container-loader/.eslintrc.cjs index 9f83dbeba921..eeb16088be31 100644 --- a/packages/loader/container-loader/.eslintrc.cjs +++ b/packages/loader/container-loader/.eslintrc.cjs @@ -8,6 +8,9 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, overrides: [ { // Rules only for test files diff --git a/packages/loader/container-loader/CHANGELOG.md b/packages/loader/container-loader/CHANGELOG.md index bdacc5b6a743..730b599edf9c 100644 --- a/packages/loader/container-loader/CHANGELOG.md +++ b/packages/loader/container-loader/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/container-loader +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/loader/container-loader/package.json b/packages/loader/container-loader/package.json index 6060a2dadabe..9109535eebf3 100644 --- a/packages/loader/container-loader/package.json +++ b/packages/loader/container-loader/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/container-loader", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid container loader", "homepage": "https://fluidframework.com", "repository": { @@ -189,10 +189,10 @@ "@fluid-internal/client-utils": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-loader-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/debug": "^4.1.5", @@ -216,7 +216,11 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IContainerExperimental": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/loader/container-loader/src/container.ts b/packages/loader/container-loader/src/container.ts index 8683ef1df0b6..3577d3b42a16 100644 --- a/packages/loader/container-loader/src/container.ts +++ b/packages/loader/container-loader/src/container.ts @@ -1319,7 +1319,7 @@ export class Container ) => { try { assert( - this.deltaManager.inbound.length === 0, + this._deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */, ); return combineAppAndProtocolSummary( @@ -1523,13 +1523,13 @@ export class Container const codeDetails = this.getCodeDetailsFromQuorum(); await Promise.all([ - this.deltaManager.inbound.pause(), - this.deltaManager.inboundSignal.pause(), + this._deltaManager.inbound.pause(), + this._deltaManager.inboundSignal.pause(), ]); if ((await this.satisfies(codeDetails)) === true) { - this.deltaManager.inbound.resume(); - this.deltaManager.inboundSignal.resume(); + this._deltaManager.inbound.resume(); + this._deltaManager.inboundSignal.resume(); return; } diff --git a/packages/loader/container-loader/src/deltaManager.ts b/packages/loader/container-loader/src/deltaManager.ts index 6410283147e0..450ee5084fa8 100644 --- a/packages/loader/container-loader/src/deltaManager.ts +++ b/packages/loader/container-loader/src/deltaManager.ts @@ -5,8 +5,8 @@ import { ICriticalContainerError } from "@fluidframework/container-definitions"; import { - IDeltaManager, IDeltaManagerEvents, + IDeltaManagerFull, IDeltaQueue, type IDeltaSender, type ReadOnlyInfo, @@ -151,9 +151,7 @@ function logIfFalse( */ export class DeltaManager extends EventEmitterWithErrorHandling - implements - IDeltaManager, - IEventProvider + implements IDeltaManagerFull, IEventProvider { public readonly connectionManager: TConnectionManager; diff --git a/packages/loader/container-loader/src/index.ts b/packages/loader/container-loader/src/index.ts index da1ce6f740a5..2c53bb9b4720 100644 --- a/packages/loader/container-loader/src/index.ts +++ b/packages/loader/container-loader/src/index.ts @@ -20,7 +20,10 @@ export { resolveWithLocationRedirectionHandling, } from "./location-redirection-utilities/index.js"; export { IProtocolHandler, ProtocolHandlerBuilder } from "./protocol.js"; -export { tryParseCompatibleResolvedUrl, IParsedUrl } from "./utils.js"; +export { + tryParseCompatibleResolvedUrl, + IParsedUrl, +} from "./utils.js"; export { IBaseProtocolHandler, IScribeProtocolState, diff --git a/packages/loader/container-loader/src/loadPaused.ts b/packages/loader/container-loader/src/loadPaused.ts index a17ee4f86349..1ab27a74f543 100644 --- a/packages/loader/container-loader/src/loadPaused.ts +++ b/packages/loader/container-loader/src/loadPaused.ts @@ -5,11 +5,13 @@ import { ILoader, + isIDeltaManagerFull, LoaderHeader, type IContainer, } from "@fluidframework/container-definitions/internal"; import { IRequest } from "@fluidframework/core-interfaces"; import type { IErrorBase } from "@fluidframework/core-interfaces"; +import { assert } from "@fluidframework/core-utils/internal"; import { GenericError } from "@fluidframework/telemetry-utils/internal"; /* eslint-disable jsdoc/check-indentation */ @@ -63,6 +65,7 @@ export async function loadContainerPaused( const lastProcessedSequenceNumber = dm.initialSequenceNumber; const pauseContainer = (): void => { + assert(isIDeltaManagerFull(dm), "Delta manager does not have inbound/outbound queues."); // eslint-disable-next-line no-void void dm.inbound.pause(); // eslint-disable-next-line no-void diff --git a/packages/loader/container-loader/src/packageVersion.ts b/packages/loader/container-loader/src/packageVersion.ts index 7d412478c02e..c8b6a3136040 100644 --- a/packages/loader/container-loader/src/packageVersion.ts +++ b/packages/loader/container-loader/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/container-loader"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/loader/driver-utils/.eslintrc.cjs b/packages/loader/driver-utils/.eslintrc.cjs index c27d8e1558b3..99682a2f1793 100644 --- a/packages/loader/driver-utils/.eslintrc.cjs +++ b/packages/loader/driver-utils/.eslintrc.cjs @@ -13,6 +13,7 @@ module.exports = { }, rules: { "import/no-nodejs-modules": ["error"], + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/loader/driver-utils/CHANGELOG.md b/packages/loader/driver-utils/CHANGELOG.md index 055a43a57f15..e4688208c824 100644 --- a/packages/loader/driver-utils/CHANGELOG.md +++ b/packages/loader/driver-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/driver-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/loader/driver-utils/package.json b/packages/loader/driver-utils/package.json index dc43f86e5a64..392aaaf3f24b 100644 --- a/packages/loader/driver-utils/package.json +++ b/packages/loader/driver-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/driver-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Collection of utility functions for Fluid drivers", "homepage": "https://fluidframework.com", "repository": { @@ -130,10 +130,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/driver-utils-previous": "npm:@fluidframework/driver-utils@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/driver-utils-previous": "npm:@fluidframework/driver-utils@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/loader/driver-utils/src/packageVersion.ts b/packages/loader/driver-utils/src/packageVersion.ts index 60365c9a02f7..cf384d42081c 100644 --- a/packages/loader/driver-utils/src/packageVersion.ts +++ b/packages/loader/driver-utils/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/driver-utils"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/loader/test-loader-utils/CHANGELOG.md b/packages/loader/test-loader-utils/CHANGELOG.md index c3bd79bf7e76..285078776848 100644 --- a/packages/loader/test-loader-utils/CHANGELOG.md +++ b/packages/loader/test-loader-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-loader-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/loader/test-loader-utils/package.json b/packages/loader/test-loader-utils/package.json index bdd113ee29ec..ff1d918f7992 100644 --- a/packages/loader/test-loader-utils/package.json +++ b/packages/loader/test-loader-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-loader-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Mocks and other test utilities for the Fluid Framework Loader", "homepage": "https://fluidframework.com", "repository": { @@ -59,9 +59,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", diff --git a/packages/runtime/container-runtime-definitions/CHANGELOG.md b/packages/runtime/container-runtime-definitions/CHANGELOG.md index 36b7bcdcf4a7..2e476d16d694 100644 --- a/packages/runtime/container-runtime-definitions/CHANGELOG.md +++ b/packages/runtime/container-runtime-definitions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/container-runtime-definitions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/container-runtime-definitions/package.json b/packages/runtime/container-runtime-definitions/package.json index 8f41418e9aa4..f28cb37dab97 100644 --- a/packages/runtime/container-runtime-definitions/package.json +++ b/packages/runtime/container-runtime-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/container-runtime-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { @@ -89,10 +89,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/container-runtime-definitions-previous": "npm:@fluidframework/container-runtime-definitions@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/container-runtime-definitions-previous": "npm:@fluidframework/container-runtime-definitions@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", @@ -103,7 +103,14 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IContainerRuntime": { + "backCompat": false + }, + "Interface_IContainerRuntimeWithResolveHandle_Deprecated": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/runtime/container-runtime-definitions/src/test/types/validateContainerRuntimeDefinitionsPrevious.generated.ts b/packages/runtime/container-runtime-definitions/src/test/types/validateContainerRuntimeDefinitionsPrevious.generated.ts index 078369017175..192f06034e4a 100644 --- a/packages/runtime/container-runtime-definitions/src/test/types/validateContainerRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/container-runtime-definitions/src/test/types/validateContainerRuntimeDefinitionsPrevious.generated.ts @@ -22,6 +22,7 @@ declare type MakeUnusedImportErrorsGoAway = TypeOnly | MinimalType | Fu * typeValidation.broken: * "Interface_IContainerRuntime": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IContainerRuntime = requireAssignableTo, TypeOnly> /* @@ -49,6 +50,7 @@ declare type old_as_current_for_Interface_IContainerRuntimeWithResolveHandle_Dep * typeValidation.broken: * "Interface_IContainerRuntimeWithResolveHandle_Deprecated": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IContainerRuntimeWithResolveHandle_Deprecated = requireAssignableTo, TypeOnly> /* diff --git a/packages/runtime/container-runtime/.eslintrc.cjs b/packages/runtime/container-runtime/.eslintrc.cjs index 5f7bcea7c239..0cfb8c689f85 100644 --- a/packages/runtime/container-runtime/.eslintrc.cjs +++ b/packages/runtime/container-runtime/.eslintrc.cjs @@ -13,6 +13,7 @@ module.exports = { }, rules: { "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/runtime/container-runtime/CHANGELOG.md b/packages/runtime/container-runtime/CHANGELOG.md index a1d14103893c..e53b1494bbe2 100644 --- a/packages/runtime/container-runtime/CHANGELOG.md +++ b/packages/runtime/container-runtime/CHANGELOG.md @@ -1,5 +1,23 @@ # @fluidframework/container-runtime +## 2.5.0 + +### Minor Changes + +- Signal telemetry events details ([#22804](https://github.com/microsoft/FluidFramework/pull/22804)) [e6566f6358](https://github.com/microsoft/FluidFramework/commit/e6566f6358551b5e579637de6c111d42281f7716) + + Properties of `eventName`s beginning "fluid:telemetry:ContainerRuntime:Signal" are updated to use `details` for all event specific information. Additional per-event changes: + + - SignalLatency: shorten names now that data is packed into details. Renames: + - `signalsSent` -> `sent` + - `signalsLost` -> `lost` + - `outOfOrderSignals` -> `outOfOrder` + - SignalLost/SignalOutOfOrder: rename `trackingSequenceNumber` to `expectedSequenceNumber` + - SignalOutOfOrder: rename `type` to `contentsType` and only emit it some of the time + + > [!IMPORTANT] + > Reminder: the naming and structure of telemetry events are not considered a part of the public API and may change at any time. + ## 2.4.0 ### Minor Changes diff --git a/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md b/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md index eb8c2099ef1a..80d377c4f3e6 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md @@ -67,7 +67,7 @@ export class ContainerRuntime extends TypedEventEmitter; + get deltaManager(): IDeltaManager; // (undocumented) dispose(error?: Error): void; // (undocumented) diff --git a/packages/runtime/container-runtime/package.json b/packages/runtime/container-runtime/package.json index f968bbc5e1e9..596cd95d8781 100644 --- a/packages/runtime/container-runtime/package.json +++ b/packages/runtime/container-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/container-runtime", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid container runtime", "homepage": "https://fluidframework.com", "repository": { @@ -205,10 +205,10 @@ "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-private/test-pairwise-generator": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", @@ -232,8 +232,56 @@ }, "typeValidation": { "broken": { + "Class_LocalFluidDataStoreContext": { + "backCompat": false + }, + "ClassStatics_LocalFluidDataStoreContext": { + "backCompat": false + }, + "Class_LocalFluidDataStoreContextBase": { + "backCompat": false + }, + "ClassStatics_LocalFluidDataStoreContextBase": { + "backCompat": false + }, + "Class_ChannelCollection": { + "backCompat": false + }, + "ClassStatics_ChannelCollection": { + "backCompat": false + }, + "Class_ContainerRuntime": { + "backCompat": false + }, "Class_DocumentsSchemaController": { "forwardCompat": false + }, + "ClassStatics_ContainerRuntime": { + "backCompat": false + }, + "Class_FluidDataStoreContext": { + "backCompat": false + }, + "ClassStatics_FluidDataStoreContext": { + "backCompat": false + }, + "Interface_ISummarizerRuntime": { + "backCompat": false + }, + "Interface_ILocalFluidDataStoreContextProps": { + "backCompat": false + }, + "Interface_IFluidDataStoreContextInternal": { + "backCompat": false + }, + "Interface_IFluidDataStoreContextProps": { + "backCompat": false + }, + "Interface_ILocalDetachedFluidDataStoreContextProps": { + "backCompat": false + }, + "Interface_LoadContainerRuntimeParams": { + "backCompat": false } }, "entrypoint": "legacy" diff --git a/packages/runtime/container-runtime/src/blobManager/blobManager.ts b/packages/runtime/container-runtime/src/blobManager/blobManager.ts index 39a3df7cb4e9..6f5036695349 100644 --- a/packages/runtime/container-runtime/src/blobManager/blobManager.ts +++ b/packages/runtime/container-runtime/src/blobManager/blobManager.ts @@ -180,6 +180,11 @@ export class BlobManager extends TypedEventEmitter { private readonly isBlobDeleted: (blobPath: string) => boolean; private readonly runtime: IBlobManagerRuntime; private readonly closeContainer: (error?: ICriticalContainerError) => void; + private readonly localBlobIdGenerator: () => string; + // TODO: consider to replace with a lazy promise + private readonly pendingStashedBlobs: Map> = + new Map(); + private stashedBlobsUploadP: Promise<(void | ICreateBlobResponse)[]> | undefined = undefined; constructor(props: { readonly routeContext: IFluidHandleContext; @@ -205,6 +210,7 @@ export class BlobManager extends TypedEventEmitter { readonly runtime: IBlobManagerRuntime; stashedBlobs: IPendingBlobs | undefined; readonly closeContainer: (error?: ICriticalContainerError) => void; + readonly localBlobIdGenerator?: (() => string) | undefined; }) { super(); const { @@ -217,6 +223,7 @@ export class BlobManager extends TypedEventEmitter { runtime, stashedBlobs, closeContainer, + localBlobIdGenerator, } = props; this.routeContext = routeContext; this.getStorage = getStorage; @@ -224,6 +231,7 @@ export class BlobManager extends TypedEventEmitter { this.isBlobDeleted = isBlobDeleted; this.runtime = runtime; this.closeContainer = closeContainer; + this.localBlobIdGenerator = localBlobIdGenerator ?? uuid; this.mc = createChildMonitoringContext({ logger: this.runtime.baseLogger, @@ -256,14 +264,18 @@ export class BlobManager extends TypedEventEmitter { return; } } - + this.pendingStashedBlobs.set(localId, this.uploadBlob(localId, blob)); this.pendingBlobs.set(localId, { ...pendingEntry, ...stashedPendingBlobOverrides, - uploadP: this.uploadBlob(localId, blob), + uploadP: this.pendingStashedBlobs.get(localId), }); }); - + this.waitForStashedBlobs() + .catch(() => {}) + .finally(() => { + this.pendingStashedBlobs.clear(); + }); this.sendBlobAttachOp = (localId: string, blobId?: string) => { const pendingEntry = this.pendingBlobs.get(localId); assert( @@ -295,6 +307,17 @@ export class BlobManager extends TypedEventEmitter { }; } + public async waitForStashedBlobs(): Promise<(void | ICreateBlobResponse)[]> { + if (!this.stashedBlobsUploadP) { + this.stashedBlobsUploadP = Promise.all(this.pendingStashedBlobs.values()).finally(() => { + this.stashedBlobsUploadP = undefined; + return; + }); + } + + return this.stashedBlobsUploadP; + } + public get allBlobsAttached(): boolean { for (const [, entry] of this.pendingBlobs) { if (entry.attached === false) { @@ -435,7 +458,7 @@ export class BlobManager extends TypedEventEmitter { // Create a local ID for the blob. After uploading it to storage and before returning it, a local ID to // storage ID mapping is created. - const localId = uuid(); + const localId = this.localBlobIdGenerator(); const pendingEntry: PendingBlob = { blob, handleP: new Deferred(), diff --git a/packages/runtime/container-runtime/src/channelCollection.ts b/packages/runtime/container-runtime/src/channelCollection.ts index 2a587ded2d73..0fed13948adb 100644 --- a/packages/runtime/container-runtime/src/channelCollection.ts +++ b/packages/runtime/container-runtime/src/channelCollection.ts @@ -171,10 +171,6 @@ export function wrapContext(context: IFluidParentContext): IFluidParentContext { getAudience: (...args) => { return context.getAudience(...args); }, - // back-compat, to be removed in 2.0 - ensureNoDataModelChanges: (...args) => { - return context.ensureNoDataModelChanges(...args); - }, submitMessage: (...args) => { return context.submitMessage(...args); }, @@ -885,7 +881,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable { return; } const currentContext = this.contexts.get(currentMessageState.address); - assert(!!currentContext, "Context not found"); + assert(!!currentContext, 0xa66 /* Context not found */); currentContext.processMessages({ envelope: { ...messageCollection.envelope, type: currentMessageState.type }, diff --git a/packages/runtime/container-runtime/src/connectionTelemetry.ts b/packages/runtime/container-runtime/src/connectionTelemetry.ts index 451c38747fb9..58a9adaed9d2 100644 --- a/packages/runtime/container-runtime/src/connectionTelemetry.ts +++ b/packages/runtime/container-runtime/src/connectionTelemetry.ts @@ -4,7 +4,7 @@ */ import { performance } from "@fluid-internal/client-utils"; -import { IDeltaManager } from "@fluidframework/container-definitions/internal"; +import { IDeltaManagerFull } from "@fluidframework/container-definitions/internal"; import { IContainerRuntimeEvents } from "@fluidframework/container-runtime-definitions/internal"; import { IEventProvider } from "@fluidframework/core-interfaces"; import { assert } from "@fluidframework/core-utils/internal"; @@ -124,7 +124,7 @@ class OpPerfTelemetry { /** * DeltaManager instance to monitor. */ - private readonly deltaManager: IDeltaManager, + private readonly deltaManager: IDeltaManagerFull, /** * Emitter of events for the container runtime. */ @@ -500,7 +500,7 @@ export interface IPerfSignalReport { */ export function ReportOpPerfTelemetry( clientId: string | undefined, - deltaManager: IDeltaManager, + deltaManager: IDeltaManagerFull, containerRuntimeEvents: IEventProvider, logger: ITelemetryLoggerExt, ): void { diff --git a/packages/runtime/container-runtime/src/containerHandleContext.ts b/packages/runtime/container-runtime/src/containerHandleContext.ts index 9ccf5d4165b3..27840fe415cd 100644 --- a/packages/runtime/container-runtime/src/containerHandleContext.ts +++ b/packages/runtime/container-runtime/src/containerHandleContext.ts @@ -8,7 +8,10 @@ import { IRequest, IResponse } from "@fluidframework/core-interfaces"; import { IFluidHandleContext } from "@fluidframework/core-interfaces/internal"; import { generateHandleContextPath } from "@fluidframework/runtime-utils/internal"; -import { ContainerRuntime } from "./containerRuntime.js"; +export interface IContainerHandleContextRuntime { + attachState: AttachState; + resolveHandle(request: IRequest): Promise; +} export class ContainerFluidHandleContext implements IFluidHandleContext { public get IFluidHandleContext() { @@ -24,7 +27,7 @@ export class ContainerFluidHandleContext implements IFluidHandleContext { */ constructor( public readonly path: string, - private readonly runtime: ContainerRuntime, + private readonly runtime: IContainerHandleContextRuntime, public readonly routeContext?: IFluidHandleContext, ) { this.absolutePath = generateHandleContextPath(path, this.routeContext); diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 95ec0488838b..d8c2b5cdc203 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -19,6 +19,8 @@ import { IRuntime, LoaderHeader, IDeltaManager, + IDeltaManagerFull, + isIDeltaManagerFull, } from "@fluidframework/container-definitions/internal"; import { IContainerRuntime, @@ -1259,16 +1261,18 @@ export class ContainerRuntime * accesses such as sets "read-only" mode for the summarizer client. This is the default delta manager that should * be used unless the innerDeltaManager is required. */ - public readonly deltaManager: IDeltaManager; + public get deltaManager(): IDeltaManager { + return this._deltaManager; + } + + private readonly _deltaManager: IDeltaManagerFull; + /** * The delta manager provided by the container context. By default, using the default delta manager (proxy) * should be sufficient. This should be used only if necessary. For example, for validating and propagating connected * events which requires access to the actual real only info, this is needed. */ - private readonly innerDeltaManager: IDeltaManager< - ISequencedDocumentMessage, - IDocumentMessage - >; + private readonly innerDeltaManager: IDeltaManagerFull; // internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc. private readonly mc: MonitoringContext; @@ -1528,6 +1532,7 @@ export class ContainerRuntime compressionAlgorithm: CompressionAlgorithms.lz4, }; + assert(isIDeltaManagerFull(deltaManager), "Invalid delta manager"); this.innerDeltaManager = deltaManager; // Here we could wrap/intercept on these functions to block/modify outgoing messages if needed. @@ -1644,7 +1649,7 @@ export class ContainerRuntime this.logger, ); - let outerDeltaManager: IDeltaManager; + let outerDeltaManager: IDeltaManagerFull; this.useDeltaManagerOpsProxy = this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true; // The summarizerDeltaManager Proxy is used to lie to the summarizer to convince it is in the right state as a summarizer client. @@ -1663,7 +1668,7 @@ export class ContainerRuntime outerDeltaManager = pendingOpsDeltaManagerProxy; } - this.deltaManager = outerDeltaManager; + this._deltaManager = outerDeltaManager; this.handleContext = new ContainerFluidHandleContext("", this); @@ -2034,7 +2039,7 @@ export class ContainerRuntime initialSequenceNumber: this.deltaManager.initialSequenceNumber, }); - ReportOpPerfTelemetry(this.clientId, this.deltaManager, this, this.logger); + ReportOpPerfTelemetry(this.clientId, this._deltaManager, this, this.logger); BindBatchTracker(this, this.logger); this.entryPoint = new LazyPromise(async () => { @@ -2239,8 +2244,8 @@ export class ContainerRuntime }); // If the inbound deltas queue is paused or disconnected, we expect a reconnect and unpause // as long as it's not a summarizer client. - if (this.deltaManager.inbound.paused) { - props.inboundPaused = this.deltaManager.inbound.paused; // reusing telemetry + if (this._deltaManager.inbound.paused) { + props.inboundPaused = this._deltaManager.inbound.paused; // reusing telemetry } const defP = new Deferred(); this.deltaManager.on("op", (message: ISequencedDocumentMessage) => { @@ -2819,17 +2824,6 @@ export class ContainerRuntime : false /* groupedBatch */, ); } else { - if (!runtimeBatch) { - // The DeltaManager used to do this, but doesn't anymore as of Loader v2.4 - // Anyone listening to our "op" event would expect the contents to be parsed per this same logic - if ( - typeof messageCopy.contents === "string" && - messageCopy.contents !== "" && - messageCopy.type !== MessageType.ClientLeave - ) { - messageCopy.contents = JSON.parse(messageCopy.contents); - } - } this.processInboundMessages( [{ message: messageCopy, localOpMetadata: undefined }], { batchStart: true, batchEnd: true }, // Single message @@ -2928,7 +2922,7 @@ export class ContainerRuntime // Helper that processes the previous bunch of messages. const sendBunchedMessages = () => { - assert(previousMessage !== undefined, "previous message must exist"); + assert(previousMessage !== undefined, 0xa67 /* previous message must exist */); this.ensureNoDataModelChanges(() => { this.validateAndProcessRuntimeMessages( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -2996,6 +2990,16 @@ export class ContainerRuntime this.updateDocumentDirtyState(false); } + // The DeltaManager used to do this, but doesn't anymore as of Loader v2.4 + // Anyone listening to our "op" event would expect the contents to be parsed per this same logic + if ( + typeof message.contents === "string" && + message.contents !== "" && + message.type !== MessageType.ClientLeave + ) { + message.contents = JSON.parse(message.contents); + } + this.emit("op", message, false /* runtimeMessage */); } @@ -3947,7 +3951,7 @@ export class ContainerRuntime ) === true; try { - await this.deltaManager.inbound.pause(); + await this._deltaManager.inbound.pause(); if (shouldPauseInboundSignal) { await this.deltaManager.inboundSignal.pause(); } @@ -4212,7 +4216,7 @@ export class ContainerRuntime this._summarizer?.recordSummaryAttempt?.(summaryRefSeqNum); // Restart the delta manager - this.deltaManager.inbound.resume(); + this._deltaManager.inbound.resume(); if (shouldPauseInboundSignal) { this.deltaManager.inboundSignal.resume(); } diff --git a/packages/runtime/container-runtime/src/dataStoreContext.ts b/packages/runtime/container-runtime/src/dataStoreContext.ts index e0f10b6bf2b3..0378cc682d68 100644 --- a/packages/runtime/container-runtime/src/dataStoreContext.ts +++ b/packages/runtime/container-runtime/src/dataStoreContext.ts @@ -224,12 +224,6 @@ export abstract class FluidDataStoreContext public get containerRuntime(): IContainerRuntimeBase { return this._containerRuntime; } - - // back-compat, to be removed in 2.0 - public ensureNoDataModelChanges(callback: () => T): T { - return this.parentContext.ensureNoDataModelChanges(callback); - } - public get isLoaded(): boolean { return this.loaded; } @@ -605,11 +599,14 @@ export abstract class FluidDataStoreContext this.summarizerNode.recordChange(envelope as ISequencedDocumentMessage); if (this.loaded) { - assert(this.channel !== undefined, "Channel is not loaded"); + assert(this.channel !== undefined, 0xa68 /* Channel is not loaded */); this.processMessagesCompat(this.channel, messageCollection); } else { assert(!local, 0x142 /* "local store channel is not loaded" */); - assert(this.pendingMessagesState !== undefined, "pending messages queue is undefined"); + assert( + this.pendingMessagesState !== undefined, + 0xa69 /* pending messages queue is undefined */, + ); this.pendingMessagesState.messageCollections.push({ ...messageCollection, messagesContent: Array.from(messagesContent), @@ -829,7 +826,10 @@ export abstract class FluidDataStoreContext protected processPendingOps(channel: IFluidDataStoreChannel) { const baseSequenceNumber = this.baseSnapshotSequenceNumber ?? -1; - assert(this.pendingMessagesState !== undefined, "pending messages queue is undefined"); + assert( + this.pendingMessagesState !== undefined, + 0xa6a /* pending messages queue is undefined */, + ); for (const messageCollection of this.pendingMessagesState.messageCollections) { // Only process ops whose seq number is greater than snapshot sequence number from which it loaded. if (messageCollection.envelope.sequenceNumber > baseSequenceNumber) { diff --git a/packages/runtime/container-runtime/src/deltaManagerProxies.ts b/packages/runtime/container-runtime/src/deltaManagerProxies.ts index 0f717aff1941..0ff85b21b529 100644 --- a/packages/runtime/container-runtime/src/deltaManagerProxies.ts +++ b/packages/runtime/container-runtime/src/deltaManagerProxies.ts @@ -6,8 +6,8 @@ import { TypedEventEmitter } from "@fluid-internal/client-utils"; import type { IConnectionDetails, - IDeltaManager, IDeltaManagerEvents, + IDeltaManagerFull, IDeltaQueue, IDeltaSender, ReadOnlyInfo, @@ -33,7 +33,7 @@ import { summarizerClientType } from "./summary/index.js"; */ export abstract class BaseDeltaManagerProxy extends TypedEventEmitter - implements IDeltaManager + implements IDeltaManagerFull { public get IDeltaSender(): IDeltaSender { return this; @@ -99,12 +99,7 @@ export abstract class BaseDeltaManagerProxy return this.deltaManager.readOnlyInfo; } - constructor( - protected readonly deltaManager: IDeltaManager< - ISequencedDocumentMessage, - IDocumentMessage - >, - ) { + constructor(protected readonly deltaManager: IDeltaManagerFull) { super(); // We are expecting this class to have many listeners, so we suppress noisy "MaxListenersExceededWarning" logging. @@ -194,12 +189,7 @@ export class DeltaManagerSummarizerProxy extends BaseDeltaManagerProxy { private readonly isSummarizerClient: boolean; - constructor( - protected readonly deltaManager: IDeltaManager< - ISequencedDocumentMessage, - IDocumentMessage - >, - ) { + constructor(protected readonly deltaManager: IDeltaManagerFull) { super(deltaManager); this.isSummarizerClient = this.deltaManager.clientDetails.type === summarizerClientType; } @@ -250,10 +240,7 @@ export class DeltaManagerPendingOpsProxy extends BaseDeltaManagerProxy { }; constructor( - protected readonly deltaManager: IDeltaManager< - ISequencedDocumentMessage, - IDocumentMessage - >, + protected readonly deltaManager: IDeltaManagerFull, private readonly pendingStateManager: Pick< PendingStateManager, "minimumPendingMessageSequenceNumber" diff --git a/packages/runtime/container-runtime/src/deltaScheduler.ts b/packages/runtime/container-runtime/src/deltaScheduler.ts index 092fdf206ba6..0bdf853d846b 100644 --- a/packages/runtime/container-runtime/src/deltaScheduler.ts +++ b/packages/runtime/container-runtime/src/deltaScheduler.ts @@ -4,11 +4,8 @@ */ import { performance } from "@fluid-internal/client-utils"; -import { IDeltaManager } from "@fluidframework/container-definitions/internal"; -import { - IDocumentMessage, - ISequencedDocumentMessage, -} from "@fluidframework/driver-definitions/internal"; +import { IDeltaManagerFull } from "@fluidframework/container-definitions/internal"; +import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal"; import { ITelemetryLoggerExt, formatTick } from "@fluidframework/telemetry-utils/internal"; /** @@ -25,7 +22,7 @@ import { ITelemetryLoggerExt, formatTick } from "@fluidframework/telemetry-utils * processed, the time and number of turns it took to process the ops. */ export class DeltaScheduler { - private readonly deltaManager: IDeltaManager; + private readonly deltaManager: IDeltaManagerFull; // The time for processing ops in a single turn. public static readonly processingTime = 50; @@ -53,7 +50,7 @@ export class DeltaScheduler { | undefined; constructor( - deltaManager: IDeltaManager, + deltaManager: IDeltaManagerFull, private readonly logger: ITelemetryLoggerExt, ) { this.deltaManager = deltaManager; diff --git a/packages/runtime/container-runtime/src/packageVersion.ts b/packages/runtime/container-runtime/src/packageVersion.ts index 58e1c247abf5..58978960d399 100644 --- a/packages/runtime/container-runtime/src/packageVersion.ts +++ b/packages/runtime/container-runtime/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/container-runtime"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/runtime/container-runtime/src/scheduleManager.ts b/packages/runtime/container-runtime/src/scheduleManager.ts index 6198674303cc..e8ad6c9b671a 100644 --- a/packages/runtime/container-runtime/src/scheduleManager.ts +++ b/packages/runtime/container-runtime/src/scheduleManager.ts @@ -5,7 +5,7 @@ import type { EventEmitter } from "@fluid-internal/client-utils"; import { performance } from "@fluid-internal/client-utils"; -import { IDeltaManager } from "@fluidframework/container-definitions/internal"; +import { IDeltaManagerFull } from "@fluidframework/container-definitions/internal"; import { assert } from "@fluidframework/core-utils/internal"; import { IDocumentMessage, @@ -43,7 +43,7 @@ export class ScheduleManager { private readonly deltaScheduler: DeltaScheduler; constructor( - private readonly deltaManager: IDeltaManager, + private readonly deltaManager: IDeltaManagerFull, private readonly emitter: EventEmitter, readonly getClientId: () => string | undefined, private readonly logger: ITelemetryLoggerExt, @@ -78,7 +78,7 @@ class ScheduleManagerCore { private batchCount = 0; constructor( - private readonly deltaManager: IDeltaManager, + private readonly deltaManager: IDeltaManagerFull, private readonly getClientId: () => string | undefined, private readonly logger: ITelemetryLoggerExt, ) { diff --git a/packages/runtime/container-runtime/src/test/blobManager.stashed.spec.ts b/packages/runtime/container-runtime/src/test/blobManager.stashed.spec.ts index 7103385cd624..29de426129c4 100644 --- a/packages/runtime/container-runtime/src/test/blobManager.stashed.spec.ts +++ b/packages/runtime/container-runtime/src/test/blobManager.stashed.spec.ts @@ -5,14 +5,19 @@ import { strict as assert } from "assert"; -import { bufferToString } from "@fluid-internal/client-utils"; +import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils"; import { generatePairwiseOptions } from "@fluid-private/test-pairwise-generator"; +import { AttachState } from "@fluidframework/container-definitions"; import { Deferred } from "@fluidframework/core-utils/internal"; import { ICreateBlobResponse } from "@fluidframework/driver-definitions/internal"; import type { IDocumentStorageService } from "@fluidframework/driver-definitions/internal"; import { createChildLogger } from "@fluidframework/telemetry-utils/internal"; import { BlobManager, IBlobManagerRuntime, type IPendingBlobs } from "../blobManager/index.js"; +import { + ContainerFluidHandleContext, + IContainerHandleContextRuntime, +} from "../containerHandleContext.js"; export const failProxy = (handler: Partial = {}) => { const proxy: T = new Proxy(handler as T, { @@ -31,12 +36,22 @@ export const failProxy = (handler: Partial = {}) => { }; function createBlobManager(overrides?: Partial[0]>) { + const runtime = failProxy({ + baseLogger: createChildLogger(), + attachState: AttachState.Attached, + resolveHandle: async () => { + throw new Error("not implemented"); + }, + }); + const routeContext = new ContainerFluidHandleContext("/", runtime, undefined); return new BlobManager( failProxy({ // defaults, these can still be overridden below - runtime: failProxy({ baseLogger: createChildLogger() }), + runtime, + routeContext, snapshot: {}, stashedBlobs: undefined, + localBlobIdGenerator: undefined, // overrides ...overrides, @@ -71,6 +86,99 @@ describe("BlobManager.stashed", () => { }); it("Stashed blob", async () => { + const createResponse = new Deferred(); + const blobManager = createBlobManager({ + sendBlobAttachOp(_localId, _storageId) {}, + stashedBlobs: {}, + getStorage: () => + failProxy({ + createBlob: async () => { + return createResponse.promise; + }, + }), + }); + const blob: ArrayBufferLike = stringToBuffer("content", "utf8"); + const sameBlobAsStashedP = blobManager.createBlob(blob); + const pendingBlobsP = blobManager.attachAndGetPendingBlobs(); + const sameBlobAsStashed = await sameBlobAsStashedP; + sameBlobAsStashed.attachGraph(); + const pendingBlobs = await pendingBlobsP; + const blobManager2 = createBlobManager({ + stashedBlobs: pendingBlobs, + getStorage: () => + failProxy({ + createBlob: async () => { + return createResponse.promise; + }, + }), + }); + assert.strictEqual(blobManager2.hasPendingStashedUploads(), true); + }); + + it("Repro bug: Process blob and complete stashed upload after", async () => { + const createResponse = new Deferred(); + const blobManager = createBlobManager({ + sendBlobAttachOp(_localId, _storageId) {}, + stashedBlobs: {}, + getStorage: () => + failProxy({ + createBlob: async () => { + return createResponse.promise; + }, + }), + localBlobIdGenerator: () => "stubbed-local-id", + }); + + const blob: ArrayBufferLike = stringToBuffer("content", "utf8"); + const sameBlobAsStashedP = blobManager.createBlob(blob); + const pendingBlobsP = blobManager.attachAndGetPendingBlobs(); + const sameBlobAsStashed = await sameBlobAsStashedP; + sameBlobAsStashed.attachGraph(); + const pendingBlobs = await pendingBlobsP; + + const createResponse2 = new Deferred(); + const blobManager2 = createBlobManager({ + stashedBlobs: pendingBlobs, + getStorage: () => + failProxy({ + createBlob: async () => { + return createResponse2.promise; + }, + }), + }); + assert.strictEqual(blobManager2.hasPendingStashedUploads(), true); + + blobManager2.processBlobAttachMessage( + { + clientId: "client-id", + minimumSequenceNumber: 1, + referenceSequenceNumber: 1, + sequenceNumber: 1, + type: "blobAttach", + metadata: { + localId: "stubbed-local-id", + blobId: "stubbed-storage-id", + }, + timestamp: Date.now(), + }, + true, + ); + await Promise.race([ + new Promise((resolve) => setTimeout(() => resolve(), 10)), + blobManager2.trackPendingStashedUploads(), + ]); + createResponse2.resolve({ + id: "new-storage-id", + }); + try { + await blobManager2.waitForStashedBlobs(); + assert.fail("Should have thrown"); + } catch (error: any) { + assert.strictEqual(error.message, "0x6c8"); + } + }); + + it("Already stashed blob", async () => { const createResponse = new Deferred(); const blobManager = createBlobManager({ stashedBlobs: { diff --git a/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts index 8953a4b2a1d1..30995d5eef1c 100644 --- a/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts +++ b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts @@ -31,17 +31,8 @@ declare type old_as_current_for_Class_ContainerRuntime = requireAssignableTo, TypeOnly> - -/* - * Validate forward compatibility by using the old type in place of the current type. - * If this test starts failing, it indicates a change that is not forward compatible. - * To acknowledge the breaking change, add the following to package.json under - * typeValidation.broken: - * "Class_DocumentsSchemaController": {"forwardCompat": false} - */ // @ts-expect-error compatibility expected to be broken -declare type old_as_current_for_Class_DocumentsSchemaController = requireAssignableTo, TypeOnly> +declare type current_as_old_for_Class_ContainerRuntime = requireAssignableTo, TypeOnly> /* * Validate backward compatibility by using the current type in place of the old type. @@ -95,6 +86,7 @@ declare type current_as_old_for_Class_SummaryCollection = requireAssignableTo, TypeOnly> /* @@ -869,6 +861,7 @@ declare type old_as_current_for_Interface_ISummarizerRuntime = requireAssignable * typeValidation.broken: * "Interface_ISummarizerRuntime": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ISummarizerRuntime = requireAssignableTo, TypeOnly> /* @@ -1103,6 +1096,7 @@ declare type old_as_current_for_Interface_LoadContainerRuntimeParams = requireAs * typeValidation.broken: * "Interface_LoadContainerRuntimeParams": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_LoadContainerRuntimeParams = requireAssignableTo, TypeOnly> /* diff --git a/packages/runtime/datastore-definitions/CHANGELOG.md b/packages/runtime/datastore-definitions/CHANGELOG.md index 67b074a146dc..4e458dad2658 100644 --- a/packages/runtime/datastore-definitions/CHANGELOG.md +++ b/packages/runtime/datastore-definitions/CHANGELOG.md @@ -1,5 +1,23 @@ # @fluidframework/datastore-definitions +## 2.5.0 + +### Minor Changes + +- The op event on IFluidDataStoreRuntimeEvents and IContainerRuntimeBaseEvents is emitted at a different time ([#22840](https://github.com/microsoft/FluidFramework/pull/22840)) [2e5b969d3a](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + + Previously, in versions 2.4 and below, the `op` event was emitted immediately after an op was processed and before the next op was processed. + + In versions 2.5.0 and beyond, the `op` event will be emitted after an op is processed, but it may not be immediate. In addition, other ops in a + batch may be processed before the op event is emitted for a particular op. + +- The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated ([#22840](https://github.com/microsoft/FluidFramework/pull/22840)) [2e5b969d3a](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + + The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now + deprecated. It has been replaced with a new function `processMessages`, which will be called to process multiple messages instead of a single one on the channel. This is part of a feature called "Op bunching", where contiguous ops of a given type and to a given data store / DDS are bunched and sent together for processing. + + Implementations of `IFluidDataStoreChannel` and `IDeltaHandler` must now also implement `processMessages`. For reference implementations, see `FluidDataStoreRuntime::processMessages` and `SharedObjectCore::attachDeltaHandler`. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/datastore-definitions/package.json b/packages/runtime/datastore-definitions/package.json index 9d3d9a4dc1e7..abc99844386f 100644 --- a/packages/runtime/datastore-definitions/package.json +++ b/packages/runtime/datastore-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/datastore-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid data store definitions", "homepage": "https://fluidframework.com", "repository": { @@ -90,10 +90,10 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/datastore-definitions-previous": "npm:@fluidframework/datastore-definitions@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/datastore-definitions-previous": "npm:@fluidframework/datastore-definitions@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", diff --git a/packages/runtime/datastore/CHANGELOG.md b/packages/runtime/datastore/CHANGELOG.md index 7f1db9b8499e..b053b8ba50a3 100644 --- a/packages/runtime/datastore/CHANGELOG.md +++ b/packages/runtime/datastore/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/datastore +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/datastore/package.json b/packages/runtime/datastore/package.json index 0257159f46e2..8024a2c645ff 100644 --- a/packages/runtime/datastore/package.json +++ b/packages/runtime/datastore/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/datastore", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid data store implementation", "homepage": "https://fluidframework.com", "repository": { @@ -136,10 +136,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/datastore-previous": "npm:@fluidframework/datastore@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/datastore-previous": "npm:@fluidframework/datastore@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", @@ -162,6 +162,10 @@ "typeValidation": { "broken": { "Class_FluidDataStoreRuntime": { + "backCompat": false + }, + "ClassStatics_FluidDataStoreRuntime": { + "backCompat": false, "forwardCompat": false } }, diff --git a/packages/runtime/datastore/src/dataStoreRuntime.ts b/packages/runtime/datastore/src/dataStoreRuntime.ts index b8fcddabf635..05a3dacde046 100644 --- a/packages/runtime/datastore/src/dataStoreRuntime.ts +++ b/packages/runtime/datastore/src/dataStoreRuntime.ts @@ -689,7 +689,7 @@ export class FluidDataStoreRuntime // process the last set of channel ops const channelContext = this.contexts.get(currentAddress); - assert(!!channelContext, "Channel context not found"); + assert(!!channelContext, 0xa6b /* Channel context not found */); channelContext.processMessages({ envelope: messageCollection.envelope, diff --git a/packages/runtime/datastore/src/packageVersion.ts b/packages/runtime/datastore/src/packageVersion.ts index 8340e64ec3a4..7a7c92840ff6 100644 --- a/packages/runtime/datastore/src/packageVersion.ts +++ b/packages/runtime/datastore/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/datastore"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/runtime/datastore/src/remoteChannelContext.ts b/packages/runtime/datastore/src/remoteChannelContext.ts index 7f97c9aa2399..ce5c78a4d65b 100644 --- a/packages/runtime/datastore/src/remoteChannelContext.ts +++ b/packages/runtime/datastore/src/remoteChannelContext.ts @@ -105,7 +105,10 @@ export class RemoteChannelContext implements IChannelContext { this.id, ); - assert(this.pendingMessagesState !== undefined, "pending messages state is undefined"); + assert( + this.pendingMessagesState !== undefined, + 0xa6c /* pending messages state is undefined */, + ); for (const messageCollection of this.pendingMessagesState.messageCollections) { this.services.deltaConnection.processMessages(messageCollection); } @@ -180,7 +183,10 @@ export class RemoteChannelContext implements IChannelContext { this.services.deltaConnection.processMessages(messageCollection); } else { assert(!local, 0x195 /* "Remote channel must not be local when processing op" */); - assert(this.pendingMessagesState !== undefined, "pending messages queue is undefined"); + assert( + this.pendingMessagesState !== undefined, + 0xa6d /* pending messages queue is undefined */, + ); this.pendingMessagesState.messageCollections.push({ ...messageCollection, messagesContent: Array.from(messagesContent), diff --git a/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts b/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts index 5fe3440cb5cc..1e149592219a 100644 --- a/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts +++ b/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts @@ -22,7 +22,6 @@ declare type MakeUnusedImportErrorsGoAway = TypeOnly | MinimalType | Fu * typeValidation.broken: * "Class_FluidDataStoreRuntime": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_Class_FluidDataStoreRuntime = requireAssignableTo, TypeOnly> /* @@ -32,6 +31,7 @@ declare type old_as_current_for_Class_FluidDataStoreRuntime = requireAssignableT * typeValidation.broken: * "Class_FluidDataStoreRuntime": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Class_FluidDataStoreRuntime = requireAssignableTo, TypeOnly> /* @@ -59,6 +59,7 @@ declare type current_as_old_for_Class_FluidObjectHandle = requireAssignableTo, TypeOnly> /* diff --git a/packages/runtime/id-compressor/.eslintrc.cjs b/packages/runtime/id-compressor/.eslintrc.cjs index 5f7bcea7c239..0cfb8c689f85 100644 --- a/packages/runtime/id-compressor/.eslintrc.cjs +++ b/packages/runtime/id-compressor/.eslintrc.cjs @@ -13,6 +13,7 @@ module.exports = { }, rules: { "@typescript-eslint/strict-boolean-expressions": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/runtime/id-compressor/CHANGELOG.md b/packages/runtime/id-compressor/CHANGELOG.md index 462bbc6827f6..28c3ebefdafb 100644 --- a/packages/runtime/id-compressor/CHANGELOG.md +++ b/packages/runtime/id-compressor/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/id-compressor +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/id-compressor/package.json b/packages/runtime/id-compressor/package.json index 1962cd2f681a..183b68d494bf 100644 --- a/packages/runtime/id-compressor/package.json +++ b/packages/runtime/id-compressor/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/id-compressor", - "version": "2.5.0", + "version": "2.10.0", "description": "ID compressor", "homepage": "https://fluidframework.com", "repository": { @@ -143,11 +143,11 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/stochastic-test-utils": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/id-compressor-previous": "npm:@fluidframework/id-compressor@~2.4.0", + "@fluidframework/id-compressor-previous": "npm:@fluidframework/id-compressor@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/runtime/id-compressor/src/packageVersion.ts b/packages/runtime/id-compressor/src/packageVersion.ts index d5ecbae1153d..efceb51d0416 100644 --- a/packages/runtime/id-compressor/src/packageVersion.ts +++ b/packages/runtime/id-compressor/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/id-compressor"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/runtime/runtime-definitions/CHANGELOG.md b/packages/runtime/runtime-definitions/CHANGELOG.md index 1b04e8d27622..04c143e6cfd8 100644 --- a/packages/runtime/runtime-definitions/CHANGELOG.md +++ b/packages/runtime/runtime-definitions/CHANGELOG.md @@ -1,5 +1,23 @@ # @fluidframework/runtime-definitions +## 2.5.0 + +### Minor Changes + +- The op event on IFluidDataStoreRuntimeEvents and IContainerRuntimeBaseEvents is emitted at a different time ([#22840](https://github.com/microsoft/FluidFramework/pull/22840)) [2e5b969d3a](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + + Previously, in versions 2.4 and below, the `op` event was emitted immediately after an op was processed and before the next op was processed. + + In versions 2.5.0 and beyond, the `op` event will be emitted after an op is processed, but it may not be immediate. In addition, other ops in a + batch may be processed before the op event is emitted for a particular op. + +- The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated ([#22840](https://github.com/microsoft/FluidFramework/pull/22840)) [2e5b969d3a](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + + The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now + deprecated. It has been replaced with a new function `processMessages`, which will be called to process multiple messages instead of a single one on the channel. This is part of a feature called "Op bunching", where contiguous ops of a given type and to a given data store / DDS are bunched and sent together for processing. + + Implementations of `IFluidDataStoreChannel` and `IDeltaHandler` must now also implement `processMessages`. For reference implementations, see `FluidDataStoreRuntime::processMessages` and `SharedObjectCore::attachDeltaHandler`. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/runtime-definitions/api-report/runtime-definitions.legacy.alpha.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.legacy.alpha.api.md index 7f570d37f6ed..77e31c6c7307 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.legacy.alpha.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.legacy.alpha.api.md @@ -94,15 +94,8 @@ export interface IContainerRuntimeBase extends IEventProvider & { - contents: unknown; - }) => void): any; - // (undocumented) - (event: "batchEnd", listener: (error: any, op: Omit & { - contents: unknown; - }) => void): any; - // (undocumented) + (event: "batchBegin", listener: (op: Omit) => void): any; + (event: "batchEnd", listener: (error: any, op: Omit) => void): any; (event: "op", listener: (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => void): any; // (undocumented) (event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void): any; @@ -208,8 +201,6 @@ export interface IFluidParentContext extends IProvideFluidHandleContext, Partial deleteChildSummarizerNode(id: string): void; // (undocumented) readonly deltaManager: IDeltaManager; - // @deprecated - ensureNoDataModelChanges(callback: () => T): T; // @deprecated (undocumented) readonly gcThrowOnTombstoneUsage: boolean; // @deprecated (undocumented) diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index 6dc302ea29b1..3f5391f027ff 100644 --- a/packages/runtime/runtime-definitions/package.json +++ b/packages/runtime/runtime-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/runtime-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { @@ -96,11 +96,11 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/runtime-definitions-previous": "npm:@fluidframework/runtime-definitions@~2.4.0", + "@fluidframework/runtime-definitions-previous": "npm:@fluidframework/runtime-definitions@2.5.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", "copyfiles": "^2.4.1", @@ -111,7 +111,17 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IFluidDataStoreContext": { + "backCompat": false + }, + "Interface_IFluidDataStoreContextDetached": { + "backCompat": false + }, + "Interface_IFluidParentContext": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/runtime/runtime-definitions/src/dataStoreContext.ts b/packages/runtime/runtime-definitions/src/dataStoreContext.ts index 0ae2c6a91aa2..ee572dca5846 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -122,28 +122,24 @@ export type VisibilityState = (typeof VisibilityState)[keyof typeof VisibilitySt * @sealed */ export interface IContainerRuntimeBaseEvents extends IEvent { - ( - event: "batchBegin", - listener: ( - op: Omit & { - /** @deprecated Use the "op" event to get details about the message contents */ - contents: unknown; - }, - ) => void, - ); + /** + * Indicates the beginning of an incoming batch of ops + * @param op - The first op in the batch. Can be inspected to learn about the sequence numbers relevant for this batch. + */ + (event: "batchBegin", listener: (op: Omit) => void); + /** + * Indicates the end of an incoming batch of ops + * @param error - If an error occurred while processing the batch, it is provided here. + * @param op - The last op in the batch. Can be inspected to learn about the sequence numbers relevant for this batch. + */ ( event: "batchEnd", - listener: ( - error: any, - op: Omit & { - /** @deprecated Use the "op" event to get details about the message contents */ - contents: unknown; - }, - ) => void, + listener: (error: any, op: Omit) => void, ); /** + * Indicates that an incoming op has been processed. * @param runtimeMessage - tells if op is runtime op. If it is, it was unpacked, i.e. its type and content - * represent internal container runtime type / content. + * represent internal container runtime type / content. i.e. A grouped batch of N ops will result in N "op" events */ (event: "op", listener: (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => void); (event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void); @@ -493,19 +489,6 @@ export interface IFluidParentContext */ getAudience(): IAudience; - /** - * Invokes the given callback and expects that no ops are submitted - * until execution finishes. If an op is submitted, an error will be raised. - * - * Can be disabled by feature gate `Fluid.ContainerRuntime.DisableOpReentryCheck` - * - * @param callback - the callback to be invoked - * - * @deprecated - * // back-compat: to be removed in 2.0 - */ - ensureNoDataModelChanges(callback: () => T): T; - /** * Submits the message to be sent to other clients. * @param type - Type of the message. diff --git a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts index 5df443622887..8d8ca17ec947 100644 --- a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts @@ -211,6 +211,7 @@ declare type old_as_current_for_Interface_IFluidDataStoreContext = requireAssign * typeValidation.broken: * "Interface_IFluidDataStoreContext": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IFluidDataStoreContext = requireAssignableTo, TypeOnly> /* @@ -229,6 +230,7 @@ declare type old_as_current_for_Interface_IFluidDataStoreContextDetached = requi * typeValidation.broken: * "Interface_IFluidDataStoreContextDetached": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IFluidDataStoreContextDetached = requireAssignableTo, TypeOnly> /* @@ -283,6 +285,7 @@ declare type old_as_current_for_Interface_IFluidParentContext = requireAssignabl * typeValidation.broken: * "Interface_IFluidParentContext": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IFluidParentContext = requireAssignableTo, TypeOnly> /* @@ -375,6 +378,24 @@ declare type old_as_current_for_Interface_IProvideFluidDataStoreRegistry = requi */ declare type current_as_old_for_Interface_IProvideFluidDataStoreRegistry = requireAssignableTo, TypeOnly> +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "Interface_IRuntimeMessageCollection": {"backCompat": false} + */ +declare type current_as_old_for_Interface_IRuntimeMessageCollection = requireAssignableTo, TypeOnly> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "Interface_IRuntimeMessagesContent": {"backCompat": false} + */ +declare type current_as_old_for_Interface_IRuntimeMessagesContent = requireAssignableTo, TypeOnly> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. @@ -681,6 +702,24 @@ declare type old_as_current_for_TypeAlias_InboundAttachMessage = requireAssignab */ declare type current_as_old_for_TypeAlias_InboundAttachMessage = requireAssignableTo, TypeOnly> +/* + * Validate forward compatibility by using the old type in place of the current type. + * If this test starts failing, it indicates a change that is not forward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_ISequencedMessageEnvelope": {"forwardCompat": false} + */ +declare type old_as_current_for_TypeAlias_ISequencedMessageEnvelope = requireAssignableTo, TypeOnly> + +/* + * Validate backward compatibility by using the current type in place of the old type. + * If this test starts failing, it indicates a change that is not backward compatible. + * To acknowledge the breaking change, add the following to package.json under + * typeValidation.broken: + * "TypeAlias_ISequencedMessageEnvelope": {"backCompat": false} + */ +declare type current_as_old_for_TypeAlias_ISequencedMessageEnvelope = requireAssignableTo, TypeOnly> + /* * Validate forward compatibility by using the old type in place of the current type. * If this test starts failing, it indicates a change that is not forward compatible. diff --git a/packages/runtime/runtime-utils/.eslintrc.cjs b/packages/runtime/runtime-utils/.eslintrc.cjs index 3514825ac914..c489a4dd5f2d 100644 --- a/packages/runtime/runtime-utils/.eslintrc.cjs +++ b/packages/runtime/runtime-utils/.eslintrc.cjs @@ -11,4 +11,7 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/packages/runtime/runtime-utils/CHANGELOG.md b/packages/runtime/runtime-utils/CHANGELOG.md index cf229433d967..df7322339220 100644 --- a/packages/runtime/runtime-utils/CHANGELOG.md +++ b/packages/runtime/runtime-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/runtime-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.beta.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.beta.api.md index 308711073b99..4008d92542f5 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.beta.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.beta.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean; + // @public export function isFluidHandle(value: unknown): value is IFluidHandle; diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md index d3effa8b6fee..ef220d91ce46 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean; + // @alpha export function convertToSummaryTreeWithStats(snapshot: ITree, fullTree?: boolean): ISummaryTreeWithStats; @@ -63,19 +66,15 @@ export abstract class RuntimeFactoryHelper implements IRu abstract preInitialize(context: IContainerContext, existing: boolean): Promise; } -// @alpha (undocumented) +// @alpha export class SummaryTreeBuilder implements ISummaryTreeWithStats { constructor(params?: { groupId?: string; }); - // (undocumented) addAttachment(id: string): void; - // (undocumented) addBlob(key: string, content: string | Uint8Array): void; addHandle(key: string, handleType: SummaryType.Tree | SummaryType.Blob | SummaryType.Attachment, handle: string): void; - // (undocumented) addWithStats(key: string, summarizeResult: ISummarizeResult): void; - // (undocumented) getSummaryTree(): ISummaryTreeWithStats; // (undocumented) get stats(): Readonly; diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.public.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.public.api.md index 29c6c128ec7c..6934941a60d0 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.public.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.public.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean; + // @public export function isFluidHandle(value: unknown): value is IFluidHandle; diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.public.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.public.api.md index 29c6c128ec7c..6934941a60d0 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.public.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.public.api.md @@ -4,6 +4,9 @@ ```ts +// @public +export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean; + // @public export function isFluidHandle(value: unknown): value is IFluidHandle; diff --git a/packages/runtime/runtime-utils/package.json b/packages/runtime/runtime-utils/package.json index be3901b576d7..7ce8404b3edb 100644 --- a/packages/runtime/runtime-utils/package.json +++ b/packages/runtime/runtime-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/runtime-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Collection of utility functions for Fluid Runtime", "homepage": "https://fluidframework.com", "repository": { @@ -131,11 +131,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/runtime-utils-previous": "npm:@fluidframework/runtime-utils@~2.4.0", + "@fluidframework/runtime-utils-previous": "npm:@fluidframework/runtime-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/runtime/runtime-utils/src/handles.ts b/packages/runtime/runtime-utils/src/handles.ts index 12fd5f73f241..a65dbee02dbe 100644 --- a/packages/runtime/runtime-utils/src/handles.ts +++ b/packages/runtime/runtime-utils/src/handles.ts @@ -64,6 +64,18 @@ export function isFluidHandle(value: unknown): value is IFluidHandle { return false; } +/** + * Compare two {@link @fluidframework/core-interfaces#IFluidHandle|IFluidHandles}. + * @remarks + * Returns true iff both handles have the same internal `absolutePath`. + * @public + */ +export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean { + const aInternal = toFluidHandleInternal(a); + const bInternal = toFluidHandleInternal(b); + return aInternal.absolutePath === bInternal.absolutePath; +} + /** * Downcast an IFluidHandle to an IFluidHandleInternal. * @legacy diff --git a/packages/runtime/runtime-utils/src/index.ts b/packages/runtime/runtime-utils/src/index.ts index 2a2de8048971..ae9272924b59 100644 --- a/packages/runtime/runtime-utils/src/index.ts +++ b/packages/runtime/runtime-utils/src/index.ts @@ -19,6 +19,7 @@ export { toFluidHandleErased, toFluidHandleInternal, FluidHandleBase, + compareFluidHandles, } from "./handles.js"; export { ObjectStoragePartition } from "./objectstoragepartition.js"; export { @@ -46,4 +47,7 @@ export { export { unpackChildNodesUsedRoutes } from "./unpackUsedRoutes.js"; export { ReadAndParseBlob, seqFromTree, encodeCompactIdToString } from "./utils.js"; export { isSnapshotFetchRequiredForLoadingGroupId } from "./snapshotUtils.js"; -export { toDeltaManagerErased, toDeltaManagerInternal } from "./deltaManager.js"; +export { + toDeltaManagerErased, + toDeltaManagerInternal, +} from "./deltaManager.js"; diff --git a/packages/runtime/runtime-utils/src/summaryUtils.ts b/packages/runtime/runtime-utils/src/summaryUtils.ts index d4822ad4c0ad..2e227c1710cf 100644 --- a/packages/runtime/runtime-utils/src/summaryUtils.ts +++ b/packages/runtime/runtime-utils/src/summaryUtils.ts @@ -159,6 +159,8 @@ export interface SummaryTreeBuilderParams { groupId?: string; } /** + * A helper class for building summary trees. + * @remarks Uses the builder pattern. * @legacy * @alpha */ @@ -190,6 +192,11 @@ export class SummaryTreeBuilder implements ISummaryTreeWithStats { private readonly summaryTree: { [path: string]: SummaryObject } = {}; private summaryStats: ISummaryStats; + /** + * Add a blob to the summary tree. This blob will be stored at the given key in the summary tree. + * @param key - The key to store the blob at in the current summary tree being generated. Should not contain any "/" characters. + * @param content - The content of the blob to be added to the summary tree. + */ public addBlob(key: string, content: string | Uint8Array): void { // Prevent cloning by directly referencing underlying private properties addBlobToSummary( @@ -228,15 +235,32 @@ export class SummaryTreeBuilder implements ISummaryTreeWithStats { this.summaryStats.handleNodeCount++; } + /** + * Adds a child and updates the stats accordingly. + * @param key - The key to store the handle at in the current summary tree being generated. Should not contain any "/" characters. + * The key should be unique within the current summary tree, and not transform when encodeURIComponent is called. + * @param summarizeResult - Similar to {@link @fluidframework/runtime-definitions#ISummaryTreeWithStats}. The provided summary can be either a {@link @fluidframework/driver-definitions#ISummaryHandle} or {@link @fluidframework/driver-definitions#ISummaryTree}. + */ public addWithStats(key: string, summarizeResult: ISummarizeResult): void { this.summaryTree[key] = summarizeResult.summary; this.summaryStats = mergeStats(this.summaryStats, summarizeResult.stats); } + /** + * Adds an {@link @fluidframework/driver-definitions#ISummaryAttachment} to the summary. This blob needs to already be uploaded to storage. + * @param id - The id of the uploaded attachment to be added to the summary tree. + */ public addAttachment(id: string) { this.summaryTree[this.attachmentCounter++] = { id, type: SummaryType.Attachment }; } + /** + * Gives you the in-memory summary tree with stats built by the SummaryTreeBuilder. + * + * @remarks + * Use this once you're done building the summary tree, the stats should automatically be generated. + * @returns The summary tree and stats built by the SummaryTreeBuilder. + */ public getSummaryTree(): ISummaryTreeWithStats { return { summary: this.summary, stats: this.stats }; } diff --git a/packages/runtime/test-runtime-utils/CHANGELOG.md b/packages/runtime/test-runtime-utils/CHANGELOG.md index 8f7b732b9062..693e67d8144e 100644 --- a/packages/runtime/test-runtime-utils/CHANGELOG.md +++ b/packages/runtime/test-runtime-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @fluidframework/test-runtime-utils +## 2.5.0 + +### Minor Changes + +- The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now deprecated ([#22840](https://github.com/microsoft/FluidFramework/pull/22840)) [2e5b969d3a](https://github.com/microsoft/FluidFramework/commit/2e5b969d3a28b05da1502d521b725cee66e36a15) + + The process function on IFluidDataStoreChannel, IDeltaHandler, MockFluidDataStoreRuntime and MockDeltaConnection is now + deprecated. It has been replaced with a new function `processMessages`, which will be called to process multiple messages instead of a single one on the channel. This is part of a feature called "Op bunching", where contiguous ops of a given type and to a given data store / DDS are bunched and sent together for processing. + + Implementations of `IFluidDataStoreChannel` and `IDeltaHandler` must now also implement `processMessages`. For reference implementations, see `FluidDataStoreRuntime::processMessages` and `SharedObjectCore::attachDeltaHandler`. + ## 2.4.0 Dependency updates only. diff --git a/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.legacy.alpha.api.md b/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.legacy.alpha.api.md index f80f0eecc66d..9608746b9726 100644 --- a/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.legacy.alpha.api.md +++ b/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.legacy.alpha.api.md @@ -303,8 +303,6 @@ export class MockFluidDataStoreContext implements IFluidDataStoreContext { // (undocumented) deltaManager: IDeltaManager; // (undocumented) - ensureNoDataModelChanges(callback: () => T): T; - // (undocumented) readonly existing: boolean; // (undocumented) readonly gcThrowOnTombstoneUsage = false; diff --git a/packages/runtime/test-runtime-utils/package.json b/packages/runtime/test-runtime-utils/package.json index e1ec585d3b1e..420f56b9fcc7 100644 --- a/packages/runtime/test-runtime-utils/package.json +++ b/packages/runtime/test-runtime-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/test-runtime-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid runtime test utilities", "homepage": "https://fluidframework.com", "repository": { @@ -134,11 +134,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/test-runtime-utils-previous": "npm:@fluidframework/test-runtime-utils@~2.4.0", + "@fluidframework/test-runtime-utils-previous": "npm:@fluidframework/test-runtime-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/jsrsasign": "^10.5.12", "@types/mocha": "^9.1.1", @@ -158,11 +158,11 @@ }, "typeValidation": { "broken": { - "Class_MockDeltaConnection": { - "forwardCompat": false + "Class_MockFluidDataStoreContext": { + "backCompat": false }, - "Class_MockFluidDataStoreRuntime": { - "forwardCompat": false + "ClassStatics_MockFluidDataStoreContext": { + "backCompat": false } }, "entrypoint": "legacy" diff --git a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts index 7ff31407d81c..78e4bf79178c 100644 --- a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts +++ b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts @@ -211,7 +211,6 @@ export const shortCodeMap = { "0x119": "This agent became inactive while releasing", "0x11a": "Unsuccessful registration", "0x11b": "Trying to clear tasks on inactive agent", - "0x11c": "Detached object routing context", "0x11d": "task is already running", "0x11e": "client is undefined", "0x11f": "requesting unknown blobs", @@ -282,7 +281,6 @@ export const shortCodeMap = { "0x182": "Data store should be attached to attach the channel.", "0x183": "There should be a channel context for the op", "0x184": "There should be a channel context for the op", - "0x185": "Channel not found", "0x189": "Should always be remote because a local dds shouldn't generate ops before loading", "0x18a": "Channel should be loaded to resubmit ops", "0x18d": "Channel should be loaded to take snapshot", @@ -380,9 +378,6 @@ export const shortCodeMap = { "0x236": "In all cases it should be already installed", "0x238": "called only in connected state", "0x23a": "seq#'s", - "0x23d": "pending is undefined", - "0x23e": "pending is undefined", - "0x23f": "pending undefined", "0x241": "Trying to send noop without active connection", "0x242": "has timer", "0x243": "Expected a noop to be synchronously sent", @@ -587,7 +582,6 @@ export const shortCodeMap = { "0x3cd": "Connection is possible only if container exists in storage", "0x3cf": "reentrancy", "0x3d0": "clientSequenceNumber can't be negative", - "0x3d1": "Can't trigger summary in the middle of a batch", "0x3d2": "Non-attached container is dirty", "0x3d3": "if doc is dirty, there has to be pending ops", "0x3d4": "System op in the middle of a batch", @@ -1621,5 +1615,24 @@ export const shortCodeMap = { "0xa57": "Expected EagerMapTree required field to have a value", "0xa58": "Signal must have a client ID", "0xa59": "Client connected without clientId", - "0xa5a": "Mismatched SessionId" + "0xa5a": "Mismatched SessionId", + "0xa5b": "must have exactly 1 field in batch", + "0xa5c": "Unexpected view implementation", + "0xa5d": "The main branch cannot be rebased onto another branch.", + "0xa5e": "Should not access 'change' property of an evicted commit", + "0xa5f": "Should not access 'revision' property of an evicted commit", + "0xa60": "Should not access 'parent' property of an evicted commit", + "0xa61": "Serialized trunk should not include the trunk base", + "0xa62": "Serialized branch should not include the trunk base", + "0xa63": "Commit must be either be on the trunk or be the trunk base", + "0xa64": "Attempted to sequence change with an outdated sequence number", + "0xa65": "documentId is required when multiplexing is enabled.", + "0xa66": "Context not found", + "0xa67": "previous message must exist", + "0xa68": "Channel is not loaded", + "0xa69": "pending messages queue is undefined", + "0xa6a": "pending messages queue is undefined", + "0xa6b": "Channel context not found", + "0xa6c": "pending messages state is undefined", + "0xa6d": "pending messages queue is undefined" }; diff --git a/packages/runtime/test-runtime-utils/src/mocksDataStoreContext.ts b/packages/runtime/test-runtime-utils/src/mocksDataStoreContext.ts index 906291816349..c8b14adacadc 100644 --- a/packages/runtime/test-runtime-utils/src/mocksDataStoreContext.ts +++ b/packages/runtime/test-runtime-utils/src/mocksDataStoreContext.ts @@ -96,11 +96,6 @@ export class MockFluidDataStoreContext implements IFluidDataStoreContext { throw new Error("Method not implemented."); } - // back-compat: to be removed in 2.0 - public ensureNoDataModelChanges(callback: () => T): T { - return callback(); - } - public getQuorum(): IQuorumClients { return undefined as any as IQuorumClients; } diff --git a/packages/runtime/test-runtime-utils/src/test/types/validateTestRuntimeUtilsPrevious.generated.ts b/packages/runtime/test-runtime-utils/src/test/types/validateTestRuntimeUtilsPrevious.generated.ts index 2da5e35506b3..d6f765fb97e6 100644 --- a/packages/runtime/test-runtime-utils/src/test/types/validateTestRuntimeUtilsPrevious.generated.ts +++ b/packages/runtime/test-runtime-utils/src/test/types/validateTestRuntimeUtilsPrevious.generated.ts @@ -112,7 +112,6 @@ declare type current_as_old_for_Class_MockContainerRuntimeForReconnection = requ * typeValidation.broken: * "Class_MockDeltaConnection": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_Class_MockDeltaConnection = requireAssignableTo, TypeOnly> /* @@ -176,6 +175,7 @@ declare type old_as_current_for_Class_MockFluidDataStoreContext = requireAssigna * typeValidation.broken: * "Class_MockFluidDataStoreContext": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Class_MockFluidDataStoreContext = requireAssignableTo, TypeOnly> /* @@ -185,7 +185,6 @@ declare type current_as_old_for_Class_MockFluidDataStoreContext = requireAssigna * typeValidation.broken: * "Class_MockFluidDataStoreRuntime": {"forwardCompat": false} */ -// @ts-expect-error compatibility expected to be broken declare type old_as_current_for_Class_MockFluidDataStoreRuntime = requireAssignableTo, TypeOnly> /* @@ -366,6 +365,7 @@ declare type current_as_old_for_ClassStatics_MockDeltaQueue = requireAssignableT * typeValidation.broken: * "ClassStatics_MockFluidDataStoreContext": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_ClassStatics_MockFluidDataStoreContext = requireAssignableTo, TypeOnly> /* diff --git a/packages/service-clients/azure-client/CHANGELOG.md b/packages/service-clients/azure-client/CHANGELOG.md index 241b7106925f..4f7fdaa1d214 100644 --- a/packages/service-clients/azure-client/CHANGELOG.md +++ b/packages/service-clients/azure-client/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/azure-client +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/service-clients/azure-client/package.json b/packages/service-clients/azure-client/package.json index 9c2e14aca40a..62f295a2b0c3 100644 --- a/packages/service-clients/azure-client/package.json +++ b/packages/service-clients/azure-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-client", - "version": "2.5.0", + "version": "2.10.0", "description": "A tool to enable creation and loading of Fluid containers using the Azure Fluid Relay service", "homepage": "https://fluidframework.com", "repository": { @@ -109,12 +109,12 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/aqueduct": "workspace:~", - "@fluidframework/azure-client-previous": "npm:@fluidframework/azure-client@~2.4.0", + "@fluidframework/azure-client-previous": "npm:@fluidframework/azure-client@2.5.0", "@fluidframework/azure-local-service": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-runtime-utils": "workspace:~", "@fluidframework/test-utils": "workspace:~", diff --git a/packages/service-clients/end-to-end-tests/azure-client/CHANGELOG.md b/packages/service-clients/end-to-end-tests/azure-client/CHANGELOG.md index a657242dfa8c..c56bf91b38f4 100644 --- a/packages/service-clients/end-to-end-tests/azure-client/CHANGELOG.md +++ b/packages/service-clients/end-to-end-tests/azure-client/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/azure-end-to-end-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/service-clients/end-to-end-tests/azure-client/package.json b/packages/service-clients/end-to-end-tests/azure-client/package.json index de979ef8e42e..04c5c9292019 100644 --- a/packages/service-clients/end-to-end-tests/azure-client/package.json +++ b/packages/service-clients/end-to-end-tests/azure-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-end-to-end-tests", - "version": "2.5.0", + "version": "2.10.0", "description": "Azure client end to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -93,7 +93,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/driver-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/mocha": "^9.1.1", diff --git a/packages/service-clients/end-to-end-tests/azure-client/src/test/tree.spec.ts b/packages/service-clients/end-to-end-tests/azure-client/src/test/tree.spec.ts index ca23b3b6993e..e28e96c1c5c8 100644 --- a/packages/service-clients/end-to-end-tests/azure-client/src/test/tree.spec.ts +++ b/packages/service-clients/end-to-end-tests/azure-client/src/test/tree.spec.ts @@ -10,7 +10,13 @@ import { ConnectionState } from "@fluidframework/container-loader"; import { ContainerSchema, type IFluidContainer } from "@fluidframework/fluid-static"; import { timeoutPromise } from "@fluidframework/test-utils/internal"; import { TreeViewConfiguration, SchemaFactory, type TreeView } from "@fluidframework/tree"; -import { SharedTree, Tree, TreeStatus, type Revertible } from "@fluidframework/tree/internal"; +import { + asTreeViewAlpha, + SharedTree, + Tree, + TreeStatus, + type Revertible, +} from "@fluidframework/tree/internal"; import type { AxiosResponse } from "axios"; import { @@ -214,8 +220,10 @@ for (const testOpts of testMatrix) { it("can handle undo/redo and transactions", async () => { const { container } = await client.createContainer(schema, "2"); await container.attach(); - const view = container.initialObjects.tree1.viewWith( - new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }), + const view = asTreeViewAlpha( + container.initialObjects.tree1.viewWith( + new TreeViewConfiguration({ schema: User, enableSchemaValidation: true }), + ), ); view.initialize( @@ -229,7 +237,7 @@ for (const testOpts of testMatrix) { const user = view.root; // Capture the Revertible so that changes can be undone let revertible: Revertible | undefined; - view.events.on("commitApplied", (_, getRevertible) => { + view.events.on("changed", (_, getRevertible) => { assert(getRevertible !== undefined); revertible = getRevertible(); }); diff --git a/packages/service-clients/end-to-end-tests/odsp-client/CHANGELOG.md b/packages/service-clients/end-to-end-tests/odsp-client/CHANGELOG.md index d4b728da36ca..356cbadf67d9 100644 --- a/packages/service-clients/end-to-end-tests/odsp-client/CHANGELOG.md +++ b/packages/service-clients/end-to-end-tests/odsp-client/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/odsp-end-to-end-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/service-clients/end-to-end-tests/odsp-client/package.json b/packages/service-clients/end-to-end-tests/odsp-client/package.json index bbe04bd73a19..686189c5ad8d 100644 --- a/packages/service-clients/end-to-end-tests/odsp-client/package.json +++ b/packages/service-clients/end-to-end-tests/odsp-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/odsp-end-to-end-tests", - "version": "2.5.0", + "version": "2.10.0", "description": "Odsp client end to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -80,7 +80,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/mocha": "^9.1.1", "@types/nock": "^9.3.0", diff --git a/packages/service-clients/odsp-client/CHANGELOG.md b/packages/service-clients/odsp-client/CHANGELOG.md index fb08a96c1495..e44140f336ff 100644 --- a/packages/service-clients/odsp-client/CHANGELOG.md +++ b/packages/service-clients/odsp-client/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-experimental/odsp-client +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/service-clients/odsp-client/package.json b/packages/service-clients/odsp-client/package.json index 3f4be0c9de32..a59175cdecfb 100644 --- a/packages/service-clients/odsp-client/package.json +++ b/packages/service-clients/odsp-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/odsp-client", - "version": "2.5.0", + "version": "2.10.0", "description": "A tool to enable creation and loading of Fluid containers using the ODSP service", "homepage": "https://fluidframework.com", "repository": { @@ -123,9 +123,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-utils": "workspace:~", "@microsoft/api-extractor": "7.47.8", diff --git a/packages/service-clients/tinylicious-client/CHANGELOG.md b/packages/service-clients/tinylicious-client/CHANGELOG.md index 20038ae75c5a..798cefeb1e30 100644 --- a/packages/service-clients/tinylicious-client/CHANGELOG.md +++ b/packages/service-clients/tinylicious-client/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/tinylicious-client +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/service-clients/tinylicious-client/package.json b/packages/service-clients/tinylicious-client/package.json index 68848796bebc..d9bcf29bf2b2 100644 --- a/packages/service-clients/tinylicious-client/package.json +++ b/packages/service-clients/tinylicious-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/tinylicious-client", - "version": "2.5.0", + "version": "2.10.0", "description": "A tool to enable creation and loading of Fluid containers using the Tinylicious service", "homepage": "https://fluidframework.com", "repository": { @@ -103,15 +103,15 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/aqueduct": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-runtime": "workspace:~", "@fluidframework/container-runtime-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-utils": "workspace:~", - "@fluidframework/tinylicious-client-previous": "npm:@fluidframework/tinylicious-client@~2.4.0", + "@fluidframework/tinylicious-client-previous": "npm:@fluidframework/tinylicious-client@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/test/functional-tests/.eslintrc.cjs b/packages/test/functional-tests/.eslintrc.cjs index 059ee119c76a..815f87590ff4 100644 --- a/packages/test/functional-tests/.eslintrc.cjs +++ b/packages/test/functional-tests/.eslintrc.cjs @@ -8,6 +8,9 @@ module.exports = { require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), "prettier", ], + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, overrides: [ { // Rules only for test files diff --git a/packages/test/functional-tests/CHANGELOG.md b/packages/test/functional-tests/CHANGELOG.md index ea760de88dac..6d87748c92ea 100644 --- a/packages/test/functional-tests/CHANGELOG.md +++ b/packages/test/functional-tests/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/functional-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/functional-tests/package.json b/packages/test/functional-tests/package.json index fd7a171e5d38..6f7f510270ce 100644 --- a/packages/test/functional-tests/package.json +++ b/packages/test/functional-tests/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/functional-tests", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Functional tests", "homepage": "https://fluidframework.com", @@ -64,9 +64,9 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-dds-utils": "workspace:~", "@fluid-private/test-loader-utils": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/cell": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime": "workspace:~", diff --git a/packages/test/local-server-tests/.eslintrc.cjs b/packages/test/local-server-tests/.eslintrc.cjs index 0f52aba1346d..13a408a0973b 100644 --- a/packages/test/local-server-tests/.eslintrc.cjs +++ b/packages/test/local-server-tests/.eslintrc.cjs @@ -11,6 +11,15 @@ module.exports = { rules: { "@typescript-eslint/strict-boolean-expressions": "off", // requires strictNullChecks=true in tsconfig "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + "import/no-extraneous-dependencies": [ + "error", + { + // This package is only used to run its tests. It's ok for the src/utils.ts to import from devDependencies, in + // addition to the test files + devDependencies: ["src/utils.ts", "src/test/**"], + }, + ], }, parserOptions: { project: ["./src/test/tsconfig.json"], diff --git a/packages/test/local-server-tests/CHANGELOG.md b/packages/test/local-server-tests/CHANGELOG.md index a613e258415e..75c9d52c3054 100644 --- a/packages/test/local-server-tests/CHANGELOG.md +++ b/packages/test/local-server-tests/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/local-server-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/local-server-tests/package.json b/packages/test/local-server-tests/package.json index 5fac2437a453..99f962c78f56 100644 --- a/packages/test/local-server-tests/package.json +++ b/packages/test/local-server-tests/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/local-server-tests", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Tests that can only run against the local server", "homepage": "https://fluidframework.com", @@ -55,17 +55,15 @@ ], "temp-directory": "nyc/.nyc_output" }, - "dependencies": { + "dependencies": {}, + "devDependencies": { + "@biomejs/biome": "~1.9.3", "@fluid-experimental/tree": "workspace:~", - "@fluid-internal/client-utils": "workspace:~", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-private/test-drivers": "workspace:~", - "@fluid-private/test-loader-utils": "workspace:~", - "@fluid-private/test-pairwise-generator": "workspace:~", "@fluidframework/aqueduct": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/cell": "workspace:~", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime": "workspace:~", @@ -74,49 +72,29 @@ "@fluidframework/core-utils": "workspace:~", "@fluidframework/datastore": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", - "@fluidframework/driver-base": "workspace:~", "@fluidframework/driver-definitions": "workspace:~", "@fluidframework/driver-utils": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/local-driver": "workspace:~", "@fluidframework/map": "workspace:~", - "@fluidframework/matrix": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", - "@fluidframework/odsp-doclib-utils": "workspace:~", - "@fluidframework/ordered-collection": "workspace:~", - "@fluidframework/protocol-definitions": "^3.2.0", - "@fluidframework/register-collection": "workspace:~", - "@fluidframework/request-handler": "workspace:~", - "@fluidframework/routerlicious-driver": "workspace:~", "@fluidframework/runtime-definitions": "workspace:~", "@fluidframework/runtime-utils": "workspace:~", "@fluidframework/sequence": "workspace:~", "@fluidframework/server-local-server": "^5.0.0", - "@fluidframework/shared-object-base": "workspace:~", "@fluidframework/telemetry-utils": "workspace:~", "@fluidframework/test-utils": "workspace:~", - "@fluidframework/tree": "workspace:~" - }, - "devDependencies": { - "@biomejs/biome": "~1.9.3", + "@fluidframework/tree": "workspace:~", "@types/mocha": "^9.1.1", - "@types/nock": "^9.3.0", "@types/node": "^18.19.0", - "@types/uuid": "^9.0.2", "c8": "^8.0.1", "cross-env": "^7.0.3", "eslint": "~8.55.0", "mocha": "^10.2.0", "mocha-multi-reporters": "^1.5.1", - "moment": "^2.21.0", - "nock": "^13.3.3", "prettier": "~3.0.3", "rimraf": "^4.4.0", "ts-loader": "^9.5.1", - "typescript": "~5.4.5", - "uuid": "^9.0.0", - "webpack": "^5.94.0", - "webpack-cli": "^5.1.4" + "typescript": "~5.4.5" }, "fluidBuild": { "tasks": { diff --git a/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts b/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts index 203b55a819b5..b88ffee67ac5 100644 --- a/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts +++ b/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts @@ -36,6 +36,7 @@ import { LoaderContainerTracker, LocalCodeLoader, TestFluidObjectFactory, + toIDeltaManagerFull, createAndAttachContainer, waitForContainerConnection, } from "@fluidframework/test-utils/internal"; @@ -654,7 +655,7 @@ describe("Ops on Reconnect", () => { // At this point, the delta manager should have the messages // in its buffer but not in its outbound queue, // as ops have not been flushed yet - assert.strictEqual(container1.deltaManager.outbound.length, 0); + assert.strictEqual(toIDeltaManagerFull(container1.deltaManager).outbound.length, 0); assert.deepStrictEqual(receivedValues, [], "Values have been sent unexpectedly"); // Wait for the Container to get reconnected. diff --git a/packages/test/mocha-test-setup/CHANGELOG.md b/packages/test/mocha-test-setup/CHANGELOG.md index 55df7adda72d..7b6187f3abf1 100644 --- a/packages/test/mocha-test-setup/CHANGELOG.md +++ b/packages/test/mocha-test-setup/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/mocha-test-setup +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/mocha-test-setup/package.json b/packages/test/mocha-test-setup/package.json index 25bf47db0fbc..ebc1f950e289 100644 --- a/packages/test/mocha-test-setup/package.json +++ b/packages/test/mocha-test-setup/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/mocha-test-setup", - "version": "2.5.0", + "version": "2.10.0", "description": "Utilities for Fluid tests", "homepage": "https://fluidframework.com", "repository": { @@ -65,9 +65,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/test/mocha-test-setup/src/packageVersion.ts b/packages/test/mocha-test-setup/src/packageVersion.ts index d4bcd73d9af7..ebbb60baa41c 100644 --- a/packages/test/mocha-test-setup/src/packageVersion.ts +++ b/packages/test/mocha-test-setup/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-internal/mocha-test-setup"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/snapshots/CHANGELOG.md b/packages/test/snapshots/CHANGELOG.md index 7911806bf31e..14e0ae9ab2d6 100644 --- a/packages/test/snapshots/CHANGELOG.md +++ b/packages/test/snapshots/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/test-snapshots +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/snapshots/package.json b/packages/test/snapshots/package.json index a09c6d3252bb..01849d705d82 100644 --- a/packages/test/snapshots/package.json +++ b/packages/test/snapshots/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-snapshots", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Comprehensive test of snapshot logic.", "homepage": "https://fluidframework.com", @@ -89,9 +89,9 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/test/snapshots/src/packageVersion.ts b/packages/test/snapshots/src/packageVersion.ts index 89a720dbf251..7c3d2eb39489 100644 --- a/packages/test/snapshots/src/packageVersion.ts +++ b/packages/test/snapshots/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-internal/test-snapshots"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/stochastic-test-utils/CHANGELOG.md b/packages/test/stochastic-test-utils/CHANGELOG.md index 0f3119ed78d1..7d4213e0fcbd 100644 --- a/packages/test/stochastic-test-utils/CHANGELOG.md +++ b/packages/test/stochastic-test-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/stochastic-test-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/stochastic-test-utils/package.json b/packages/test/stochastic-test-utils/package.json index ae897402b8b8..600dd52792ac 100644 --- a/packages/test/stochastic-test-utils/package.json +++ b/packages/test/stochastic-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/stochastic-test-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Utilities for stochastic tests", "homepage": "https://fluidframework.com", "repository": { @@ -101,9 +101,9 @@ "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", "@fluid-tools/benchmark": "^0.50.0", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/test/test-driver-definitions/CHANGELOG.md b/packages/test/test-driver-definitions/CHANGELOG.md index 779b3c835851..996e35a22ceb 100644 --- a/packages/test/test-driver-definitions/CHANGELOG.md +++ b/packages/test/test-driver-definitions/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/test-driver-definitions +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-driver-definitions/package.json b/packages/test/test-driver-definitions/package.json index 9fb3a318455a..f72a6e3f65a9 100644 --- a/packages/test/test-driver-definitions/package.json +++ b/packages/test/test-driver-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-driver-definitions", - "version": "2.5.0", + "version": "2.10.0", "description": "A driver abstraction and implementations for testing against server", "homepage": "https://fluidframework.com", "repository": { @@ -57,9 +57,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "concurrently": "^8.2.1", diff --git a/packages/test/test-drivers/.eslintrc.cjs b/packages/test/test-drivers/.eslintrc.cjs index a8f5cb2c58b2..f35e95f32e54 100644 --- a/packages/test/test-drivers/.eslintrc.cjs +++ b/packages/test/test-drivers/.eslintrc.cjs @@ -13,5 +13,6 @@ module.exports = { }, rules: { "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/test/test-drivers/CHANGELOG.md b/packages/test/test-drivers/CHANGELOG.md index 30946c64372e..1e506a2b48b6 100644 --- a/packages/test/test-drivers/CHANGELOG.md +++ b/packages/test/test-drivers/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-drivers +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-drivers/package.json b/packages/test/test-drivers/package.json index 0a031b0b09ad..8360abefe6f8 100644 --- a/packages/test/test-drivers/package.json +++ b/packages/test/test-drivers/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-drivers", - "version": "2.5.0", + "version": "2.10.0", "description": "A driver abstraction and implementations for testing against server", "homepage": "https://fluidframework.com", "repository": { @@ -77,9 +77,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/node": "^18.19.0", diff --git a/packages/test/test-drivers/src/packageVersion.ts b/packages/test/test-drivers/src/packageVersion.ts index b4ab3112ba62..3bcacaf561e7 100644 --- a/packages/test/test-drivers/src/packageVersion.ts +++ b/packages/test/test-drivers/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-private/test-drivers"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/test-end-to-end-tests/.eslintrc.cjs b/packages/test/test-end-to-end-tests/.eslintrc.cjs index 083349fd9098..f7af4907730c 100644 --- a/packages/test/test-end-to-end-tests/.eslintrc.cjs +++ b/packages/test/test-end-to-end-tests/.eslintrc.cjs @@ -14,6 +14,7 @@ module.exports = { rules: { "prefer-arrow-callback": "off", "@typescript-eslint/strict-boolean-expressions": "off", // requires strictNullChecks=true in tsconfig + "@fluid-internal/fluid/no-unchecked-record-access": "warn", // This library is used in the browser, so we don't want dependencies on most node libraries. "import/no-nodejs-modules": ["error"], diff --git a/packages/test/test-end-to-end-tests/CHANGELOG.md b/packages/test/test-end-to-end-tests/CHANGELOG.md index fdb953241450..3c3c3d1d7f93 100644 --- a/packages/test/test-end-to-end-tests/CHANGELOG.md +++ b/packages/test/test-end-to-end-tests/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-end-to-end-tests +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-end-to-end-tests/package.json b/packages/test/test-end-to-end-tests/package.json index 8f13407b8595..c7e3ac41b494 100644 --- a/packages/test/test-end-to-end-tests/package.json +++ b/packages/test/test-end-to-end-tests/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-end-to-end-tests", - "version": "2.5.0", + "version": "2.10.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -132,9 +132,9 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/protocol-definitions": "^3.2.0", "@types/mocha": "^9.1.1", diff --git a/packages/test/test-end-to-end-tests/src/packageVersion.ts b/packages/test/test-end-to-end-tests/src/packageVersion.ts index 428d68344508..4e4109e904e8 100644 --- a/packages/test/test-end-to-end-tests/src/packageVersion.ts +++ b/packages/test/test-end-to-end-tests/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-private/test-end-to-end-tests"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/test-end-to-end-tests/src/test/benchmark/opCriticalPath.time.spec.ts b/packages/test/test-end-to-end-tests/src/test/benchmark/opCriticalPath.time.spec.ts index 9e54b84e46ed..cce7fc9dae47 100644 --- a/packages/test/test-end-to-end-tests/src/test/benchmark/opCriticalPath.time.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/benchmark/opCriticalPath.time.spec.ts @@ -13,6 +13,7 @@ import { DefaultSummaryConfiguration, } from "@fluidframework/container-runtime/internal"; import { + toIDeltaManagerFull, ITestContainerConfig, ITestObjectProvider, timeoutPromise, @@ -72,7 +73,7 @@ describeCompat( sendOps("A"); const opsSent = await timeoutPromise( (resolve) => { - containerRuntime.deltaManager.outbound.once("idle", resolve); + toIDeltaManagerFull(containerRuntime.deltaManager).outbound.once("idle", resolve); }, { errorMsg: "container2 outbound queue never reached idle state" }, ); diff --git a/packages/test/test-end-to-end-tests/src/test/containerDirtyFlag.spec.ts b/packages/test/test-end-to-end-tests/src/test/containerDirtyFlag.spec.ts index 3e3e5d251582..c3a5044a1045 100644 --- a/packages/test/test-end-to-end-tests/src/test/containerDirtyFlag.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/containerDirtyFlag.spec.ts @@ -17,6 +17,7 @@ import { ITestContainerConfig, ITestFluidObject, ITestObjectProvider, + toIDeltaManagerFull, createAndAttachContainer, waitForContainerConnection, } from "@fluidframework/test-utils/internal"; @@ -63,7 +64,10 @@ describeCompat("Container dirty flag", "NoCompat", (getTestObjectProvider, apis) await args.ensureSynchronized(); await args.opProcessingController.pauseProcessing(container); - assert(toDeltaManagerInternal(dataStore.runtime.deltaManager).outbound.paused); + const deltaManagerFull = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore.runtime.deltaManager), + ); + assert(deltaManagerFull.outbound.paused); await cb(container, dataStore, map); diff --git a/packages/test/test-end-to-end-tests/src/test/data-virtualization/loadNewerGroupIdSnapshot.spec.ts b/packages/test/test-end-to-end-tests/src/test/data-virtualization/loadNewerGroupIdSnapshot.spec.ts index 7414a3e5e4ab..b08c54e7218d 100644 --- a/packages/test/test-end-to-end-tests/src/test/data-virtualization/loadNewerGroupIdSnapshot.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/data-virtualization/loadNewerGroupIdSnapshot.spec.ts @@ -26,6 +26,7 @@ import type { ISnapshot, ISnapshotTree } from "@fluidframework/driver-definition import { MockLogger } from "@fluidframework/telemetry-utils/internal"; import { type ITestObjectProvider, + toIDeltaManagerFull, createSummarizerFromFactory, summarizeNow, } from "@fluidframework/test-utils/internal"; @@ -439,7 +440,7 @@ describeCompat( await provider.ensureSynchronized(); // Pause the summarizer2 so we can generate a summary in the future // Note: The summarizing containers don't get added to the loader container tracker, so we manually pause here - await container2.deltaManager.inbound.pause(); + await toIDeltaManagerFull(container2.deltaManager).inbound.pause(); // Send an op dataObjectA._root.set("C", "C"); diff --git a/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts index 768ecd76fcc7..a08eb931de89 100644 --- a/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/detachedContainerTests.spec.ts @@ -41,6 +41,7 @@ import { LocalCodeLoader, SupportedExportInterfaces, TestFluidObjectFactory, + toIDeltaManagerFull, getContainerEntryPointBackCompat, getDataStoreEntryPointBackCompat, timeoutPromise, @@ -115,7 +116,7 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) ); assert.strictEqual(container.closed, false, "Container should be open"); assert.strictEqual( - container.deltaManager.inbound.length, + toIDeltaManagerFull(container.deltaManager).inbound.length, 0, "Inbound queue should be empty", ); @@ -156,7 +157,7 @@ describeCompat("Detached Container", "FullCompat", (getTestObjectProvider, apis) ); assert.strictEqual(container.closed, false, "Container should be open"); assert.strictEqual( - container.deltaManager.inbound.length, + toIDeltaManagerFull(container.deltaManager).inbound.length, 0, "Inbound queue should be empty", ); @@ -931,7 +932,7 @@ describeCompat("Detached Container", "NoCompat", (getTestObjectProvider, apis) = ); assert.strictEqual(container.closed, false, "Container should be open"); assert.strictEqual( - container.deltaManager.inbound.length, + toIDeltaManagerFull(container.deltaManager).inbound.length, 0, "Inbound queue should be empty", ); diff --git a/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts index 472995368fda..1e6512f4a33f 100644 --- a/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts @@ -26,6 +26,7 @@ import { ITestContainerConfig, ITestFluidObject, ITestObjectProvider, + toIDeltaManagerFull, getContainerEntryPointBackCompat, } from "@fluidframework/test-utils/internal"; @@ -1304,18 +1305,18 @@ describeCompat( } async function pauseAllContainers() { - await container1.deltaManager.inbound.pause(); - await container2.deltaManager.inbound.pause(); - await container3.deltaManager.inbound.pause(); + await toIDeltaManagerFull(container1.deltaManager).inbound.pause(); + await toIDeltaManagerFull(container2.deltaManager).inbound.pause(); + await toIDeltaManagerFull(container3.deltaManager).inbound.pause(); - await container1.deltaManager.outbound.pause(); - await container2.deltaManager.outbound.pause(); - await container3.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container1.deltaManager).outbound.pause(); + await toIDeltaManagerFull(container2.deltaManager).outbound.pause(); + await toIDeltaManagerFull(container3.deltaManager).outbound.pause(); } function resumeContainer(c: IContainer) { - c.deltaManager.inbound.resume(); - c.deltaManager.outbound.resume(); + toIDeltaManagerFull(c.deltaManager).inbound.resume(); + toIDeltaManagerFull(c.deltaManager).outbound.resume(); } /** diff --git a/packages/test/test-end-to-end-tests/src/test/fewerBatches.spec.ts b/packages/test/test-end-to-end-tests/src/test/fewerBatches.spec.ts index 7cce237a323d..c7ff88478c1d 100644 --- a/packages/test/test-end-to-end-tests/src/test/fewerBatches.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/fewerBatches.spec.ts @@ -18,6 +18,7 @@ import { FlushModeExperimental, } from "@fluidframework/runtime-definitions/internal"; import { + toIDeltaManagerFull, ChannelFactoryRegistry, DataObjectFactoryType, ITestContainerConfig, @@ -86,9 +87,12 @@ describeCompat("Fewer batches", "NoCompat", (getTestObjectProvider, apis) => { await waitForContainerConnection(localContainer); await waitForContainerConnection(remoteContainer); - localContainer.deltaManager.outbound.on("op", (batch: IDocumentMessage[]) => { - capturedBatches.push(batch); - }); + toIDeltaManagerFull(localContainer.deltaManager).outbound.on( + "op", + (batch: IDocumentMessage[]) => { + capturedBatches.push(batch); + }, + ); await provider.ensureSynchronized(); }; diff --git a/packages/test/test-end-to-end-tests/src/test/gc/gcSweepAttachmentBlobs.spec.ts b/packages/test/test-end-to-end-tests/src/test/gc/gcSweepAttachmentBlobs.spec.ts index a619fc4fb45d..69920693332e 100644 --- a/packages/test/test-end-to-end-tests/src/test/gc/gcSweepAttachmentBlobs.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/gc/gcSweepAttachmentBlobs.spec.ts @@ -35,6 +35,7 @@ import { toFluidHandleInternal } from "@fluidframework/runtime-utils/internal"; import { ITestContainerConfig, ITestObjectProvider, + toIDeltaManagerFull, createSummarizer, createTestConfigProvider, summarizeNow, @@ -1159,7 +1160,7 @@ describeCompat("GC attachment blob sweep tests", "NoCompat", (getTestObjectProvi // Pause the inbound queue so that GC ops are not processed in between failures. This will be resumed // before the final attempt. if (blockInboundGCOp) { - await containerRuntime.deltaManager.inbound.pause(); + await toIDeltaManagerFull(containerRuntime.deltaManager).inbound.pause(); } let summarizeFunc = containerRuntime.summarize; @@ -1175,7 +1176,7 @@ describeCompat("GC attachment blob sweep tests", "NoCompat", (getTestObjectProvi } // If this is the last attempt, resume the inbound queue to let the GC ops (if any) through. if (blockInboundGCOp) { - containerRuntime.deltaManager.inbound.resume(); + toIDeltaManagerFull(containerRuntime.deltaManager).inbound.resume(); } return results; }; diff --git a/packages/test/test-end-to-end-tests/src/test/gc/gcSweepDataStores.spec.ts b/packages/test/test-end-to-end-tests/src/test/gc/gcSweepDataStores.spec.ts index 142618dd5f48..05c7e79eaaa2 100644 --- a/packages/test/test-end-to-end-tests/src/test/gc/gcSweepDataStores.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/gc/gcSweepDataStores.spec.ts @@ -42,6 +42,7 @@ import { import { ITestContainerConfig, ITestObjectProvider, + toIDeltaManagerFull, createSummarizer, createTestConfigProvider, getContainerEntryPointBackCompat, @@ -781,7 +782,7 @@ describeCompat("GC data store sweep tests", "NoCompat", (getTestObjectProvider) // Pause the inbound queue so that GC ops are not processed in between failures. This will be resumed // before the final attempt. if (blockInboundGCOp) { - await containerRuntime.deltaManager.inbound.pause(); + await toIDeltaManagerFull(containerRuntime.deltaManager).inbound.pause(); } let summarizeFunc = containerRuntime.summarize; @@ -797,7 +798,7 @@ describeCompat("GC data store sweep tests", "NoCompat", (getTestObjectProvider) } // If this is the last attempt, resume the inbound queue to let the GC ops (if any) through. if (blockInboundGCOp) { - containerRuntime.deltaManager.inbound.resume(); + toIDeltaManagerFull(containerRuntime.deltaManager).inbound.resume(); } return results; }; diff --git a/packages/test/test-end-to-end-tests/src/test/intervalCollectionEndToEndTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/intervalCollectionEndToEndTests.spec.ts index a5929287701f..efe4420cf547 100644 --- a/packages/test/test-end-to-end-tests/src/test/intervalCollectionEndToEndTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/intervalCollectionEndToEndTests.spec.ts @@ -22,6 +22,7 @@ import { ITestContainerConfig, ITestFluidObject, ITestObjectProvider, + toIDeltaManagerFull, getContainerEntryPointBackCompat, waitForContainerConnection, } from "@fluidframework/test-utils/internal"; @@ -132,7 +133,10 @@ describeCompat( await provider.ensureSynchronized(); await provider.opProcessingController.pauseProcessing(container); - assert(toDeltaManagerInternal(dataStore.runtime.deltaManager).outbound.paused); + const deltaManagerFull = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore.runtime.deltaManager), + ); + assert(deltaManagerFull.outbound.paused); // the "callback" portion of the original e2e test const sharedString = await dataStore.getSharedObject(stringId); diff --git a/packages/test/test-end-to-end-tests/src/test/messageSize.spec.ts b/packages/test/test-end-to-end-tests/src/test/messageSize.spec.ts index b1b78576c46f..08094501a6b8 100644 --- a/packages/test/test-end-to-end-tests/src/test/messageSize.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/messageSize.spec.ts @@ -23,6 +23,7 @@ import type { ISharedMap } from "@fluidframework/map/internal"; import { FlushMode } from "@fluidframework/runtime-definitions/internal"; import { GenericError } from "@fluidframework/telemetry-utils/internal"; import { + toIDeltaManagerFull, ChannelFactoryRegistry, DataObjectFactoryType, ITestContainerConfig, @@ -447,7 +448,7 @@ describeCompat("Message size", "NoCompat", (getTestObjectProvider, apis) => { }); totalPayloadSizeInBytes = 0; totalOps = 0; - localContainer.deltaManager.outbound.on("push", (messages) => { + toIDeltaManagerFull(localContainer.deltaManager).outbound.on("push", (messages) => { totalPayloadSizeInBytes += JSON.stringify(messages).length; totalOps += messages.length; }); @@ -623,13 +624,13 @@ describeCompat("Message size", "NoCompat", (getTestObjectProvider, apis) => { container.disconnect(); container.once("connected", () => { resolve(); - container.deltaManager.outbound.off("op", handler); + toIDeltaManagerFull(container.deltaManager).outbound.off("op", handler); }); container.connect(); } }; - container.deltaManager.outbound.on("op", handler); + toIDeltaManagerFull(container.deltaManager).outbound.on("op", handler); }); }; diff --git a/packages/test/test-end-to-end-tests/src/test/migration-shim/reconnect.spec.ts b/packages/test/test-end-to-end-tests/src/test/migration-shim/reconnect.spec.ts index d31fe4e908c9..e7cff7c15ca3 100644 --- a/packages/test/test-end-to-end-tests/src/test/migration-shim/reconnect.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/migration-shim/reconnect.spec.ts @@ -26,6 +26,7 @@ import { type ConfigTypes, type IConfigProviderBase } from "@fluidframework/core import { type IChannel } from "@fluidframework/datastore-definitions/internal"; import { type ITestObjectProvider, + toIDeltaManagerFull, createSummarizerFromFactory, summarizeNow, waitForContainerConnection, @@ -330,7 +331,7 @@ describeCompat("Stamped v2 ops", "NoCompat", (getTestObjectProvider, apis) => { // generate stashed ops await provider.opProcessingController.pauseProcessing(container1); - await container1.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container1.deltaManager).outbound.pause(); node1.quantity = 1; node1.quantity = 2; node1.quantity = 3; @@ -392,7 +393,7 @@ describeCompat("Stamped v2 ops", "NoCompat", (getTestObjectProvider, apis) => { // generate stashed ops await provider.opProcessingController.pauseProcessing(container2); - await container2.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container2.deltaManager).outbound.pause(); node2.quantity = 1; node2.quantity = 2; node2.quantity = 3; @@ -431,7 +432,7 @@ describeCompat("Stamped v2 ops", "NoCompat", (getTestObjectProvider, apis) => { // generate stashed ops with a migration occurring await provider.opProcessingController.pauseProcessing(container1); - await container1.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container1.deltaManager).outbound.pause(); shim1.submitMigrateOp(); updateQuantity(legacyTree1, 1); @@ -499,7 +500,7 @@ describeCompat("Stamped v2 ops", "NoCompat", (getTestObjectProvider, apis) => { // generate stashed ops with a migration occurring await provider.opProcessingController.pauseProcessing(container1); - await container1.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container1.deltaManager).outbound.pause(); shim1.submitMigrateOp(); const pendingState = await container1.closeAndGetPendingLocalState?.(); diff --git a/packages/test/test-end-to-end-tests/src/test/opReentrancy.spec.ts b/packages/test/test-end-to-end-tests/src/test/opReentrancy.spec.ts index 297067545284..336c0b4aab0c 100644 --- a/packages/test/test-end-to-end-tests/src/test/opReentrancy.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/opReentrancy.spec.ts @@ -18,6 +18,7 @@ import { ITestContainerConfig, ITestFluidObject, ITestObjectProvider, + toIDeltaManagerFull, getContainerEntryPointBackCompat, } from "@fluidframework/test-utils/internal"; @@ -274,8 +275,9 @@ describeCompat( runtimeOptions: {}, }); - await container1.deltaManager.inbound.pause(); - await container1.deltaManager.outbound.pause(); + const container1DeltaManager = toIDeltaManagerFull(container1.deltaManager); + await container1DeltaManager.inbound.pause(); + await container1DeltaManager.outbound.pause(); sharedMap1.on("valueChanged", (changed) => { if (changed.key !== "key2") { @@ -285,8 +287,8 @@ describeCompat( sharedMap1.set("key1", "1"); - container1.deltaManager.inbound.resume(); - container1.deltaManager.outbound.resume(); + container1DeltaManager.inbound.resume(); + container1DeltaManager.outbound.resume(); await provider.ensureSynchronized(); @@ -356,8 +358,9 @@ describeCompat( await setupContainers(testConfig.options, testConfig.featureGates); - await container1.deltaManager.inbound.pause(); - await container1.deltaManager.outbound.pause(); + const deltaManagerFull = toIDeltaManagerFull(container1.deltaManager); + await deltaManagerFull.inbound.pause(); + await deltaManagerFull.outbound.pause(); sharedMap1.on("valueChanged", (changed) => { if (changed.key !== "key2") { @@ -372,8 +375,8 @@ describeCompat( sharedMap1.set("key1", "1"); - container1.deltaManager.inbound.resume(); - container1.deltaManager.outbound.resume(); + deltaManagerFull.inbound.resume(); + deltaManagerFull.outbound.resume(); await provider.ensureSynchronized(); // The offending container is not closed diff --git a/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts b/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts index c7702fd2e70e..013af583f215 100644 --- a/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/orderSequentially.spec.ts @@ -12,7 +12,11 @@ import { IContainerRuntime } from "@fluidframework/container-runtime-definitions import { ConfigTypes, IConfigProviderBase } from "@fluidframework/core-interfaces"; import { Serializable } from "@fluidframework/datastore-definitions/internal"; import type { SharedDirectory, ISharedMap, IValueChanged } from "@fluidframework/map/internal"; -import type { ISharedString, SharedString } from "@fluidframework/sequence/internal"; +import type { + ISharedString, + SequenceDeltaEvent, + SharedString, +} from "@fluidframework/sequence/internal"; import { ChannelFactoryRegistry, DataObjectFactoryType, @@ -29,7 +33,6 @@ const mapId = "mapKey"; describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvider, apis) => { const { SharedMap, SharedDirectory, SharedString, SharedCell } = apis.dds; - const { SequenceDeltaEvent } = apis.dataRuntime.packages.sequence; const registry: ChannelFactoryRegistry = [ [stringId, SharedString.getFactory()], @@ -55,11 +58,10 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi let sharedDir: SharedDirectory; let sharedCell: ISharedCell; let sharedMap: ISharedMap; - let changedEventData: ( - | IValueChanged - | Serializable - | InstanceType - )[]; + let changedEventData: { + event: IValueChanged | Serializable | SequenceDeltaEvent | undefined; + target: SharedString | SharedDirectory | ISharedCell | ISharedMap; + }[]; let containerRuntime: IContainerRuntime; let error: Error | undefined; @@ -87,23 +89,23 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi containerRuntime = dataObject.context.containerRuntime as IContainerRuntime; changedEventData = []; - sharedString.on("sequenceDelta", (event, _target) => { - changedEventData.push(event); + sharedString.on("sequenceDelta", (event, target) => { + changedEventData.push({ event, target }); }); - sharedString2.on("sequenceDelta", (event, _target) => { - changedEventData.push(event); + sharedString2.on("sequenceDelta", (event, target) => { + changedEventData.push({ event, target }); }); - sharedDir.on("valueChanged", (changed, _local, _target) => { - changedEventData.push(changed); + sharedDir.on("valueChanged", (event, _local, target) => { + changedEventData.push({ event, target }); }); - sharedCell.on("valueChanged", (value) => { - changedEventData.push(value); + sharedCell.on("valueChanged", (event) => { + changedEventData.push({ event, target: sharedCell }); }); sharedCell.on("delete", () => { - changedEventData.push(undefined); + changedEventData.push({ event: undefined, target: sharedCell }); }); - sharedMap.on("valueChanged", (value) => { - changedEventData.push(value); + sharedMap.on("valueChanged", (event) => { + changedEventData.push({ event, target: sharedMap }); }); }); @@ -133,30 +135,30 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi assert.equal(changedEventData.length, 9); assert( - changedEventData[0] instanceof SequenceDeltaEvent, + changedEventData[0].target === sharedString, `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.deepEqual(changedEventData[1], { key: "key1", previousValue: undefined }); + assert.deepEqual(changedEventData[1].event, { key: "key1", previousValue: undefined }); - assert.equal(changedEventData[2], 2); + assert.equal(changedEventData[2].event, 2); assert( - changedEventData[3] instanceof SequenceDeltaEvent, + changedEventData[3].target === sharedString, `Unexpected event type - ${typeof changedEventData[3]}`, ); - assert.deepEqual(changedEventData[4], { key: "key1", previousValue: 0 }); + assert.deepEqual(changedEventData[4].event, { key: "key1", previousValue: 0 }); - assert.equal(changedEventData[5], undefined); + assert.equal(changedEventData[5].event, undefined); // rollback - assert.equal(changedEventData[6], 2); + assert.equal(changedEventData[6].event, 2); - assert.deepEqual(changedEventData[7], { key: "key1", previousValue: undefined }); + assert.deepEqual(changedEventData[7].event, { key: "key1", previousValue: undefined }); assert( - changedEventData[8] instanceof SequenceDeltaEvent, + changedEventData[8].target === sharedString, `Unexpected event type - ${typeof changedEventData[6]}`, ); }); @@ -199,64 +201,64 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi assert.equal(changedEventData.length, 17); assert( - changedEventData[0] instanceof SequenceDeltaEvent, + changedEventData[0].target === sharedString, `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.deepEqual(changedEventData[1], { key: "key1", previousValue: undefined }); + assert.deepEqual(changedEventData[1].event, { key: "key1", previousValue: undefined }); assert( - changedEventData[2] instanceof SequenceDeltaEvent, + changedEventData[2].target === sharedString, `Unexpected event type - ${typeof changedEventData[2]}`, ); - assert.equal(changedEventData[3], 2); + assert.equal(changedEventData[3].event, 2); - assert.deepEqual(changedEventData[4], { key: "key1", previousValue: 0 }); + assert.deepEqual(changedEventData[4].event, { key: "key1", previousValue: 0 }); assert( - changedEventData[5] instanceof SequenceDeltaEvent, + changedEventData[5].target === sharedString, `Unexpected event type - ${typeof changedEventData[5]}`, ); - assert.equal(changedEventData[6], 5); + assert.equal(changedEventData[6].event, 5); assert( - changedEventData[7] instanceof SequenceDeltaEvent, + changedEventData[7].target === sharedString, `Unexpected event type - ${typeof changedEventData[7]}`, ); - assert.equal(changedEventData[8], undefined); + assert.equal(changedEventData[8].event, undefined); // rollback - assert.equal(changedEventData[9], 5); + assert.equal(changedEventData[9].event, 5); // segments are split up at some point - reason for multiple events assert( - changedEventData[10] instanceof SequenceDeltaEvent, + changedEventData[10].target === sharedString, `Unexpected event type - ${typeof changedEventData[10]}`, ); assert( - changedEventData[11] instanceof SequenceDeltaEvent, + changedEventData[11].target === sharedString, `Unexpected event type - ${typeof changedEventData[11]}`, ); assert( - changedEventData[12] instanceof SequenceDeltaEvent, + changedEventData[12].target === sharedString, `Unexpected event type - ${typeof changedEventData[12]}`, ); - assert.equal(changedEventData[13], 2); + assert.equal(changedEventData[13].event, 2); // segments are split up at some point - reason for multiple events assert( - changedEventData[14] instanceof SequenceDeltaEvent, + changedEventData[14].target === sharedString, `Unexpected event type - ${typeof changedEventData[14]}`, ); assert( - changedEventData[15] instanceof SequenceDeltaEvent, + changedEventData[15].target === sharedString, `Unexpected event type - ${typeof changedEventData[15]}`, ); - assert.deepEqual(changedEventData[16], { key: "key1", previousValue: 3 }); + assert.deepEqual(changedEventData[16].event, { key: "key1", previousValue: 3 }); }); it("Should handle rollback on multiple instances of the same DDS type", () => { @@ -287,41 +289,41 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi assert.equal(changedEventData.length, 9); assert( - changedEventData[0] instanceof SequenceDeltaEvent, + changedEventData[0].target === sharedString, `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.deepEqual(changedEventData[1], { key: "key", previousValue: undefined }); + assert.deepEqual(changedEventData[1].event, { key: "key", previousValue: undefined }); assert( - changedEventData[2] instanceof SequenceDeltaEvent, + changedEventData[2].target === sharedString2, `Unexpected event type - ${typeof changedEventData[2]}`, ); - assert.deepEqual(changedEventData[3], { key: "key", previousValue: 1 }); + assert.deepEqual(changedEventData[3].event, { key: "key", previousValue: 1 }); assert( - changedEventData[4] instanceof SequenceDeltaEvent, + changedEventData[4].target === sharedString2, `Unexpected event type - ${typeof changedEventData[4]}`, ); assert( - changedEventData[5] instanceof SequenceDeltaEvent, + changedEventData[5].target === sharedString, `Unexpected event type - ${typeof changedEventData[5]}`, ); // rollback assert( - changedEventData[6] instanceof SequenceDeltaEvent, + changedEventData[6].target === sharedString, `Unexpected event type - ${typeof changedEventData[6]}`, ); assert( - changedEventData[7] instanceof SequenceDeltaEvent, + changedEventData[7].target === sharedString2, `Unexpected event type - ${typeof changedEventData[7]}`, ); - assert.deepEqual(changedEventData[8], { key: "key", previousValue: undefined }); + assert.deepEqual(changedEventData[8].event, { key: "key", previousValue: undefined }); }); it("Should handle nested calls to orderSequentially", () => { @@ -357,30 +359,30 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi assert.equal(changedEventData.length, 8); assert( - changedEventData[0] instanceof SequenceDeltaEvent, + changedEventData[0].target === sharedString, `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.deepEqual(changedEventData[1], { key: "key", previousValue: undefined }); + assert.deepEqual(changedEventData[1].event, { key: "key", previousValue: undefined }); - assert.deepEqual(changedEventData[2], { key: "key", previousValue: 1 }); + assert.deepEqual(changedEventData[2].event, { key: "key", previousValue: 1 }); assert( - changedEventData[3] instanceof SequenceDeltaEvent, + changedEventData[3].target === sharedString, `Unexpected event type - ${typeof changedEventData[3]}`, ); // rollback - inner orderSequentially call assert( - changedEventData[4] instanceof SequenceDeltaEvent, + changedEventData[4].target === sharedString, `Unexpected event type - ${typeof changedEventData[4]}`, ); - assert.deepEqual(changedEventData[5], { key: "key", previousValue: 0 }); + assert.deepEqual(changedEventData[5].event, { key: "key", previousValue: 0 }); // rollback - outer orderSequentially call - assert.deepEqual(changedEventData[6], { key: "key", previousValue: undefined }); + assert.deepEqual(changedEventData[6].event, { key: "key", previousValue: undefined }); - assert.deepEqual(changedEventData[7], { key: "key", previousValue: 0 }); + assert.deepEqual(changedEventData[7].event, { key: "key", previousValue: 0 }); }); }); diff --git a/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts b/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts index 7576ab8d6bed..fcf58276fea1 100644 --- a/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts @@ -63,6 +63,7 @@ import { timeoutPromise, waitForContainerConnection, timeoutAwait, + toIDeltaManagerFull, } from "@fluidframework/test-utils/internal"; import { SchemaFactory, ITree, TreeViewConfiguration } from "@fluidframework/tree"; import { SharedTree } from "@fluidframework/tree/internal"; @@ -114,7 +115,10 @@ const getPendingOps = async ( await testObjectProvider.ensureSynchronized(); await testObjectProvider.opProcessingController.pauseProcessing(container); - assert(toDeltaManagerInternal(dataStore.runtime.deltaManager).outbound.paused); + const deltaManagerInternal = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore.runtime.deltaManager), + ); + assert(deltaManagerInternal.outbound.paused); await cb(container, dataStore); @@ -1276,7 +1280,10 @@ describeCompat("stashed ops", "NoCompat", (getTestObjectProvider, apis) => { assert.ok(serializedClientId); await provider.opProcessingController.pauseProcessing(container); - assert(toDeltaManagerInternal(dataStore.runtime.deltaManager).outbound.paused); + const deltaManagerFull = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore.runtime.deltaManager), + ); + assert(deltaManagerFull.outbound.paused); [...Array(lots).keys()].map((i) => dataStore.root.set(`test op #${i}`, i)); @@ -1479,7 +1486,10 @@ describeCompat("stashed ops", "NoCompat", (getTestObjectProvider, apis) => { ); await provider.opProcessingController.pauseProcessing(container2); - assert(toDeltaManagerInternal(dataStore2.runtime.deltaManager).outbound.paused); + const deltaManagerFull = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore2.runtime.deltaManager), + ); + assert(deltaManagerFull.outbound.paused); [...Array(lots).keys()].map((i) => map2.set((i + lots).toString(), i + lots)); const morePendingOps = await container2.getPendingLocalState?.(); @@ -1638,6 +1648,14 @@ describeCompat("stashed ops", "NoCompat", (getTestObjectProvider, apis) => { }); it("load offline with blob redirect table", async function () { + // TODO: AB#22741: Re-enable "load offline with blob redirect table" + if ( + provider.driver.type === "odsp" || + (provider.driver.type === "routerlicious" && provider.driver.endpointName === "frs") + ) { + this.skip(); + } + const container = await loader.resolve({ url }); const dataStore = (await container.getEntryPoint()) as ITestFluidObject; const map = await dataStore.getSharedObject(mapId); @@ -1856,6 +1874,11 @@ describeCompat("stashed ops", "NoCompat", (getTestObjectProvider, apis) => { // TODO: https://github.com/microsoft/FluidFramework/issues/10729 it("works with summary while offline", async function () { + // TODO: AB#22740: Re-enable "works with summary while offline" on ODSP + if (provider.driver.type === "odsp") { + this.skip(); + } + map1.set("test op 1", "test op 1"); await waitForSummary(provider, container1, testContainerConfig); @@ -1918,7 +1941,7 @@ describeCompat("stashed ops", "NoCompat", (getTestObjectProvider, apis) => { )) as IContainerExperimental; // pause outgoing ops so we can detect dropped stashed changes - await container.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container.deltaManager).outbound.pause(); let pendingState: string | undefined; let pendingStateP; const dataStore = (await container.getEntryPoint()) as ITestFluidObject; @@ -2220,7 +2243,7 @@ describeCompat( }, pendingLocalState, ); - await container.deltaManager.outbound.pause(); + await toIDeltaManagerFull(container.deltaManager).outbound.pause(); container.connect(); // Wait for the container to connect, and then pause the inbound queue @@ -2229,7 +2252,7 @@ describeCompat( reject: true, errorMsg: `${loggingId} didn't connect in time`, }); - await container.deltaManager.inbound.pause(); + await toIDeltaManagerFull(container.deltaManager).inbound.pause(); // Now this container should submit the op when we resume the outbound queue return container; @@ -2245,27 +2268,29 @@ describeCompat( const dataStore3 = (await container3.getEntryPoint()) as ITestFluidObject; const counter3 = await dataStore3.getSharedObject(counterId); + const container2DeltaManager = toIDeltaManagerFull(container2.deltaManager); + const container3DeltaManager = toIDeltaManagerFull(container3.deltaManager); // Here's the "in parallel" part - resume both outbound queues at the same time, // and then resume both inbound queues once the outbound queues are idle (done sending). const allSentP = Promise.all([ timeoutPromise( (resolve) => { - container2.deltaManager.outbound.once("idle", resolve); + container2DeltaManager.outbound.once("idle", resolve); }, { errorMsg: "container2 outbound queue never reached idle state" }, ), timeoutPromise( (resolve) => { - container3.deltaManager.outbound.once("idle", resolve); + container3DeltaManager.outbound.once("idle", resolve); }, { errorMsg: "container3 outbound queue never reached idle state" }, ), ]); - container2.deltaManager.outbound.resume(); - container3.deltaManager.outbound.resume(); + container2DeltaManager.outbound.resume(); + container3DeltaManager.outbound.resume(); await allSentP; - container2.deltaManager.inbound.resume(); - container3.deltaManager.inbound.resume(); + container2DeltaManager.inbound.resume(); + container3DeltaManager.inbound.resume(); // At this point, both rehydrated containers should have submitted the same Counter op. // ContainerRuntime will use PSM and BatchTracker and it will play out like this: diff --git a/packages/test/test-end-to-end-tests/src/test/summarization/summarizeIncrementallySubDds.spec.ts b/packages/test/test-end-to-end-tests/src/test/summarization/summarizeIncrementallySubDds.spec.ts index e4b192052c17..10c69869d1a3 100644 --- a/packages/test/test-end-to-end-tests/src/test/summarization/summarizeIncrementallySubDds.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/summarization/summarizeIncrementallySubDds.spec.ts @@ -589,7 +589,12 @@ describeCompat( assert(ddsTree.tree["3"].type === SummaryType.Blob, "Blob 3 should be a blob"); }); - it("can create summary handles for trees in DDSes that do not change", async () => { + it("can create summary handles for trees in DDSes that do not change", async function () { + // Skip this test for standard r11s as its summarization timing is flaky. + // This test is covering client logic and the coverage from other drivers/endpoints is sufficient. + if (provider.driver.type === "r11s" && provider.driver.endpointName !== "frs") { + this.skip(); + } const container = await createContainer(); const datastore = (await container.getEntryPoint()) as ITestFluidObject; const dds = await datastore.getSharedObject( diff --git a/packages/test/test-end-to-end-tests/src/test/summarization/summarizeWithOutOfOrderDataStoreRealization.spec.ts b/packages/test/test-end-to-end-tests/src/test/summarization/summarizeWithOutOfOrderDataStoreRealization.spec.ts index b11fee43e81c..20e8d5f9a709 100644 --- a/packages/test/test-end-to-end-tests/src/test/summarization/summarizeWithOutOfOrderDataStoreRealization.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/summarization/summarizeWithOutOfOrderDataStoreRealization.spec.ts @@ -150,7 +150,13 @@ describeCompat( await waitForContainerConnection(mainContainer); }); - it("No Summary Upload Error when DS gets realized between summarize and completeSummary", async () => { + it("No Summary Upload Error when DS gets realized between summarize and completeSummary", async function () { + // Skip this test for standard r11s as its summarization timing is flaky and non-reproducible. + // This test is covering client logic and the coverage from other drivers/endpoints is sufficient. + if (provider.driver.type === "r11s" && provider.driver.endpointName !== "frs") { + this.skip(); + } + const summarizerClient = await createSummarizerWithVersion(); await provider.ensureSynchronized(); mainDataStore._root.set("1", "2"); diff --git a/packages/test/test-end-to-end-tests/src/test/t9sregressiontest.spec.ts b/packages/test/test-end-to-end-tests/src/test/t9sregressiontest.spec.ts index 65a9bbdaf2d4..79d37131c33c 100644 --- a/packages/test/test-end-to-end-tests/src/test/t9sregressiontest.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/t9sregressiontest.spec.ts @@ -13,6 +13,7 @@ import { DataObjectFactoryType, ITestContainerConfig, ITestFluidObject, + toIDeltaManagerFull, createAndAttachContainer, } from "@fluidframework/test-utils/internal"; @@ -54,7 +55,10 @@ describeCompat("t9s issue regression test", "NoCompat", (getTestObjectProvider, [...Array(60).keys()].map((i) => map2.set(`test op ${i}`, i)); await provider.ensureSynchronized(); await provider.opProcessingController.pauseProcessing(container2); - assert(toDeltaManagerInternal(dataStore2.runtime.deltaManager).outbound.paused); + const deltaManagerFull = toIDeltaManagerFull( + toDeltaManagerInternal(dataStore2.runtime.deltaManager), + ); + assert(deltaManagerFull.outbound.paused); map2.set("a key", "a value"); await provider.ensureSynchronized(); diff --git a/packages/test/test-pairwise-generator/CHANGELOG.md b/packages/test/test-pairwise-generator/CHANGELOG.md index 7221723c2c9a..437506df34f4 100644 --- a/packages/test/test-pairwise-generator/CHANGELOG.md +++ b/packages/test/test-pairwise-generator/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-pairwise-generator +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-pairwise-generator/package.json b/packages/test/test-pairwise-generator/package.json index ec496641f13e..8890a8463296 100644 --- a/packages/test/test-pairwise-generator/package.json +++ b/packages/test/test-pairwise-generator/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-pairwise-generator", - "version": "2.5.0", + "version": "2.10.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -86,9 +86,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/test/test-service-load/.eslintrc.cjs b/packages/test/test-service-load/.eslintrc.cjs index b8ea1b89b791..afffe3928fff 100644 --- a/packages/test/test-service-load/.eslintrc.cjs +++ b/packages/test/test-service-load/.eslintrc.cjs @@ -13,5 +13,6 @@ module.exports = { }, rules: { "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/test/test-service-load/CHANGELOG.md b/packages/test/test-service-load/CHANGELOG.md index 02fe47c5fae8..f49a2eb7f985 100644 --- a/packages/test/test-service-load/CHANGELOG.md +++ b/packages/test/test-service-load/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/test-service-load +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-service-load/package.json b/packages/test/test-service-load/package.json index ad6f2379b14b..1ab4f7db66bd 100644 --- a/packages/test/test-service-load/package.json +++ b/packages/test/test-service-load/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-service-load", - "version": "2.5.0", + "version": "2.10.0", "description": "Service load tests", "homepage": "https://fluidframework.com", "repository": { @@ -110,9 +110,9 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/test/test-service-load/src/packageVersion.ts b/packages/test/test-service-load/src/packageVersion.ts index bdda25c2de71..b94f9b5f93f8 100644 --- a/packages/test/test-service-load/src/packageVersion.ts +++ b/packages/test/test-service-load/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-internal/test-service-load"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/test-utils/.eslintrc.cjs b/packages/test/test-utils/.eslintrc.cjs index b8ea1b89b791..afffe3928fff 100644 --- a/packages/test/test-utils/.eslintrc.cjs +++ b/packages/test/test-utils/.eslintrc.cjs @@ -13,5 +13,6 @@ module.exports = { }, rules: { "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/test/test-utils/CHANGELOG.md b/packages/test/test-utils/CHANGELOG.md index 51fdbad9e3b5..af691cf3045b 100644 --- a/packages/test/test-utils/CHANGELOG.md +++ b/packages/test/test-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/test-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-utils/package.json b/packages/test/test-utils/package.json index 62af2060ef15..b57189eebc81 100644 --- a/packages/test/test-utils/package.json +++ b/packages/test/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/test-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Utilities for Fluid tests", "homepage": "https://fluidframework.com", "repository": { @@ -143,11 +143,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/test-utils-previous": "npm:@fluidframework/test-utils@~2.4.0", + "@fluidframework/test-utils-previous": "npm:@fluidframework/test-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/debug": "^4.1.5", "@types/diff": "^3.5.1", @@ -167,7 +167,20 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Class_TestFluidObject": { + "backCompat": false + }, + "ClassStatics_TestFluidObject": { + "backCompat": false + }, + "Interface_ITestFluidObject": { + "backCompat": false + }, + "Interface_IProvideTestFluidObject": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/test/test-utils/src/containerUtils.ts b/packages/test/test-utils/src/containerUtils.ts index 0d34463f7fe3..303d0aaaa0da 100644 --- a/packages/test/test-utils/src/containerUtils.ts +++ b/packages/test/test-utils/src/containerUtils.ts @@ -3,10 +3,19 @@ * Licensed under the MIT License. */ -import { IContainer } from "@fluidframework/container-definitions/internal"; +import { + IContainer, + IDeltaManager, + type IDeltaManagerFull, +} from "@fluidframework/container-definitions/internal"; import { ConnectionState } from "@fluidframework/container-loader"; import { IResponse } from "@fluidframework/core-interfaces"; import { assert } from "@fluidframework/core-utils/internal"; +import type { IDeltaManagerErased } from "@fluidframework/datastore-definitions/internal"; +import type { + IDocumentMessage, + ISequencedDocumentMessage, +} from "@fluidframework/driver-definitions/internal"; import { IDataStore } from "@fluidframework/runtime-definitions/internal"; import { PromiseExecutor, TimeoutWithError, timeoutPromise } from "./timeoutUtils.js"; @@ -89,3 +98,20 @@ export async function getDataStoreEntryPointBackCompat(dataStore: IDataStore) assert(response.status === 200, "empty request should return data object"); return response.value as T; } + +/** + * @internal + */ +export function toIDeltaManagerFull( + deltaManager: + | IDeltaManager + | IDeltaManagerErased, +): IDeltaManagerFull { + assert( + "inbound" in deltaManager && "outbound" in deltaManager, + "Delta manager does not have inbound/outbound queues.", + ); + return deltaManager as unknown as + | IDeltaManagerErased + | IDeltaManager as IDeltaManagerFull; +} diff --git a/packages/test/test-utils/src/index.ts b/packages/test/test-utils/src/index.ts index 06313883301f..e479866fc9d5 100644 --- a/packages/test/test-utils/src/index.ts +++ b/packages/test/test-utils/src/index.ts @@ -56,6 +56,7 @@ export { type TimeoutWithValue, } from "./timeoutUtils.js"; export { + toIDeltaManagerFull, waitForContainerConnection, getContainerEntryPointBackCompat, getDataStoreEntryPointBackCompat, diff --git a/packages/test/test-utils/src/loaderContainerTracker.ts b/packages/test/test-utils/src/loaderContainerTracker.ts index a48956d6882c..42ad813fd86e 100644 --- a/packages/test/test-utils/src/loaderContainerTracker.ts +++ b/packages/test/test-utils/src/loaderContainerTracker.ts @@ -22,7 +22,7 @@ import { } from "@fluidframework/driver-definitions/internal"; import { canBeCoalescedByService } from "@fluidframework/driver-utils/internal"; -import { waitForContainerConnection } from "./containerUtils.js"; +import { toIDeltaManagerFull, waitForContainerConnection } from "./containerUtils.js"; import { debug } from "./debug.js"; import { IOpProcessingController } from "./testObjectProvider.js"; import { timeoutAwait, timeoutPromise } from "./timeoutUtils.js"; @@ -145,7 +145,8 @@ export class LoaderContainerTracker implements IOpProcessingController { * @param record - the record to update the trailing op information */ private trackTrailingNoOps(container: IContainer, record: ContainerRecord) { - container.deltaManager.outbound.on("op", (messages) => { + const deltaManagerFull = toIDeltaManagerFull(container.deltaManager); + deltaManagerFull.outbound.on("op", (messages) => { for (const msg of messages) { if (canBeCoalescedByService(msg)) { // Track the NoOp that was sent. @@ -161,7 +162,7 @@ export class LoaderContainerTracker implements IOpProcessingController { } }); - container.deltaManager.inbound.on("push", (message) => { + deltaManagerFull.inbound.on("push", (message) => { // Received the no op back, update the record if we are tracking if ( canBeCoalescedByService(message) && @@ -458,12 +459,12 @@ export class LoaderContainerTracker implements IOpProcessingController { return new Promise((resolve) => { const handler = () => { containersToApply.map((c) => { - c.deltaManager.inbound.off("push", handler); + toIDeltaManagerFull(c.deltaManager).inbound.off("push", handler); }); resolve(); }; containersToApply.map((c) => { - c.deltaManager.inbound.on("push", handler); + toIDeltaManagerFull(c.deltaManager).inbound.on("push", handler); }); }); } @@ -482,8 +483,9 @@ export class LoaderContainerTracker implements IOpProcessingController { ); if (record?.paused === true) { debugWait(`${record.index}: container resumed`); - container.deltaManager.inbound.resume(); - container.deltaManager.outbound.resume(); + const deltaManagerFull = toIDeltaManagerFull(container.deltaManager); + deltaManagerFull.inbound.resume(); + deltaManagerFull.outbound.resume(); resumed.push(container); record.paused = false; } @@ -526,12 +528,13 @@ export class LoaderContainerTracker implements IOpProcessingController { */ private async pauseContainer(container: IContainer, record: ContainerRecord) { debugWait(`${record.index}: pausing container`); - assert(!container.deltaManager.outbound.paused, "Container should not be paused yet"); - assert(!container.deltaManager.inbound.paused, "Container should not be paused yet"); + const deltaManagerFull = toIDeltaManagerFull(container.deltaManager); + assert(!deltaManagerFull.outbound.paused, "Container should not be paused yet"); + assert(!deltaManagerFull.inbound.paused, "Container should not be paused yet"); // Pause outbound debugWait(`${record.index}: pausing container outbound queues`); - await container.deltaManager.outbound.pause(); + await deltaManagerFull.outbound.pause(); // Ensure the container is connected first. if (container.connectionState !== ConnectionState.Connected) { @@ -542,7 +545,7 @@ export class LoaderContainerTracker implements IOpProcessingController { // Check if the container is in write mode if (!container.deltaManager.active) { let proposalP: Promise | undefined; - if (container.deltaManager.outbound.idle) { + if (deltaManagerFull.outbound.idle) { // Need to generate an op to force write mode debugWait(`${record.index}: container force write connection`); const maybeContainer = container as Partial; @@ -556,11 +559,11 @@ export class LoaderContainerTracker implements IOpProcessingController { // Wait for nack debugWait(`${record.index}: Wait for container disconnect`); - container.deltaManager.outbound.resume(); + deltaManagerFull.outbound.resume(); await new Promise((resolve) => container.once("disconnected", resolve)); const accepted = proposalP ? await proposalP : false; assert(!accepted, "A proposal in read mode should be rejected"); - await container.deltaManager.outbound.pause(); + await deltaManagerFull.outbound.pause(); // Ensure the container is reconnect. if (container.connectionState !== ConnectionState.Connected) { @@ -572,7 +575,7 @@ export class LoaderContainerTracker implements IOpProcessingController { debugWait(`${record.index}: pausing container inbound queues`); // Pause inbound - await container.deltaManager.inbound.pause(); + await deltaManagerFull.inbound.pause(); debugWait(`${record.index}: container paused`); @@ -588,7 +591,10 @@ export class LoaderContainerTracker implements IOpProcessingController { * Pausing will switch the container to write mode. See `pauseProcessing` */ public async processIncoming(...containers: IContainer[]) { - return this.processQueue(containers, (container) => container.deltaManager.inbound); + return this.processQueue( + containers, + (container) => toIDeltaManagerFull(container.deltaManager).inbound, + ); } /** @@ -599,7 +605,10 @@ export class LoaderContainerTracker implements IOpProcessingController { * Pausing will switch the container to write mode. See `pauseProcessing` */ public async processOutgoing(...containers: IContainer[]) { - return this.processQueue(containers, (container) => container.deltaManager.outbound); + return this.processQueue( + containers, + (container) => toIDeltaManagerFull(container.deltaManager).outbound, + ); } /** @@ -678,12 +687,13 @@ export class LoaderContainerTracker implements IOpProcessingController { } }; - container.deltaManager.outbound.on("op", outHandler); - container.deltaManager.inbound.on("push", inHandler); + const deltaManagerFull = toIDeltaManagerFull(container.deltaManager); + deltaManagerFull.outbound.on("op", outHandler); + deltaManagerFull.inbound.on("push", inHandler); return () => { - container.deltaManager.outbound.off("op", outHandler); - container.deltaManager.inbound.off("push", inHandler); + deltaManagerFull.outbound.off("op", outHandler); + deltaManagerFull.inbound.off("push", inHandler); }; } @@ -726,7 +736,8 @@ export class LoaderContainerTracker implements IOpProcessingController { } }; debugOp(`${index}: ADD: clientId: ${container.clientId}`); - container.deltaManager.outbound.on("op", (messages) => { + const deltaManagerFull = toIDeltaManagerFull(container.deltaManager); + deltaManagerFull.outbound.on("op", (messages) => { for (const msg of messages) { debugOp( `${index}: OUT: ` + @@ -749,8 +760,8 @@ export class LoaderContainerTracker implements IOpProcessingController { ); }; }; - container.deltaManager.inbound.on("push", getInboundHandler("IN ")); - container.deltaManager.inbound.on("op", getInboundHandler("OP ")); + deltaManagerFull.inbound.on("push", getInboundHandler("IN ")); + deltaManagerFull.inbound.on("op", getInboundHandler("OP ")); container.deltaManager.on("connect", (details) => { debugOp(`${index}: CON: clientId: ${details.clientId}`); }); diff --git a/packages/test/test-utils/src/packageVersion.ts b/packages/test/test-utils/src/packageVersion.ts index 6591b2cd92d4..1f4443c3fa57 100644 --- a/packages/test/test-utils/src/packageVersion.ts +++ b/packages/test/test-utils/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/test-utils"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts b/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts index 23bb8b2365d0..d14180f98964 100644 --- a/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts +++ b/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts @@ -85,6 +85,7 @@ declare type old_as_current_for_Interface_IProvideTestFluidObject = requireAssig * typeValidation.broken: * "Interface_IProvideTestFluidObject": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_IProvideTestFluidObject = requireAssignableTo, TypeOnly> /* @@ -103,4 +104,5 @@ declare type old_as_current_for_Interface_ITestFluidObject = requireAssignableTo * typeValidation.broken: * "Interface_ITestFluidObject": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ITestFluidObject = requireAssignableTo, TypeOnly> diff --git a/packages/test/test-version-utils/CHANGELOG.md b/packages/test/test-version-utils/CHANGELOG.md index 8b2309729c79..52f2b0699a2f 100644 --- a/packages/test/test-version-utils/CHANGELOG.md +++ b/packages/test/test-version-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/test-version-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/test/test-version-utils/package.json b/packages/test/test-version-utils/package.json index aad39e10674c..358fa4a3c85c 100644 --- a/packages/test/test-version-utils/package.json +++ b/packages/test/test-version-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/test-version-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { @@ -77,7 +77,7 @@ "@fluid-experimental/sequence-deprecated": "workspace:~", "@fluid-internal/test-driver-definitions": "workspace:~", "@fluid-private/test-drivers": "workspace:~", - "@fluid-tools/version-tools": "^0.49.0", + "@fluid-tools/version-tools": "^0.50.0", "@fluidframework/agent-scheduler": "workspace:~", "@fluidframework/aqueduct": "workspace:~", "@fluidframework/cell": "workspace:~", @@ -109,9 +109,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", diff --git a/packages/test/test-version-utils/src/packageVersion.ts b/packages/test/test-version-utils/src/packageVersion.ts index 7ca8795436d3..318f81432875 100644 --- a/packages/test/test-version-utils/src/packageVersion.ts +++ b/packages/test/test-version-utils/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-private/test-version-utils"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/test/types_jest-environment-puppeteer/package.json b/packages/test/types_jest-environment-puppeteer/package.json index 2927c61ee12e..54ac3fa30cb2 100644 --- a/packages/test/types_jest-environment-puppeteer/package.json +++ b/packages/test/types_jest-environment-puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "@types/jest-environment-puppeteer", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "TypeScript `globals` definitions fix-up for jest-environment-puppeteer", "homepage": "https://fluidframework.com", @@ -25,7 +25,7 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "jest-environment-puppeteer": "^10.1.3", "prettier": "~3.0.3", "rimraf": "^4.4.0" diff --git a/packages/tools/changelog-generator-wrapper/CHANGELOG.md b/packages/tools/changelog-generator-wrapper/CHANGELOG.md index 942e13b093e0..f5145542b32c 100644 --- a/packages/tools/changelog-generator-wrapper/CHANGELOG.md +++ b/packages/tools/changelog-generator-wrapper/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-private/changelog-generator-wrapper +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/changelog-generator-wrapper/package.json b/packages/tools/changelog-generator-wrapper/package.json index ceabbd5e240d..603a0292d195 100644 --- a/packages/tools/changelog-generator-wrapper/package.json +++ b/packages/tools/changelog-generator-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-private/changelog-generator-wrapper", - "version": "2.5.0", + "version": "2.10.0", "private": true, "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/devtools/devtools-browser-extension/CHANGELOG.md b/packages/tools/devtools/devtools-browser-extension/CHANGELOG.md index 1324942a7d4a..904b8ace830b 100644 --- a/packages/tools/devtools/devtools-browser-extension/CHANGELOG.md +++ b/packages/tools/devtools/devtools-browser-extension/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/devtools-browser-extension +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/devtools/devtools-browser-extension/EXTENSION_CHANGELOG.md b/packages/tools/devtools/devtools-browser-extension/EXTENSION_CHANGELOG.md index 38cb1f62a21b..f2fdd581b33c 100644 --- a/packages/tools/devtools/devtools-browser-extension/EXTENSION_CHANGELOG.md +++ b/packages/tools/devtools/devtools-browser-extension/EXTENSION_CHANGELOG.md @@ -5,6 +5,7 @@ - Update icon assets with new Fluid logo. +- Update sample screenshots to reflect various UI updates. # 0.1.2 diff --git a/packages/tools/devtools/devtools-browser-extension/README.md b/packages/tools/devtools/devtools-browser-extension/README.md index 2b2705043f82..3f31869d44f8 100644 --- a/packages/tools/devtools/devtools-browser-extension/README.md +++ b/packages/tools/devtools/devtools-browser-extension/README.md @@ -124,6 +124,21 @@ You should now see the Devtools usage telemetry events appear! Note: The browser extensions may only be published by Microsoft employees. For details on the steps required, see [here](https://eng.ms/docs/experiences-devices/opg/office-shared/fluid-framework/fluid-framework-internal/fluid-framework/docs/infrastructure/devtools/publishing-the-browser-extension) (Microsoft only). +#### Store Assets + +The following are links to image assets intended for use in the extension store pages: + +- Logo (128 x 128 pixels): https://storage.fluidframework.com/static/images/devtools/logo.png +- Large marquee (1400 x 560 pixels): https://storage.fluidframework.com/static/images/devtools/marquee-large.png +- Small marquee (440 x 280 pixels): https://storage.fluidframework.com/static/images/devtools/marquee-small.png +- Sample screenshots (either 640 x 400 pixels or 1280 x 800 pixels) + - Audience View: https://storage.fluidframework.com/static/images/devtools/screenshots/audience-view.png + - Data View: https://storage.fluidframework.com/static/images/devtools/screenshots/data-view.png + - Telemetry View: https://storage.fluidframework.com/static/images/devtools/screenshots/telemetry-view.png + +Updates to these assets can only be made by a Microsoft Fluid team member. +If you update these images, be sure to update each of the browser extension store pages with the new images. + diff --git a/packages/tools/devtools/devtools-browser-extension/package.json b/packages/tools/devtools/devtools-browser-extension/package.json index 185f891f63fb..de59984738fb 100644 --- a/packages/tools/devtools/devtools-browser-extension/package.json +++ b/packages/tools/devtools/devtools-browser-extension/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/devtools-browser-extension", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "A browser extension for visualizing Fluid Framework stats and operations", "homepage": "https://fluidframework.com", @@ -94,7 +94,7 @@ "@fluid-internal/mocha-test-setup": "workspace:~", "@fluidframework/aqueduct": "workspace:~", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", "@fluidframework/container-runtime-definitions": "workspace:~", diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/README.md b/packages/tools/devtools/devtools-browser-extension/store-assets/README.md deleted file mode 100644 index 55284b450599..000000000000 --- a/packages/tools/devtools/devtools-browser-extension/store-assets/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This directory contains assets used when publishing the extensions to the browser store. - -Note: there is currently no tooling that will automatically update the store pages if you change the assets here. -If you change these, you will need to upload the new assets to the appropriate stores. diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/logo.png b/packages/tools/devtools/devtools-browser-extension/store-assets/logo.png deleted file mode 100644 index befe1c9f14f5..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/logo.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-large.png b/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-large.png deleted file mode 100644 index 469a0bb3dc0a..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-large.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-small.png b/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-small.png deleted file mode 100644 index a48d1418453e..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/marquee-small.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/audience-view.png b/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/audience-view.png deleted file mode 100644 index 29756d70d519..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/audience-view.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/data-view.png b/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/data-view.png deleted file mode 100644 index 90dbafbd3a97..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/data-view.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/telemetry-view.png b/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/telemetry-view.png deleted file mode 100644 index c36d09609703..000000000000 Binary files a/packages/tools/devtools/devtools-browser-extension/store-assets/screenshots/telemetry-view.png and /dev/null differ diff --git a/packages/tools/devtools/devtools-core/.eslintrc.cjs b/packages/tools/devtools/devtools-core/.eslintrc.cjs index a71fb8b6e870..346908b00ef0 100644 --- a/packages/tools/devtools/devtools-core/.eslintrc.cjs +++ b/packages/tools/devtools/devtools-core/.eslintrc.cjs @@ -14,6 +14,7 @@ module.exports = { // Disabled because it is incompatible with API-Extractor. "@typescript-eslint/no-namespace": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/tools/devtools/devtools-core/CHANGELOG.md b/packages/tools/devtools/devtools-core/CHANGELOG.md index 3d98da480eb4..23df62c0fac5 100644 --- a/packages/tools/devtools/devtools-core/CHANGELOG.md +++ b/packages/tools/devtools/devtools-core/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/devtools-core +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/devtools/devtools-core/package.json b/packages/tools/devtools/devtools-core/package.json index 43f73079a5b1..dc8fb4a2852a 100644 --- a/packages/tools/devtools/devtools-core/package.json +++ b/packages/tools/devtools/devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/devtools-core", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid Framework developer tools core functionality", "homepage": "https://fluidframework.com", "repository": { @@ -130,10 +130,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/devtools-core-previous": "npm:@fluidframework/devtools-core@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/devtools-core-previous": "npm:@fluidframework/devtools-core@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/id-compressor": "workspace:~", "@fluidframework/test-runtime-utils": "workspace:~", @@ -168,7 +168,14 @@ } }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_ContainerDevtoolsProps": { + "backCompat": false + }, + "Interface_FluidDevtoolsProps": { + "backCompat": false + } + }, "entrypoint": "alpha" } } diff --git a/packages/tools/devtools/devtools-core/src/packageVersion.ts b/packages/tools/devtools/devtools-core/src/packageVersion.ts index 1bbc45606930..6585327f274f 100644 --- a/packages/tools/devtools/devtools-core/src/packageVersion.ts +++ b/packages/tools/devtools/devtools-core/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/devtools-core"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/tools/devtools/devtools-core/src/test/types/validateDevtoolsCorePrevious.generated.ts b/packages/tools/devtools/devtools-core/src/test/types/validateDevtoolsCorePrevious.generated.ts index aaf3c5289081..3539a56d4ac0 100644 --- a/packages/tools/devtools/devtools-core/src/test/types/validateDevtoolsCorePrevious.generated.ts +++ b/packages/tools/devtools/devtools-core/src/test/types/validateDevtoolsCorePrevious.generated.ts @@ -49,6 +49,7 @@ declare type old_as_current_for_Interface_ContainerDevtoolsProps = requireAssign * typeValidation.broken: * "Interface_ContainerDevtoolsProps": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_ContainerDevtoolsProps = requireAssignableTo, TypeOnly> /* @@ -67,6 +68,7 @@ declare type old_as_current_for_Interface_FluidDevtoolsProps = requireAssignable * typeValidation.broken: * "Interface_FluidDevtoolsProps": {"backCompat": false} */ +// @ts-expect-error compatibility expected to be broken declare type current_as_old_for_Interface_FluidDevtoolsProps = requireAssignableTo, TypeOnly> /* diff --git a/packages/tools/devtools/devtools-example/.eslintrc.cjs b/packages/tools/devtools/devtools-example/.eslintrc.cjs index f03f3e970e94..4415b3806042 100644 --- a/packages/tools/devtools/devtools-example/.eslintrc.cjs +++ b/packages/tools/devtools/devtools-example/.eslintrc.cjs @@ -26,6 +26,7 @@ module.exports = { // TODO: AB#18875 - Re-enable react/no-deprecated once we replace uses of the deprecated ReactDOM.render() // with the new React 18 createRoot(). "react/no-deprecated": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, overrides: [ { diff --git a/packages/tools/devtools/devtools-example/CHANGELOG.md b/packages/tools/devtools/devtools-example/CHANGELOG.md index 5fa70b91dded..3b255c556513 100644 --- a/packages/tools/devtools/devtools-example/CHANGELOG.md +++ b/packages/tools/devtools/devtools-example/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-example/devtools-example +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/devtools/devtools-example/package.json b/packages/tools/devtools/devtools-example/package.json index dcf0a7bab400..d8dfa20cf845 100644 --- a/packages/tools/devtools/devtools-example/package.json +++ b/packages/tools/devtools/devtools-example/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/devtools-example", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "An example application demonstrating how Fluid's devtools can be integrated into an application", "homepage": "https://fluidframework.com", @@ -75,7 +75,7 @@ "devDependencies": { "@biomejs/biome": "~1.9.3", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@fluidframework/test-utils": "workspace:~", "@testing-library/dom": "^10.4.0", diff --git a/packages/tools/devtools/devtools-view/.eslintrc.cjs b/packages/tools/devtools/devtools-view/.eslintrc.cjs index a156e6950763..db321f388876 100644 --- a/packages/tools/devtools/devtools-view/.eslintrc.cjs +++ b/packages/tools/devtools/devtools-view/.eslintrc.cjs @@ -18,6 +18,7 @@ module.exports = { // Disabled because they disagrees with React common patterns / best practices. "@typescript-eslint/unbound-method": "off", "unicorn/consistent-function-scoping": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", // Disabled because they conflict with Prettier. "unicorn/no-nested-ternary": "off", diff --git a/packages/tools/devtools/devtools-view/CHANGELOG.md b/packages/tools/devtools/devtools-view/CHANGELOG.md index fe92f7ab0662..dfd796abf167 100644 --- a/packages/tools/devtools/devtools-view/CHANGELOG.md +++ b/packages/tools/devtools/devtools-view/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/devtools-view +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/devtools/devtools-view/package.json b/packages/tools/devtools/devtools-view/package.json index 7c4c4bf1a073..13b0c2b4702d 100644 --- a/packages/tools/devtools/devtools-view/package.json +++ b/packages/tools/devtools/devtools-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/devtools-view", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Contains a visualization suite for use alongside the Fluid Devtools", "homepage": "https://fluidframework.com", @@ -85,9 +85,9 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/core-interfaces": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", "@fluidframework/eslint-config-fluid": "^5.4.0", diff --git a/packages/tools/devtools/devtools/CHANGELOG.md b/packages/tools/devtools/devtools/CHANGELOG.md index 33243a8e1fb1..36256bd181de 100644 --- a/packages/tools/devtools/devtools/CHANGELOG.md +++ b/packages/tools/devtools/devtools/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/devtools +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/devtools/devtools/package.json b/packages/tools/devtools/devtools/package.json index 72d632cd1f49..742c182d9e5e 100644 --- a/packages/tools/devtools/devtools/package.json +++ b/packages/tools/devtools/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/devtools", - "version": "2.5.0", + "version": "2.10.0", "description": "Fluid Framework developer tools", "homepage": "https://fluidframework.com", "repository": { @@ -126,10 +126,10 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", - "@fluidframework/devtools-previous": "npm:@fluidframework/devtools@~2.4.0", + "@fluidframework/build-tools": "^0.50.0", + "@fluidframework/devtools-previous": "npm:@fluidframework/devtools@~2.5.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@microsoft/api-extractor": "7.47.8", "@types/chai": "^4.0.0", diff --git a/packages/tools/fetch-tool/.eslintrc.cjs b/packages/tools/fetch-tool/.eslintrc.cjs index 30a416fcf4dd..954133a1982e 100644 --- a/packages/tools/fetch-tool/.eslintrc.cjs +++ b/packages/tools/fetch-tool/.eslintrc.cjs @@ -11,5 +11,6 @@ module.exports = { rules: { // This library is used in the browser, so we don't want dependencies on most node libraries. "import/no-nodejs-modules": ["error", { allow: ["child_process", "fs", "util"] }], + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/tools/fetch-tool/CHANGELOG.md b/packages/tools/fetch-tool/CHANGELOG.md index 9c16c55adb72..23142b0c1e81 100644 --- a/packages/tools/fetch-tool/CHANGELOG.md +++ b/packages/tools/fetch-tool/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-tools/fetch-tool +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/fetch-tool/package.json b/packages/tools/fetch-tool/package.json index 579df656c5b7..13caf7f4a39c 100644 --- a/packages/tools/fetch-tool/package.json +++ b/packages/tools/fetch-tool/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/fetch-tool", - "version": "2.5.0", + "version": "2.10.0", "description": "Console tool to fetch Fluid data from relay service", "homepage": "https://fluidframework.com", "repository": { @@ -51,10 +51,10 @@ }, "devDependencies": { "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", - "@fluid-tools/fetch-tool-previous": "npm:@fluid-tools/fetch-tool@~2.4.0", + "@fluid-tools/build-cli": "^0.50.0", + "@fluid-tools/fetch-tool-previous": "npm:@fluid-tools/fetch-tool@~2.5.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/node": "^18.19.0", "copyfiles": "^2.4.1", diff --git a/packages/tools/fluid-runner/CHANGELOG.md b/packages/tools/fluid-runner/CHANGELOG.md index beea26a5ce34..61f5d7e13bd9 100644 --- a/packages/tools/fluid-runner/CHANGELOG.md +++ b/packages/tools/fluid-runner/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/fluid-runner +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/fluid-runner/package.json b/packages/tools/fluid-runner/package.json index b4f3d814653d..97a1d4f2038c 100644 --- a/packages/tools/fluid-runner/package.json +++ b/packages/tools/fluid-runner/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/fluid-runner", - "version": "2.5.0", + "version": "2.10.0", "description": "Utility for running various functionality inside a Fluid Framework environment", "homepage": "https://fluidframework.com", "repository": { @@ -133,11 +133,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/fluid-runner-previous": "npm:@fluidframework/fluid-runner@~2.4.0", + "@fluidframework/fluid-runner-previous": "npm:@fluidframework/fluid-runner@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/tools/replay-tool/CHANGELOG.md b/packages/tools/replay-tool/CHANGELOG.md index 339037073c50..09f3ccdbeca1 100644 --- a/packages/tools/replay-tool/CHANGELOG.md +++ b/packages/tools/replay-tool/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-internal/replay-tool +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/tools/replay-tool/package.json b/packages/tools/replay-tool/package.json index f7adace5c4e7..683997f33512 100644 --- a/packages/tools/replay-tool/package.json +++ b/packages/tools/replay-tool/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/replay-tool", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "A tool that lets the user to replay ops.", "homepage": "https://fluidframework.com", @@ -80,9 +80,9 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", "@types/json-stable-stringify": "^1.0.32", "@types/node": "^18.19.0", diff --git a/packages/utils/odsp-doclib-utils/CHANGELOG.md b/packages/utils/odsp-doclib-utils/CHANGELOG.md index 174064bb06e4..4947b4aa86d0 100644 --- a/packages/utils/odsp-doclib-utils/CHANGELOG.md +++ b/packages/utils/odsp-doclib-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/odsp-doclib-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/utils/odsp-doclib-utils/package.json b/packages/utils/odsp-doclib-utils/package.json index 20d88e4cf607..1ee400c79290 100644 --- a/packages/utils/odsp-doclib-utils/package.json +++ b/packages/utils/odsp-doclib-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/odsp-doclib-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "ODSP utilities", "homepage": "https://fluidframework.com", "repository": { @@ -130,11 +130,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/odsp-doclib-utils-previous": "npm:@fluidframework/odsp-doclib-utils@~2.4.0", + "@fluidframework/odsp-doclib-utils-previous": "npm:@fluidframework/odsp-doclib-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", diff --git a/packages/utils/odsp-doclib-utils/src/packageVersion.ts b/packages/utils/odsp-doclib-utils/src/packageVersion.ts index 4e0da03024ee..7e14c1aa982c 100644 --- a/packages/utils/odsp-doclib-utils/src/packageVersion.ts +++ b/packages/utils/odsp-doclib-utils/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/odsp-doclib-utils"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/packages/utils/telemetry-utils/.eslintrc.cjs b/packages/utils/telemetry-utils/.eslintrc.cjs index 956f065bac05..b3e5dfc66bd2 100644 --- a/packages/utils/telemetry-utils/.eslintrc.cjs +++ b/packages/utils/telemetry-utils/.eslintrc.cjs @@ -8,5 +8,7 @@ module.exports = { parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, - rules: {}, + rules: { + "@fluid-internal/fluid/no-unchecked-record-access": "warn", + }, }; diff --git a/packages/utils/telemetry-utils/CHANGELOG.md b/packages/utils/telemetry-utils/CHANGELOG.md index 22fa27813894..f7406bcca428 100644 --- a/packages/utils/telemetry-utils/CHANGELOG.md +++ b/packages/utils/telemetry-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/telemetry-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/utils/telemetry-utils/package.json b/packages/utils/telemetry-utils/package.json index 0df40e73c57f..5015b8bdb273 100644 --- a/packages/utils/telemetry-utils/package.json +++ b/packages/utils/telemetry-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/telemetry-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Collection of telemetry relates utilities for Fluid", "homepage": "https://fluidframework.com", "repository": { @@ -128,11 +128,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@~2.4.0", + "@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/debug": "^4.1.5", "@types/mocha": "^9.1.1", diff --git a/packages/utils/telemetry-utils/src/index.ts b/packages/utils/telemetry-utils/src/index.ts index a0d52c95f46c..7df70caef4d1 100644 --- a/packages/utils/telemetry-utils/src/index.ts +++ b/packages/utils/telemetry-utils/src/index.ts @@ -89,7 +89,6 @@ export type { ITelemetryErrorEventExt, ITelemetryPerformanceEventExt, ITelemetryLoggerExt, - ITaggedTelemetryPropertyTypeExt, ITelemetryPropertiesExt, TelemetryEventCategory, } from "./telemetryTypes.js"; diff --git a/packages/utils/telemetry-utils/src/telemetryTypes.ts b/packages/utils/telemetry-utils/src/telemetryTypes.ts index a57cbebcea75..4f4117a83c83 100644 --- a/packages/utils/telemetry-utils/src/telemetryTypes.ts +++ b/packages/utils/telemetry-utils/src/telemetryTypes.ts @@ -35,19 +35,6 @@ export type TelemetryEventPropertyTypeExt = | (string | number | boolean)[] | Record; -/** - * A property to be logged to telemetry containing both the value and a tag. Tags are generic strings that can be used - * to mark pieces of information that should be organized or handled differently by loggers in various first or third - * party scenarios. For example, tags are used to mark personal information that should not be stored in logs. - * - * @deprecated Use {@link @fluidframework/core-interfaces#Tagged}\<{@link TelemetryEventPropertyTypeExt}\> - * @internal - */ -export interface ITaggedTelemetryPropertyTypeExt { - value: TelemetryEventPropertyTypeExt; - tag: string; -} - /** * JSON-serializable properties, which will be logged with telemetry. * @legacy diff --git a/packages/utils/telemetry-utils/src/test/telemetryLogger.spec.ts b/packages/utils/telemetry-utils/src/test/telemetryLogger.spec.ts index f7aabdfbbe91..4a39ca755a80 100644 --- a/packages/utils/telemetry-utils/src/test/telemetryLogger.spec.ts +++ b/packages/utils/telemetry-utils/src/test/telemetryLogger.spec.ts @@ -5,7 +5,7 @@ import assert from "node:assert"; -import type { ITelemetryBaseEvent } from "@fluidframework/core-interfaces"; +import type { ITelemetryBaseEvent, Tagged } from "@fluidframework/core-interfaces"; import { type ITelemetryLoggerPropertyBag, @@ -13,10 +13,7 @@ import { TelemetryLogger, convertToBasePropertyType, } from "../logger.js"; -import type { - ITaggedTelemetryPropertyTypeExt, - TelemetryEventPropertyTypeExt, -} from "../telemetryTypes.js"; +import type { TelemetryEventPropertyTypeExt } from "../telemetryTypes.js"; class TestTelemetryLogger extends TelemetryLogger { public events: ITelemetryBaseEvent[] = []; @@ -205,48 +202,48 @@ describe("TelemetryLogger", () => { describe("convertToBasePropertyType", () => { describe("tagged properties", () => { it("tagged number", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: 123, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: 123, tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged string", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: "test", tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: "test", tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged boolean", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: true, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: true, tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged array", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: [true, "test"], tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: JSON.stringify([true, "test"]), tag: "tag", }; @@ -259,12 +256,12 @@ describe("convertToBasePropertyType", () => { c: true, d: [false, "okay"], }; - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: JSON.stringify(value), tag: "tag", }; @@ -327,66 +324,66 @@ describe("convertToBasePropertyType", () => { // These are unexpected, but it's good to have coverage to ensure they behave "well enough" // (e.g. they shouldn't crash) describe("Check various invalid (per typings) cases", () => { - it("nested ITaggedTelemetryPropertyTypeExt", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + it("nested Tagged", () => { + const taggedProperty: Tagged = { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions value: { value: true, tag: "tag" } as TelemetryEventPropertyTypeExt, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: '{"value":true,"tag":"tag"}', tag: "tag", }; assert.deepStrictEqual(converted, expected); }); - it("nested non ITaggedTelemetryPropertyTypeExt", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + it("nested non Tagged", () => { + const taggedProperty: Tagged = { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions value: { foo: 3, bar: { x: 5 } as unknown } as TelemetryEventPropertyTypeExt, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: '{"foo":3,"bar":{"x":5}}', tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged function", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: function x() { return 54; } as unknown as TelemetryEventPropertyTypeExt, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: "INVALID PROPERTY (typed as function)", tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged null value", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { // eslint-disable-next-line unicorn/no-null value: null as unknown as TelemetryEventPropertyTypeExt, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: "null", tag: "tag", }; assert.deepStrictEqual(converted, expected); }); it("tagged symbol", () => { - const taggedProperty: ITaggedTelemetryPropertyTypeExt = { + const taggedProperty: Tagged = { value: Symbol("Test") as unknown as TelemetryEventPropertyTypeExt, tag: "tag", }; const converted = convertToBasePropertyType(taggedProperty); - const expected: ITaggedTelemetryPropertyTypeExt = { + const expected: Tagged = { value: "INVALID PROPERTY (typed as symbol)", tag: "tag", }; @@ -400,7 +397,7 @@ describe("convertToBasePropertyType", () => { const converted = convertToBasePropertyType( nestedObject as unknown as | TelemetryEventPropertyTypeExt - | ITaggedTelemetryPropertyTypeExt, + | Tagged, ); const expected = '{"foo":{"foo":true,"test":"test"},"test":"test"}'; assert.deepStrictEqual(converted, expected); @@ -408,14 +405,16 @@ describe("convertToBasePropertyType", () => { it("function", () => { const converted = convertToBasePropertyType(function x() { return 54; - } as unknown as TelemetryEventPropertyTypeExt | ITaggedTelemetryPropertyTypeExt); + } as unknown as TelemetryEventPropertyTypeExt | Tagged); const expected = "INVALID PROPERTY (typed as function)"; assert.deepStrictEqual(converted, expected); }); it("null", () => { const converted = convertToBasePropertyType( // eslint-disable-next-line unicorn/no-null - null as unknown as TelemetryEventPropertyTypeExt | ITaggedTelemetryPropertyTypeExt, + null as unknown as + | TelemetryEventPropertyTypeExt + | Tagged, ); const expected = "null"; assert.deepStrictEqual(converted, expected); @@ -424,7 +423,7 @@ describe("convertToBasePropertyType", () => { const converted = convertToBasePropertyType( Symbol("Test") as unknown as | TelemetryEventPropertyTypeExt - | ITaggedTelemetryPropertyTypeExt, + | Tagged, ); const expected = "INVALID PROPERTY (typed as symbol)"; assert.deepStrictEqual(converted, expected); diff --git a/packages/utils/tool-utils/.eslintrc.cjs b/packages/utils/tool-utils/.eslintrc.cjs index 865881d212c5..9756b6229ce6 100644 --- a/packages/utils/tool-utils/.eslintrc.cjs +++ b/packages/utils/tool-utils/.eslintrc.cjs @@ -11,5 +11,6 @@ module.exports = { rules: { // This package is intended to be used in node.js environments "import/no-nodejs-modules": "off", + "@fluid-internal/fluid/no-unchecked-record-access": "warn", }, }; diff --git a/packages/utils/tool-utils/CHANGELOG.md b/packages/utils/tool-utils/CHANGELOG.md index d10d9da5012e..e13246adc64e 100644 --- a/packages/utils/tool-utils/CHANGELOG.md +++ b/packages/utils/tool-utils/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluidframework/tool-utils +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/packages/utils/tool-utils/package.json b/packages/utils/tool-utils/package.json index 0a3b8f495097..c8d8cb4579e1 100644 --- a/packages/utils/tool-utils/package.json +++ b/packages/utils/tool-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/tool-utils", - "version": "2.5.0", + "version": "2.10.0", "description": "Common utilities for Fluid tools", "homepage": "https://fluidframework.com", "repository": { @@ -112,11 +112,11 @@ "@arethetypeswrong/cli": "^0.16.4", "@biomejs/biome": "~1.9.3", "@fluid-internal/mocha-test-setup": "workspace:~", - "@fluid-tools/build-cli": "^0.49.0", + "@fluid-tools/build-cli": "^0.50.0", "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.49.0", + "@fluidframework/build-tools": "^0.50.0", "@fluidframework/eslint-config-fluid": "^5.4.0", - "@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@~2.4.0", + "@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@2.5.0", "@microsoft/api-extractor": "7.47.8", "@types/debug": "^4.1.5", "@types/mocha": "^9.1.1", diff --git a/packages/utils/tool-utils/src/packageVersion.ts b/packages/utils/tool-utils/src/packageVersion.ts index cd318ae1bcb2..f22c59bc6e3e 100644 --- a/packages/utils/tool-utils/src/packageVersion.ts +++ b/packages/utils/tool-utils/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/tool-utils"; -export const pkgVersion = "2.5.0"; +export const pkgVersion = "2.10.0"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3936bca87e84..d51c87c1eca6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@changesets/cli': specifier: ^2.27.8 version: 2.27.8 @@ -37,8 +37,8 @@ importers: specifier: workspace:~ version: link:packages/tools/changelog-generator-wrapper '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluid-tools/markdown-magic': specifier: workspace:~ version: link:tools/markdown-magic @@ -46,8 +46,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -123,13 +123,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -172,19 +172,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/azure-service-utils-previous': - specifier: npm:@fluidframework/azure-service-utils@~2.4.0 - version: /@fluidframework/azure-service-utils@2.4.0 + specifier: npm:@fluidframework/azure-service-utils@~2.5.0 + version: /@fluidframework/azure-service-utils@2.5.0 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -302,16 +302,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -365,28 +365,28 @@ importers: version: 3.25.0 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@emotion/react': specifier: ^11.13.3 version: 11.13.3(@types/react@18.3.11)(react@18.3.1) '@emotion/styled': specifier: ^11.13.0 version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1) - '@fluid-experimental/ai-collab': - specifier: workspace:~ - version: link:../../../packages/framework/ai-collab '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) + '@fluidframework/ai-collab': + specifier: workspace:~ + version: link:../../../packages/framework/ai-collab '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/devtools': specifier: workspace:~ version: link:../../../packages/tools/devtools/devtools @@ -455,7 +455,7 @@ importers: version: 5.0.0 typechat: specifier: ^0.1.1 - version: 0.1.1(typescript@5.4.5) + version: 0.1.1(typescript@5.4.5)(zod@3.23.8) typescript: specifier: ~5.4.5 version: 5.4.5 @@ -498,16 +498,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -586,16 +586,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -716,16 +716,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -882,16 +882,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1033,16 +1033,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1160,16 +1160,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1243,6 +1243,64 @@ importers: specifier: ^6.0.1 version: 6.0.1 + examples/apps/tree-cli-app: + dependencies: + '@fluidframework/core-interfaces': + specifier: workspace:~ + version: link:../../../packages/common/core-interfaces + '@fluidframework/id-compressor': + specifier: workspace:~ + version: link:../../../packages/runtime/id-compressor + '@fluidframework/runtime-utils': + specifier: workspace:~ + version: link:../../../packages/runtime/runtime-utils + '@fluidframework/tree': + specifier: workspace:~ + version: link:../../../packages/dds/tree + '@sinclair/typebox': + specifier: ^0.32.29 + version: 0.32.35 + devDependencies: + '@biomejs/biome': + specifier: ~1.9.3 + version: 1.9.4 + '@fluid-internal/mocha-test-setup': + specifier: workspace:~ + version: link:../../../packages/test/mocha-test-setup + '@fluidframework/build-tools': + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) + '@fluidframework/eslint-config-fluid': + specifier: ^5.4.0 + version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) + '@types/mocha': + specifier: ^9.1.1 + version: 9.1.1 + '@types/node': + specifier: ^18.19.0 + version: 18.19.54 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + eslint: + specifier: ~8.55.0 + version: 8.55.0 + mocha: + specifier: ^10.2.0 + version: 10.7.3 + mocha-json-output-reporter: + specifier: ^2.0.1 + version: 2.1.0(mocha@10.7.3)(moment@2.30.1) + mocha-multi-reporters: + specifier: ^1.5.1 + version: 1.5.1(mocha@10.7.3) + rimraf: + specifier: ^4.4.0 + version: 4.4.1 + typescript: + specifier: ~5.4.5 + version: 5.4.5 + examples/apps/tree-comparison: dependencies: '@fluid-example/example-utils': @@ -1308,16 +1366,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1435,7 +1493,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../../utils/webpack-fluid-loader @@ -1443,8 +1501,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1547,16 +1605,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1614,7 +1672,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../../utils/webpack-fluid-loader @@ -1622,8 +1680,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1729,7 +1787,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../../utils/webpack-fluid-loader @@ -1737,8 +1795,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1835,7 +1893,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../../utils/webpack-fluid-loader @@ -1843,8 +1901,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -1950,16 +2008,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2053,7 +2111,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup @@ -2064,8 +2122,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2165,13 +2223,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2286,7 +2344,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2294,8 +2352,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2404,7 +2462,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2412,8 +2470,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2537,7 +2595,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2545,8 +2603,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2613,7 +2671,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2621,8 +2679,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2722,7 +2780,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2730,8 +2788,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2831,7 +2889,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -2839,8 +2897,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2922,13 +2980,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -2959,13 +3017,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3038,7 +3096,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../../utils/webpack-fluid-loader @@ -3046,8 +3104,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3141,13 +3199,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3172,16 +3230,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3209,13 +3267,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3249,13 +3307,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3289,13 +3347,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3401,7 +3459,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -3409,8 +3467,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3522,7 +3580,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -3530,8 +3588,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3613,7 +3671,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup @@ -3621,14 +3679,14 @@ importers: specifier: workspace:~ version: link:../../../packages/test/test-version-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3719,7 +3777,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -3727,8 +3785,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -3855,7 +3913,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/webpack-fluid-loader': specifier: workspace:~ version: link:../../utils/webpack-fluid-loader @@ -3869,8 +3927,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4063,13 +4121,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4220,13 +4278,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../../../packages/common/container-definitions @@ -4365,16 +4423,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4465,22 +4523,22 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@cerner/duplicate-package-checker-webpack-plugin': specifier: ~2.3.0 version: 2.3.0(webpack@5.95.0) '@fluid-tools/version-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/bundle-size-tools': - specifier: ^0.49.0 - version: 0.49.0(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(webpack-cli@5.1.4) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4613,16 +4671,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4719,16 +4777,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4843,19 +4901,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -4964,16 +5022,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5118,16 +5176,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5296,16 +5354,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5462,16 +5520,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5598,16 +5656,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5710,16 +5768,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5828,16 +5886,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -5946,7 +6004,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../../packages/test/mocha-test-setup @@ -5954,8 +6012,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@types/lodash': specifier: ^4.14.118 version: 4.17.9 @@ -6043,7 +6101,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../../packages/test/mocha-test-setup @@ -6051,8 +6109,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -6178,7 +6236,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-experimental/property-common': specifier: workspace:~ version: link:../property-common @@ -6189,14 +6247,14 @@ importers: specifier: workspace:~ version: link:../../../../packages/test/test-drivers '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-loader': specifier: workspace:~ version: link:../../../../packages/loader/container-loader @@ -6302,7 +6360,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../../packages/test/mocha-test-setup @@ -6310,8 +6368,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@types/mocha': specifier: ^9.1.1 version: 9.1.1 @@ -6396,10 +6454,10 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-experimental/attributable-map-previous': - specifier: npm:@fluid-experimental/attributable-map@~2.4.0 - version: /@fluid-experimental/attributable-map@2.4.0 + specifier: npm:@fluid-experimental/attributable-map@2.5.0 + version: /@fluid-experimental/attributable-map@2.5.0 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup @@ -6413,14 +6471,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../../packages/common/container-definitions @@ -6505,7 +6563,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../../packages/test/mocha-test-setup @@ -6513,14 +6571,14 @@ importers: specifier: workspace:~ version: link:../../../../packages/dds/test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -6602,7 +6660,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../../../packages/test/mocha-test-setup @@ -6610,14 +6668,14 @@ importers: specifier: workspace:~ version: link:../../../../../packages/dds/test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -6696,7 +6754,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup @@ -6704,14 +6762,14 @@ importers: specifier: workspace:~ version: link:../../../packages/dds/test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -6826,7 +6884,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup @@ -6843,8 +6901,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-loader': specifier: workspace:~ version: link:../../../packages/loader/container-loader @@ -6956,16 +7014,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7026,16 +7084,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7096,19 +7154,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../packages/test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7193,22 +7251,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/client-utils-previous': - specifier: npm:@fluid-internal/client-utils@~2.4.0 - version: /@fluid-internal/client-utils@2.4.0 + specifier: npm:@fluid-internal/client-utils@2.5.0 + version: /@fluid-internal/client-utils@2.5.0 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7317,19 +7375,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions-previous': - specifier: npm:@fluidframework/container-definitions@~2.4.0 - version: /@fluidframework/container-definitions@2.4.0 + specifier: npm:@fluidframework/container-definitions@~2.5.0 + version: /@fluidframework/container-definitions@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7365,19 +7423,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/core-interfaces-previous': - specifier: npm:@fluidframework/core-interfaces@~2.4.0 - version: /@fluidframework/core-interfaces@2.4.0 + specifier: npm:@fluidframework/core-interfaces@~2.5.0 + version: /@fluidframework/core-interfaces@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7413,7 +7471,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -7421,17 +7479,17 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/core-utils-previous': - specifier: npm:@fluidframework/core-utils@~2.4.0 - version: /@fluidframework/core-utils@2.4.0 + specifier: npm:@fluidframework/core-utils@~2.5.0 + version: /@fluidframework/core-utils@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7498,19 +7556,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-definitions-previous': - specifier: npm:@fluidframework/driver-definitions@~2.4.0 - version: /@fluidframework/driver-definitions@2.4.0 + specifier: npm:@fluidframework/driver-definitions@~2.5.0 + version: /@fluidframework/driver-definitions@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7565,7 +7623,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -7573,17 +7631,17 @@ importers: specifier: workspace:~ version: link:../test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/cell-previous': - specifier: npm:@fluidframework/cell@~2.4.0 - version: /@fluidframework/cell@2.4.0 + specifier: npm:@fluidframework/cell@~2.5.0 + version: /@fluidframework/cell@2.5.0 '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -7668,25 +7726,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions '@fluidframework/counter-previous': - specifier: npm:@fluidframework/counter@~2.4.0 - version: /@fluidframework/counter@2.4.0 + specifier: npm:@fluidframework/counter@2.5.0 + version: /@fluidframework/counter@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -7768,19 +7826,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -7883,7 +7941,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -7897,14 +7955,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -7912,8 +7970,8 @@ importers: specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/map-previous': - specifier: npm:@fluidframework/map@~2.4.0 - version: /@fluidframework/map@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/map@2.5.0 + version: /@fluidframework/map@2.5.0(debug@4.3.7) '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8016,7 +8074,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8030,14 +8088,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -8045,8 +8103,8 @@ importers: specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/matrix-previous': - specifier: npm:@fluidframework/matrix@~2.4.0 - version: /@fluidframework/matrix@2.4.0 + specifier: npm:@fluidframework/matrix@2.5.0 + version: /@fluidframework/matrix@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8149,7 +8207,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8163,20 +8221,20 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/merge-tree-previous': - specifier: npm:@fluidframework/merge-tree@~2.4.0 - version: /@fluidframework/merge-tree@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/merge-tree@2.5.0 + version: /@fluidframework/merge-tree@2.5.0(debug@4.3.7) '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8270,7 +8328,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8278,20 +8336,20 @@ importers: specifier: workspace:~ version: link:../test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/ordered-collection-previous': - specifier: npm:@fluidframework/ordered-collection@~2.4.0 - version: /@fluidframework/ordered-collection@2.4.0 + specifier: npm:@fluidframework/ordered-collection@2.5.0 + version: /@fluidframework/ordered-collection@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8376,7 +8434,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8384,14 +8442,14 @@ importers: specifier: workspace:~ version: link:../test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -8479,7 +8537,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8487,20 +8545,20 @@ importers: specifier: workspace:~ version: link:../test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/register-collection-previous': - specifier: npm:@fluidframework/register-collection@~2.4.0 - version: /@fluidframework/register-collection@2.4.0 + specifier: npm:@fluidframework/register-collection@2.5.0 + version: /@fluidframework/register-collection@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8594,7 +8652,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8608,14 +8666,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -8623,8 +8681,8 @@ importers: specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/sequence-previous': - specifier: npm:@fluidframework/sequence@~2.4.0 - version: /@fluidframework/sequence@2.4.0 + specifier: npm:@fluidframework/sequence@2.5.0 + version: /@fluidframework/sequence@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8733,7 +8791,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8741,20 +8799,20 @@ importers: specifier: workspace:~ version: link:../../test/test-pairwise-generator '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/shared-object-base-previous': - specifier: npm:@fluidframework/shared-object-base@~2.4.0 - version: /@fluidframework/shared-object-base@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/shared-object-base@2.5.0 + version: /@fluidframework/shared-object-base@2.5.0(debug@4.3.7) '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8842,19 +8900,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -8862,8 +8920,8 @@ importers: specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/shared-summary-block-previous': - specifier: npm:@fluidframework/shared-summary-block@~2.4.0 - version: /@fluidframework/shared-summary-block@2.4.0 + specifier: npm:@fluidframework/shared-summary-block@2.5.0 + version: /@fluidframework/shared-summary-block@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -8957,7 +9015,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -8968,20 +9026,20 @@ importers: specifier: workspace:~ version: link:../test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/task-manager-previous': - specifier: npm:@fluidframework/task-manager@~2.4.0 - version: /@fluidframework/task-manager@2.4.0 + specifier: npm:@fluidframework/task-manager@2.5.0 + version: /@fluidframework/task-manager@2.5.0 '@fluidframework/test-runtime-utils': specifier: workspace:~ version: link:../../runtime/test-runtime-utils @@ -9081,19 +9139,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -9199,7 +9257,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -9216,14 +9274,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -9240,8 +9298,8 @@ importers: specifier: workspace:~ version: link:../../test/test-utils '@fluidframework/tree-previous': - specifier: npm:@fluidframework/tree@~2.4.0 - version: /@fluidframework/tree@2.4.0 + specifier: npm:@fluidframework/tree@2.5.0 + version: /@fluidframework/tree@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -9335,19 +9393,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/debugger-previous': - specifier: npm:@fluidframework/debugger@~2.4.0 - version: /@fluidframework/debugger@2.4.0 + specifier: npm:@fluidframework/debugger@~2.5.0 + version: /@fluidframework/debugger@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -9402,22 +9460,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-base-previous': - specifier: npm:@fluidframework/driver-base@~2.4.0 - version: /@fluidframework/driver-base@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/driver-base@~2.5.0 + version: /@fluidframework/driver-base@2.5.0(debug@4.3.7) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -9490,19 +9548,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-web-cache-previous': - specifier: npm:@fluidframework/driver-web-cache@~2.4.0 - version: /@fluidframework/driver-web-cache@2.4.0 + specifier: npm:@fluidframework/driver-web-cache@~2.5.0 + version: /@fluidframework/driver-web-cache@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -9566,22 +9624,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/file-driver-previous': - specifier: npm:@fluidframework/file-driver@~2.4.0 - version: /@fluidframework/file-driver@2.4.0 + specifier: npm:@fluidframework/file-driver@2.5.0 + version: /@fluidframework/file-driver@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -9660,25 +9718,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/local-driver-previous': - specifier: npm:@fluidframework/local-driver@~2.4.0 - version: /@fluidframework/local-driver@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/local-driver@2.5.0 + version: /@fluidframework/local-driver@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -9775,25 +9833,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/odsp-driver-previous': - specifier: npm:@fluidframework/odsp-driver@~2.4.0 - version: /@fluidframework/odsp-driver@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/odsp-driver@2.5.0 + version: /@fluidframework/odsp-driver@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -9860,22 +9918,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/odsp-driver-definitions-previous': - specifier: npm:@fluidframework/odsp-driver-definitions@~2.4.0 - version: /@fluidframework/odsp-driver-definitions@2.4.0 + specifier: npm:@fluidframework/odsp-driver-definitions@2.5.0 + version: /@fluidframework/odsp-driver-definitions@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -9927,25 +9985,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/odsp-urlresolver-previous': - specifier: npm:@fluidframework/odsp-urlresolver@~2.4.0 - version: /@fluidframework/odsp-urlresolver@2.4.0 + specifier: npm:@fluidframework/odsp-urlresolver@2.5.0 + version: /@fluidframework/odsp-urlresolver@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10012,22 +10070,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/replay-driver-previous': - specifier: npm:@fluidframework/replay-driver@~2.4.0 - version: /@fluidframework/replay-driver@2.4.0 + specifier: npm:@fluidframework/replay-driver@2.5.0 + version: /@fluidframework/replay-driver@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10103,25 +10161,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/routerlicious-driver-previous': - specifier: npm:@fluidframework/routerlicious-driver@~2.4.0 - version: /@fluidframework/routerlicious-driver@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/routerlicious-driver@2.5.0 + version: /@fluidframework/routerlicious-driver@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10203,25 +10261,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/routerlicious-urlresolver-previous': - specifier: npm:@fluidframework/routerlicious-urlresolver@~2.4.0 - version: /@fluidframework/routerlicious-urlresolver@2.4.0 + specifier: npm:@fluidframework/routerlicious-urlresolver@2.5.0 + version: /@fluidframework/routerlicious-urlresolver@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10294,25 +10352,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/tinylicious-driver-previous': - specifier: npm:@fluidframework/tinylicious-driver@~2.4.0 - version: /@fluidframework/tinylicious-driver@2.4.0 + specifier: npm:@fluidframework/tinylicious-driver@2.5.0 + version: /@fluidframework/tinylicious-driver@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10397,19 +10455,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/agent-scheduler-previous': - specifier: npm:@fluidframework/agent-scheduler@~2.4.0 - version: /@fluidframework/agent-scheduler@2.4.0 + specifier: npm:@fluidframework/agent-scheduler@~2.5.0 + version: /@fluidframework/agent-scheduler@2.5.0 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -10443,9 +10501,24 @@ importers: packages/framework/ai-collab: dependencies: + '@fluidframework/core-utils': + specifier: workspace:~ + version: link:../../common/core-utils + '@fluidframework/runtime-utils': + specifier: workspace:~ + version: link:../../runtime/runtime-utils + '@fluidframework/telemetry-utils': + specifier: workspace:~ + version: link:../../utils/telemetry-utils '@fluidframework/tree': specifier: workspace:~ version: link:../../dds/tree + openai: + specifier: ^4.67.3 + version: 4.68.0(zod@3.23.8) + typechat: + specifier: ^0.1.1 + version: 0.1.1(typescript@5.4.5)(zod@3.23.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -10455,22 +10528,28 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) + '@fluidframework/id-compressor': + specifier: workspace:~ + version: link:../../runtime/id-compressor + '@fluidframework/test-runtime-utils': + specifier: workspace:^ + version: link:../../runtime/test-runtime-utils '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10567,22 +10646,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/aqueduct-previous': - specifier: npm:@fluidframework/aqueduct@~2.4.0 - version: /@fluidframework/aqueduct@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/aqueduct@~2.5.0 + version: /@fluidframework/aqueduct@2.5.0(debug@4.3.7) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -10676,7 +10755,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -10684,14 +10763,14 @@ importers: specifier: workspace:~ version: link:../../test/stochastic-test-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -10764,19 +10843,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -10858,7 +10937,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/client-utils': specifier: workspace:~ version: link:../../../common/client-utils @@ -10866,11 +10945,11 @@ importers: specifier: workspace:~ version: link:../../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/test-utils': specifier: workspace:~ version: link:../../../test/test-utils @@ -10976,16 +11055,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11037,19 +11116,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11143,16 +11222,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11231,25 +11310,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/fluid-static-previous': - specifier: npm:@fluidframework/fluid-static@~2.4.0 - version: /@fluidframework/fluid-static@2.4.0 + specifier: npm:@fluidframework/fluid-static@2.5.0 + version: /@fluidframework/fluid-static@2.5.0 '@fluidframework/map': specifier: workspace:~ version: link:../../dds/map @@ -11322,19 +11401,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-private/test-dds-utils': specifier: workspace:~ version: link:../../dds/test-dds-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11413,16 +11492,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-definitions': specifier: workspace:~ version: link:../../common/driver-definitions @@ -11504,25 +11583,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/request-handler-previous': - specifier: npm:@fluidframework/request-handler@~2.4.0 - version: /@fluidframework/request-handler@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/request-handler@2.5.0 + version: /@fluidframework/request-handler@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -11583,19 +11662,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/core-interfaces': specifier: workspace:~ version: link:../../common/core-interfaces @@ -11609,8 +11688,8 @@ importers: specifier: workspace:~ version: link:../../runtime/runtime-utils '@fluidframework/synthesize-previous': - specifier: npm:@fluidframework/synthesize@~2.4.0 - version: /@fluidframework/synthesize@2.4.0 + specifier: npm:@fluidframework/synthesize@2.5.0 + version: /@fluidframework/synthesize@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -11677,19 +11756,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11697,8 +11776,8 @@ importers: specifier: workspace:~ version: link:../../runtime/test-runtime-utils '@fluidframework/undo-redo-previous': - specifier: npm:@fluidframework/undo-redo@~2.4.0 - version: /@fluidframework/undo-redo@2.4.0 + specifier: npm:@fluidframework/undo-redo@2.5.0 + version: /@fluidframework/undo-redo@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -11795,7 +11874,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -11803,17 +11882,17 @@ importers: specifier: workspace:~ version: link:../test-loader-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-loader-previous': - specifier: npm:@fluidframework/container-loader@~2.4.0 - version: /@fluidframework/container-loader@2.4.0 + specifier: npm:@fluidframework/container-loader@~2.5.0 + version: /@fluidframework/container-loader@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -11910,22 +11989,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-utils-previous': - specifier: npm:@fluidframework/driver-utils@~2.4.0 - version: /@fluidframework/driver-utils@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/driver-utils@~2.5.0 + version: /@fluidframework/driver-utils@2.5.0(debug@4.3.7) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12001,16 +12080,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12092,7 +12171,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -12106,17 +12185,17 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-runtime-previous': - specifier: npm:@fluidframework/container-runtime@~2.4.0 - version: /@fluidframework/container-runtime@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/container-runtime@~2.5.0 + version: /@fluidframework/container-runtime@2.5.0(debug@4.3.7) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12198,19 +12277,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-runtime-definitions-previous': - specifier: npm:@fluidframework/container-runtime-definitions@~2.4.0 - version: /@fluidframework/container-runtime-definitions@2.4.0 + specifier: npm:@fluidframework/container-runtime-definitions@~2.5.0 + version: /@fluidframework/container-runtime-definitions@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12280,22 +12359,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/datastore-previous': - specifier: npm:@fluidframework/datastore@~2.4.0 - version: /@fluidframework/datastore@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/datastore@~2.5.0 + version: /@fluidframework/datastore@2.5.0(debug@4.3.7) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12374,19 +12453,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/datastore-definitions-previous': - specifier: npm:@fluidframework/datastore-definitions@~2.4.0 - version: /@fluidframework/datastore-definitions@2.4.0 + specifier: npm:@fluidframework/datastore-definitions@~2.5.0 + version: /@fluidframework/datastore-definitions@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -12438,7 +12517,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup @@ -12449,20 +12528,20 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/id-compressor-previous': - specifier: npm:@fluidframework/id-compressor@~2.4.0 - version: /@fluidframework/id-compressor@2.4.0 + specifier: npm:@fluidframework/id-compressor@2.5.0 + version: /@fluidframework/id-compressor@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -12532,22 +12611,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/runtime-definitions-previous': - specifier: npm:@fluidframework/runtime-definitions@~2.4.0 - version: /@fluidframework/runtime-definitions@2.4.0 + specifier: npm:@fluidframework/runtime-definitions@2.5.0 + version: /@fluidframework/runtime-definitions@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -12611,25 +12690,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/runtime-utils-previous': - specifier: npm:@fluidframework/runtime-utils@~2.4.0 - version: /@fluidframework/runtime-utils@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/runtime-utils@2.5.0 + version: /@fluidframework/runtime-utils@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -12732,25 +12811,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/test-runtime-utils-previous': - specifier: npm:@fluidframework/test-runtime-utils@~2.4.0 - version: /@fluidframework/test-runtime-utils@2.4.0 + specifier: npm:@fluidframework/test-runtime-utils@2.5.0 + version: /@fluidframework/test-runtime-utils@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -12841,16 +12920,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/aqueduct': specifier: workspace:~ version: link:../../framework/aqueduct '@fluidframework/azure-client-previous': - specifier: npm:@fluidframework/azure-client@~2.4.0 - version: /@fluidframework/azure-client@2.4.0 + specifier: npm:@fluidframework/azure-client@2.5.0 + version: /@fluidframework/azure-client@2.5.0 '@fluidframework/azure-local-service': specifier: workspace:~ version: link:../../../azure/packages/azure-local-service @@ -12858,8 +12937,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13013,13 +13092,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/driver-definitions': specifier: workspace:~ version: link:../../../common/driver-definitions @@ -13128,13 +13207,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13216,19 +13295,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13319,10 +13398,10 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/aqueduct': specifier: workspace:~ version: link:../../framework/aqueduct @@ -13330,8 +13409,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-runtime': specifier: workspace:~ version: link:../../runtime/container-runtime @@ -13345,8 +13424,8 @@ importers: specifier: workspace:~ version: link:../../test/test-utils '@fluidframework/tinylicious-client-previous': - specifier: npm:@fluidframework/tinylicious-client@~2.4.0 - version: /@fluidframework/tinylicious-client@2.4.0 + specifier: npm:@fluidframework/tinylicious-client@2.5.0 + version: /@fluidframework/tinylicious-client@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -13388,7 +13467,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-experimental/tree': specifier: workspace:~ version: link:../../../experimental/dds/tree @@ -13402,14 +13481,14 @@ importers: specifier: workspace:~ version: link:../../loader/test-loader-utils '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/cell': specifier: workspace:~ version: link:../../dds/cell @@ -13511,25 +13590,19 @@ importers: version: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0) packages/test/local-server-tests: - dependencies: + devDependencies: + '@biomejs/biome': + specifier: ~1.9.3 + version: 1.9.4 '@fluid-experimental/tree': specifier: workspace:~ version: link:../../../experimental/dds/tree - '@fluid-internal/client-utils': - specifier: workspace:~ - version: link:../../common/client-utils '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-private/test-drivers': specifier: workspace:~ version: link:../test-drivers - '@fluid-private/test-loader-utils': - specifier: workspace:~ - version: link:../../loader/test-loader-utils - '@fluid-private/test-pairwise-generator': - specifier: workspace:~ - version: link:../test-pairwise-generator '@fluidframework/aqueduct': specifier: workspace:~ version: link:../../framework/aqueduct @@ -13537,11 +13610,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 - '@fluidframework/cell': - specifier: workspace:~ - version: link:../../dds/cell + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../common/container-definitions @@ -13566,9 +13636,6 @@ importers: '@fluidframework/datastore-definitions': specifier: workspace:~ version: link:../../runtime/datastore-definitions - '@fluidframework/driver-base': - specifier: workspace:~ - version: link:../../drivers/driver-base '@fluidframework/driver-definitions': specifier: workspace:~ version: link:../../common/driver-definitions @@ -13584,30 +13651,6 @@ importers: '@fluidframework/map': specifier: workspace:~ version: link:../../dds/map - '@fluidframework/matrix': - specifier: workspace:~ - version: link:../../dds/matrix - '@fluidframework/merge-tree': - specifier: workspace:~ - version: link:../../dds/merge-tree - '@fluidframework/odsp-doclib-utils': - specifier: workspace:~ - version: link:../../utils/odsp-doclib-utils - '@fluidframework/ordered-collection': - specifier: workspace:~ - version: link:../../dds/ordered-collection - '@fluidframework/protocol-definitions': - specifier: ^3.2.0 - version: 3.2.0 - '@fluidframework/register-collection': - specifier: workspace:~ - version: link:../../dds/register-collection - '@fluidframework/request-handler': - specifier: workspace:~ - version: link:../../framework/request-handler - '@fluidframework/routerlicious-driver': - specifier: workspace:~ - version: link:../../drivers/routerlicious-driver '@fluidframework/runtime-definitions': specifier: workspace:~ version: link:../../runtime/runtime-definitions @@ -13620,9 +13663,6 @@ importers: '@fluidframework/server-local-server': specifier: ^5.0.0 version: 5.0.0 - '@fluidframework/shared-object-base': - specifier: workspace:~ - version: link:../../dds/shared-object-base '@fluidframework/telemetry-utils': specifier: workspace:~ version: link:../../utils/telemetry-utils @@ -13632,22 +13672,12 @@ importers: '@fluidframework/tree': specifier: workspace:~ version: link:../../dds/tree - devDependencies: - '@biomejs/biome': - specifier: ~1.9.3 - version: 1.9.3 '@types/mocha': specifier: ^9.1.1 version: 9.1.1 - '@types/nock': - specifier: ^9.3.0 - version: 9.3.1 '@types/node': specifier: ^18.19.0 version: 18.19.54 - '@types/uuid': - specifier: ^9.0.2 - version: 9.0.8 c8: specifier: ^8.0.1 version: 8.0.1 @@ -13663,12 +13693,6 @@ importers: mocha-multi-reporters: specifier: ^1.5.1 version: 1.5.1(mocha@10.7.3) - moment: - specifier: ^2.21.0 - version: 2.30.1 - nock: - specifier: ^13.3.3 - version: 13.5.5 prettier: specifier: ~3.0.3 version: 3.0.3 @@ -13681,15 +13705,6 @@ importers: typescript: specifier: ~5.4.5 version: 5.4.5 - uuid: - specifier: ^9.0.0 - version: 9.0.1 - webpack: - specifier: ^5.94.0 - version: 5.95.0(webpack-cli@5.1.4) - webpack-cli: - specifier: ^5.1.4 - version: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0) packages/test/mocha-test-setup: dependencies: @@ -13711,16 +13726,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13823,19 +13838,19 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13887,7 +13902,7 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup @@ -13895,14 +13910,14 @@ importers: specifier: ^0.50.0 version: 0.50.0 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -13966,16 +13981,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14069,16 +14084,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14268,16 +14283,16 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14332,19 +14347,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14468,19 +14483,19 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14595,25 +14610,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/test-utils-previous': - specifier: npm:@fluidframework/test-utils@~2.4.0 - version: /@fluidframework/test-utils@2.4.0 + specifier: npm:@fluidframework/test-utils@2.5.0 + version: /@fluidframework/test-utils@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -14681,8 +14696,8 @@ importers: specifier: workspace:~ version: link:../test-drivers '@fluid-tools/version-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/agent-scheduler': specifier: workspace:~ version: link:../../framework/agent-scheduler @@ -14767,19 +14782,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -14854,10 +14869,10 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) jest-environment-puppeteer: specifier: ^10.1.3 version: 10.1.3(typescript@5.4.5) @@ -14922,22 +14937,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/devtools-previous': - specifier: npm:@fluidframework/devtools@~2.4.0 - version: /@fluidframework/devtools@2.4.0 + specifier: npm:@fluidframework/devtools@~2.5.0 + version: /@fluidframework/devtools@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -15034,7 +15049,7 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-example/example-utils': specifier: workspace:~ version: link:../../../../examples/utils/example-utils @@ -15048,8 +15063,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/container-definitions': specifier: workspace:~ version: link:../../../common/container-definitions @@ -15254,22 +15269,22 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/devtools-core-previous': - specifier: npm:@fluidframework/devtools-core@~2.4.0 - version: /@fluidframework/devtools-core@2.4.0 + specifier: npm:@fluidframework/devtools-core@~2.5.0 + version: /@fluidframework/devtools-core@2.5.0 '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -15417,13 +15432,13 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -15580,19 +15595,19 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -15779,19 +15794,19 @@ importers: devDependencies: '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluid-tools/fetch-tool-previous': - specifier: npm:@fluid-tools/fetch-tool@~2.4.0 - version: /@fluid-tools/fetch-tool@2.4.0 + specifier: npm:@fluid-tools/fetch-tool@~2.5.0 + version: /@fluid-tools/fetch-tool@2.5.0 '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -15852,25 +15867,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/fluid-runner-previous': - specifier: npm:@fluidframework/fluid-runner@~2.4.0 - version: /@fluidframework/fluid-runner@2.4.0 + specifier: npm:@fluidframework/fluid-runner@2.5.0 + version: /@fluidframework/fluid-runner@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -16006,16 +16021,16 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) @@ -16073,25 +16088,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/odsp-doclib-utils-previous': - specifier: npm:@fluidframework/odsp-doclib-utils@~2.4.0 - version: /@fluidframework/odsp-doclib-utils@2.4.0(debug@4.3.7) + specifier: npm:@fluidframework/odsp-doclib-utils@2.5.0 + version: /@fluidframework/odsp-doclib-utils@2.5.0(debug@4.3.7) '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -16161,25 +16176,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/telemetry-utils-previous': - specifier: npm:@fluidframework/telemetry-utils@~2.4.0 - version: /@fluidframework/telemetry-utils@2.4.0 + specifier: npm:@fluidframework/telemetry-utils@2.5.0 + version: /@fluidframework/telemetry-utils@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -16267,25 +16282,25 @@ importers: version: 0.16.4 '@biomejs/biome': specifier: ~1.9.3 - version: 1.9.3 + version: 1.9.4 '@fluid-internal/mocha-test-setup': specifier: workspace:~ version: link:../../test/mocha-test-setup '@fluid-tools/build-cli': - specifier: ^0.49.0 - version: 0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4) '@fluidframework/build-common': specifier: ^2.0.3 version: 2.0.3 '@fluidframework/build-tools': - specifier: ^0.49.0 - version: 0.49.0 + specifier: ^0.50.0 + version: 0.50.0(@types/node@18.19.54) '@fluidframework/eslint-config-fluid': specifier: ^5.4.0 version: 5.4.0(eslint@8.55.0)(typescript@5.4.5) '@fluidframework/tool-utils-previous': - specifier: npm:@fluidframework/tool-utils@~2.4.0 - version: /@fluidframework/tool-utils@2.4.0 + specifier: npm:@fluidframework/tool-utils@2.5.0 + version: /@fluidframework/tool-utils@2.5.0 '@microsoft/api-extractor': specifier: 7.47.8 version: 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) @@ -16366,6 +16381,13 @@ packages: resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} dev: true + /@alcalzone/ansi-tokenize@0.1.3: + resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} + engines: {node: '>=14.13.1'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -16549,6 +16571,7 @@ packages: dependencies: '@babel/highlight': 7.25.7 picocolors: 1.1.0 + dev: true /@babel/compat-data@7.25.7: resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==} @@ -16668,6 +16691,7 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.0 + dev: true /@babel/parser@7.25.7: resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} @@ -16897,24 +16921,24 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@biomejs/biome@1.9.3: - resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} + /@biomejs/biome@1.9.4: + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} hasBin: true requiresBuild: true optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.3 - '@biomejs/cli-darwin-x64': 1.9.3 - '@biomejs/cli-linux-arm64': 1.9.3 - '@biomejs/cli-linux-arm64-musl': 1.9.3 - '@biomejs/cli-linux-x64': 1.9.3 - '@biomejs/cli-linux-x64-musl': 1.9.3 - '@biomejs/cli-win32-arm64': 1.9.3 - '@biomejs/cli-win32-x64': 1.9.3 - dev: true - - /@biomejs/cli-darwin-arm64@1.9.3: - resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + dev: true + + /@biomejs/cli-darwin-arm64@1.9.4: + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] @@ -16922,8 +16946,8 @@ packages: dev: true optional: true - /@biomejs/cli-darwin-x64@1.9.3: - resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} + /@biomejs/cli-darwin-x64@1.9.4: + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] @@ -16931,8 +16955,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64-musl@1.9.3: - resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} + /@biomejs/cli-linux-arm64-musl@1.9.4: + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -16940,8 +16964,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64@1.9.3: - resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} + /@biomejs/cli-linux-arm64@1.9.4: + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -16949,8 +16973,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64-musl@1.9.3: - resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} + /@biomejs/cli-linux-x64-musl@1.9.4: + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -16958,8 +16982,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64@1.9.3: - resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} + /@biomejs/cli-linux-x64@1.9.4: + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -16967,8 +16991,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-arm64@1.9.3: - resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} + /@biomejs/cli-win32-arm64@1.9.4: + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] @@ -16976,8 +17000,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-x64@1.9.3: - resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} + /@biomejs/cli-win32-x64@1.9.4: + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -17318,6 +17342,7 @@ packages: comment-parser: 1.4.0 esquery: 1.6.0 jsdoc-type-pratt-parser: 4.0.0 + dev: true /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} @@ -17525,10 +17550,12 @@ packages: dependencies: eslint: 8.55.0 eslint-visitor-keys: 3.4.3 + dev: true /@eslint-community/regexpp@4.11.1: resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true /@eslint/eslintrc@2.1.4: resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} @@ -17545,10 +17572,12 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true /@eslint/js@8.55.0: resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /@faker-js/faker@8.4.1: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} @@ -19238,29 +19267,29 @@ packages: tslib: 2.7.0 dev: false - /@fluid-experimental/attributable-map@2.4.0: - resolution: {integrity: sha512-V/pp6WUEOX8D49S92fi2QKKzWtv0VDfPkjiF8lTJQKtHF2PP/M5xqPwE2sTKxQZUK8In7VfD8RVBPSGw1jvonw==} + /@fluid-experimental/attributable-map@2.5.0: + resolution: {integrity: sha512-ndKa5q5XFTuHg+cZCkdzMx3nJ5Dqj2GOg0gCSDr/4aI4GR5VD7CZWptsGJs4reoHSor+olvQXR1cwPhkStjKVw==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) path-browserify: 1.0.1 transitivePeerDependencies: - debug - supports-color dev: true - /@fluid-internal/client-utils@2.4.0: - resolution: {integrity: sha512-/WIRn90yUclI05awdqn9sNR44I1s4EcrIffezn4eyS2yHaZyMHAeMJxx/1xQyIRgYjUSQj8UeTQeyEKVDrIGHw==} + /@fluid-internal/client-utils@2.5.0: + resolution: {integrity: sha512-2jDol2fdXI856cCf7Qnb708EbstrnSncU+nuBFOs7sajoiipEY4eIpgaD6860RAj2TZJBSeRr0anhHotb994sQ==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 '@types/events_pkg': /@types/events@3.0.3 base64-js: 1.5.1 buffer: 6.0.3 @@ -19278,12 +19307,13 @@ packages: - eslint - supports-color - typescript + dev: true - /@fluid-internal/test-driver-definitions@2.4.0: - resolution: {integrity: sha512-bx/h8LQ6T0AUfm2WfyQp/W3vrlPZ8MXxBXL1Tq1PS2+UBrMb63nYglm0JstgpJsuHT6oqZSSS8DmpEWAAo0Y1w==} + /@fluid-internal/test-driver-definitions@2.5.0: + resolution: {integrity: sha512-llztyCuxpDCf0MHl+SwplPclgQFBpOih/JVMxfUntU41ghu9aqYBa6wfNM5qKtAJdQlKo7Oo4YG/uFjjZCyV8Q==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 dev: true /@fluid-tools/benchmark@0.50.0: @@ -19299,22 +19329,23 @@ packages: transitivePeerDependencies: - supports-color - /@fluid-tools/build-cli@0.49.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4): - resolution: {integrity: sha512-V9h8OCJDvSz8m4zmeCO6y8DJi972BSFp3YO6S/R1v7J/CpaG5A6v1Di0Kp5+JYf+sQ2ILoBaEvdjCp3ii+eYTw==} + /@fluid-tools/build-cli@0.50.0(@types/node@18.19.54)(typescript@5.4.5)(webpack-cli@5.1.4): + resolution: {integrity: sha512-vng+x2hQqm74+XERk3ziDaZRPREOipqwDGeBjmhTndll2bcs94XYIJ3ngze2GUcyymJnGXjgQxr9llnw/6WvQA==} engines: {node: '>=18.17.1'} hasBin: true dependencies: '@andrewbranch/untar.js': 1.0.3 - '@fluid-tools/version-tools': 0.49.0 - '@fluidframework/build-tools': 0.49.0 - '@fluidframework/bundle-size-tools': 0.49.0(webpack-cli@5.1.4) - '@microsoft/api-extractor': 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) - '@oclif/core': 4.0.25 - '@oclif/plugin-autocomplete': 3.2.5 - '@oclif/plugin-commands': 4.0.16 - '@oclif/plugin-help': 6.2.13 - '@oclif/plugin-not-found': 3.2.22 - '@octokit/core': 4.2.4 + '@fluid-tools/version-tools': 0.50.0(@types/node@18.19.54) + '@fluidframework/build-tools': 0.50.0(@types/node@18.19.54) + '@fluidframework/bundle-size-tools': 0.50.0(webpack-cli@5.1.4) + '@inquirer/prompts': 7.0.1(@types/node@18.19.54) + '@microsoft/api-extractor': 7.47.11(@types/node@18.19.54) + '@oclif/core': 4.0.31 + '@oclif/plugin-autocomplete': 3.2.8 + '@oclif/plugin-commands': 4.1.7 + '@oclif/plugin-help': 6.2.16 + '@oclif/plugin-not-found': 3.2.25(@types/node@18.19.54) + '@octokit/core': 5.2.0 '@octokit/rest': 21.0.2 '@rushstack/node-core-library': 3.66.1(@types/node@18.19.54) async: 3.2.6 @@ -19322,7 +19353,7 @@ packages: chalk: 5.3.0 change-case: 3.1.0 cosmiconfig: 8.3.6(typescript@5.4.5) - danger: 11.3.1 + danger: 12.3.3 date-fns: 2.30.0 debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 @@ -19332,19 +19363,17 @@ packages: globby: 11.1.0 gray-matter: 4.0.3 human-id: 4.1.1 - inquirer: 8.2.6 issue-parser: 7.0.1 json5: 2.2.3 - jssm: 5.98.2 + jssm: 5.104.1 jszip: 3.10.1 - latest-version: 5.1.0 + latest-version: 9.0.0 mdast: 3.0.0 mdast-util-heading-range: 4.0.0 mdast-util-to-string: 4.0.0 minimatch: 7.4.6 - node-fetch: 2.7.0 npm-check-updates: 16.14.20 - oclif: 4.15.0 + oclif: 4.15.20(@types/node@18.19.54) prettier: 3.2.5 prompts: 2.4.2 read-pkg-up: 7.0.1 @@ -19370,30 +19399,34 @@ packages: - '@swc/core' - '@types/node' - bluebird + - bufferutil - encoding - esbuild + - react-devtools-core - supports-color - typescript - uglify-js + - utf-8-validate - webpack-cli dev: true - /@fluid-tools/build-cli@0.49.0(typescript@5.4.5)(webpack-cli@5.1.4): - resolution: {integrity: sha512-V9h8OCJDvSz8m4zmeCO6y8DJi972BSFp3YO6S/R1v7J/CpaG5A6v1Di0Kp5+JYf+sQ2ILoBaEvdjCp3ii+eYTw==} + /@fluid-tools/build-cli@0.50.0(typescript@5.4.5)(webpack-cli@5.1.4): + resolution: {integrity: sha512-vng+x2hQqm74+XERk3ziDaZRPREOipqwDGeBjmhTndll2bcs94XYIJ3ngze2GUcyymJnGXjgQxr9llnw/6WvQA==} engines: {node: '>=18.17.1'} hasBin: true dependencies: '@andrewbranch/untar.js': 1.0.3 - '@fluid-tools/version-tools': 0.49.0 - '@fluidframework/build-tools': 0.49.0 - '@fluidframework/bundle-size-tools': 0.49.0(webpack-cli@5.1.4) - '@microsoft/api-extractor': 7.47.8(patch_hash=ldzfpsbo3oeejrejk775zxplmi)(@types/node@18.19.54) - '@oclif/core': 4.0.25 - '@oclif/plugin-autocomplete': 3.2.5 - '@oclif/plugin-commands': 4.0.16 - '@oclif/plugin-help': 6.2.13 - '@oclif/plugin-not-found': 3.2.22 - '@octokit/core': 4.2.4 + '@fluid-tools/version-tools': 0.50.0(@types/node@18.19.54) + '@fluidframework/build-tools': 0.50.0(@types/node@18.19.54) + '@fluidframework/bundle-size-tools': 0.50.0(webpack-cli@5.1.4) + '@inquirer/prompts': 7.0.1(@types/node@18.19.54) + '@microsoft/api-extractor': 7.47.11(@types/node@18.19.54) + '@oclif/core': 4.0.31 + '@oclif/plugin-autocomplete': 3.2.8 + '@oclif/plugin-commands': 4.1.7 + '@oclif/plugin-help': 6.2.16 + '@oclif/plugin-not-found': 3.2.25(@types/node@18.19.54) + '@octokit/core': 5.2.0 '@octokit/rest': 21.0.2 '@rushstack/node-core-library': 3.66.1(@types/node@18.19.54) async: 3.2.6 @@ -19401,7 +19434,7 @@ packages: chalk: 5.3.0 change-case: 3.1.0 cosmiconfig: 8.3.6(typescript@5.4.5) - danger: 11.3.1 + danger: 12.3.3 date-fns: 2.30.0 debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 @@ -19411,19 +19444,17 @@ packages: globby: 11.1.0 gray-matter: 4.0.3 human-id: 4.1.1 - inquirer: 8.2.6 issue-parser: 7.0.1 json5: 2.2.3 - jssm: 5.98.2 + jssm: 5.104.1 jszip: 3.10.1 - latest-version: 5.1.0 + latest-version: 9.0.0 mdast: 3.0.0 mdast-util-heading-range: 4.0.0 mdast-util-to-string: 4.0.0 minimatch: 7.4.6 - node-fetch: 2.7.0 npm-check-updates: 16.14.20 - oclif: 4.15.0 + oclif: 4.15.20(@types/node@18.19.54) prettier: 3.2.5 prompts: 2.4.2 read-pkg-up: 7.0.1 @@ -19449,34 +19480,37 @@ packages: - '@swc/core' - '@types/node' - bluebird + - bufferutil - encoding - esbuild + - react-devtools-core - supports-color - typescript - uglify-js + - utf-8-validate - webpack-cli dev: true - /@fluid-tools/fetch-tool@2.4.0: - resolution: {integrity: sha512-mu7gcA8232MXn5LdrvlLE5ivhsw4SvoUs5T2bRb/O3ZMGgVejuTca7Av6AjzVD/DISifRcRHb1xirfeujqxjhg==} + /@fluid-tools/fetch-tool@2.5.0: + resolution: {integrity: sha512-iG27g4vokkGuppAko1TPCn9qb0PPPUs6guOl0kP+E5fdMFLvWhlkagP7y2RdA9MxjP/O69PMa+EC6juYAmVlUA==} hasBin: true dependencies: '@azure/identity': 4.4.1 '@azure/identity-cache-persistence': 1.1.1 - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/odsp-doclib-utils': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver-definitions': 2.4.0 - '@fluidframework/odsp-urlresolver': 2.4.0 - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) - '@fluidframework/routerlicious-urlresolver': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/tool-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/odsp-doclib-utils': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver-definitions': 2.5.0 + '@fluidframework/odsp-urlresolver': 2.5.0 + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) + '@fluidframework/routerlicious-urlresolver': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/tool-utils': 2.5.0 transitivePeerDependencies: - bufferutil - debug @@ -19485,36 +19519,40 @@ packages: - utf-8-validate dev: true - /@fluid-tools/version-tools@0.49.0: - resolution: {integrity: sha512-3BI1rmCBx7ZZGhuchtwCNgL6XSRMRtDtflvi2ks7dKE04T8WoKxUwi3+YNVlXf5XlcSLtwprbRjnraIA2rjgAQ==} + /@fluid-tools/version-tools@0.50.0(@types/node@18.19.54): + resolution: {integrity: sha512-i/veL9kQr1QaDZFS+t9umb0EF7x7uwhkAZ1Zz1zN5zMs6HNyL+hnj4q8gj6Yrkwr6zxnSQ+N+hERhvUIAQjbvQ==} engines: {node: '>=18.17.1'} hasBin: true dependencies: - '@oclif/core': 4.0.25 - '@oclif/plugin-autocomplete': 3.2.5 - '@oclif/plugin-commands': 4.0.16 - '@oclif/plugin-help': 6.2.13 - '@oclif/plugin-not-found': 3.2.22 + '@oclif/core': 4.0.31 + '@oclif/plugin-autocomplete': 3.2.8 + '@oclif/plugin-commands': 4.1.7 + '@oclif/plugin-help': 6.2.16 + '@oclif/plugin-not-found': 3.2.25(@types/node@18.19.54) chalk: 2.4.2 semver: 7.6.3 table: 6.8.2 transitivePeerDependencies: + - '@types/node' + - bufferutil + - react-devtools-core - supports-color + - utf-8-validate - /@fluidframework/agent-scheduler@2.4.0: - resolution: {integrity: sha512-YiTGTr1kuSN8nLjHr/6/nUxxXm9GRowpRGwGhAgTFoqF9RsBB3XCIRJTqvTAQGqT/UQ/xfGnWEjLv4iQ/1aCGg==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/register-collection': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/agent-scheduler@2.5.0: + resolution: {integrity: sha512-yqEmNMjV8kRH+6t9Hrns8hUyZZQUE47oiLhKa4ANsNBVk0dGm2iWwQ4hjyImJUObipZ9RMTAF+DEQfAkhtZZKQ==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/register-collection': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 uuid: 9.0.1 transitivePeerDependencies: - debug @@ -19545,23 +19583,23 @@ packages: - supports-color dev: false - /@fluidframework/aqueduct@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-pUptq5htEXURujfK+NRzNNOXL1wcutAiY3v4a2mqF3f9zaVmGTmMqcPO8gnFcc9S9Sn2IPoNH4yICmcSdDEQtw==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/request-handler': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/synthesize': 2.4.0 + /@fluidframework/aqueduct@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-xADjE+6W+FP1H36n88DmhFJLrGKEkepyrjFX4RkRKDJ/y9cFX1VeQJ+F13l/bbTWKArh/4NxcViCpbhBKSzFTw==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/request-handler': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/synthesize': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -19598,20 +19636,20 @@ packages: - utf-8-validate dev: false - /@fluidframework/azure-client@2.4.0: - resolution: {integrity: sha512-Xs1J7t7WmG0Mqx9JcHG6bQd1fO9GhA6e9XXTTTmY5g5qbLewpJqUTWTXw65tcLIr6TRtQ4GRTZi7K8SalFKK/w==} + /@fluidframework/azure-client@2.5.0: + resolution: {integrity: sha512-cla792bIVMi1uFOyPTRF8OawWzDAlpuj3OVZduqsoGvm4D1/DrIjnyPy64rd1oMHxuxuSWsFQZKrpt94fwDhXQ==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/fluid-static': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/fluid-static': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - bufferutil - debug @@ -19620,10 +19658,10 @@ packages: - utf-8-validate dev: true - /@fluidframework/azure-service-utils@2.4.0: - resolution: {integrity: sha512-bYejUU9YBXt6+Y5eD8Rkp+Mk6K4LBuZK29NTxqwRsnMi4SKofHRKcuol6BJZSnw9jdD31qMe75pmI6enNGI3Qg==} + /@fluidframework/azure-service-utils@2.5.0: + resolution: {integrity: sha512-lrURmBhdqHnCO1dueBgPxXs/32Y/j0DZrvUGYUn/sEdgwzh4vuobklq4nh4AxOyMaT21b81iPcwkWaIY9AUT/w==} dependencies: - '@fluidframework/driver-definitions': 2.4.0 + '@fluidframework/driver-definitions': 2.5.0 jsrsasign: 11.1.0 uuid: 9.0.1 dev: true @@ -19631,13 +19669,14 @@ packages: /@fluidframework/build-common@2.0.3: resolution: {integrity: sha512-1LU/2uyCeMxf63z5rhFOFEBvFyBogZ7ZXwzXLxyBhSgq/fGiq8PLjBW7uX++r0LcVCdaWyopf7w060eJpANYdg==} hasBin: true + dev: true - /@fluidframework/build-tools@0.49.0: - resolution: {integrity: sha512-hz5xf320HfbnpziCOw1I+BqbYktaJbtX5nuSsjSSvJJTzm/RPM+kvRgp02isG8kF1WKhMsMwueHwcNek+sHOxQ==} + /@fluidframework/build-tools@0.50.0(@types/node@18.19.54): + resolution: {integrity: sha512-j41rBT9xv0LkJ3jbBf8l+G5kYoir6LsaHmPy5R0xumdYU8E4n2EuyXF2wdsZp/kb3yZE8jTMHUDUw1ffEA0JxQ==} engines: {node: '>=18.17.1'} hasBin: true dependencies: - '@fluid-tools/version-tools': 0.49.0 + '@fluid-tools/version-tools': 0.50.0(@types/node@18.19.54) '@manypkg/get-packages': 2.2.2 async: 3.2.6 chalk: 2.4.2 @@ -19662,12 +19701,17 @@ packages: ts-morph: 22.0.0 type-fest: 2.19.0 typescript: 5.4.5 - yaml: 2.5.1 + yaml: 2.6.0 transitivePeerDependencies: + - '@types/node' + - bufferutil + - react-devtools-core - supports-color + - utf-8-validate + dev: true - /@fluidframework/bundle-size-tools@0.49.0(webpack-cli@5.1.4): - resolution: {integrity: sha512-SUrWc931wwOkwIERX282SmHUVjXz0mRhlYIoY68DkYVZ3XuUrKaVvHbJB6a3ek+TIX33zg90HKFNkp9K56m0SQ==} + /@fluidframework/bundle-size-tools@0.50.0(webpack-cli@5.1.4): + resolution: {integrity: sha512-nZs5ORhLguRUt1eVyZd+VRDyZqWlkYiLrlImbVfF7zASa4WP9F+p//ymnLvpDB221BTNXrJUw2Tpo0MnH4NVVQ==} dependencies: azure-devops-node-api: 11.2.0 jszip: 3.10.1 @@ -19682,16 +19726,16 @@ packages: - webpack-cli dev: true - /@fluidframework/cell@2.4.0: - resolution: {integrity: sha512-4WFGL1e+Q+dIMngkhEi+YoFDKXIPFiBMT2onl5ATudYwM0loFYcW6onrcOOEFqIS2ZKEd2QHYxJitPg2rhY3Qw==} + /@fluidframework/cell@2.5.0: + resolution: {integrity: sha512-1SgETF15DNCEklIaKfTj8lfMk1MtOZ3K8Owgk8cPM4kcb8eGD5mDa4LmIM2oF/eWb6fTiYLXY7rSJVGMa+QVLg==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color @@ -19749,11 +19793,11 @@ packages: events: 3.3.0 dev: false - /@fluidframework/container-definitions@2.4.0: - resolution: {integrity: sha512-Q8ayKIxUFANdUk7FjpIc3GGDf7B4RjlxPAeAmfe5X2wyPZWOhSd0Lf8UeU/CIOcPjnd0kjWNOGqKy3eaculgJQ==} + /@fluidframework/container-definitions@2.5.0: + resolution: {integrity: sha512-3qDdsTScOIoRi+cj8KTLtAfy6a8aBkZ0Tg5eVBjad22aLP1EFRE+pyztfxul8cZyuVzKJo2viheDKbRXmN/D+A==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 dev: true /@fluidframework/container-loader@1.4.0: @@ -19780,16 +19824,16 @@ packages: - supports-color dev: false - /@fluidframework/container-loader@2.4.0: - resolution: {integrity: sha512-BEVDrMqNCpbV492XJKRkTE8l53eM6lfgOJ+LBe0w+JYInKNf4H+c+BGYFI5JBT7lZHhqUKYe7JQgEYFqMimmdQ==} + /@fluidframework/container-loader@2.5.0: + resolution: {integrity: sha512-17mSFCSaoBBQWP/Crh4scUEKbJrH5Vr1WPJayMJev3FMNquswNrlvjEZnY+8zTvJHcJPVNbUtTMHh/KS+zlMsQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 '@types/events_pkg': /@types/events@3.0.3 '@ungap/structured-clone': 1.2.0 debug: 4.3.7(supports-color@8.1.1) @@ -19811,13 +19855,13 @@ packages: '@fluidframework/runtime-definitions': 1.4.0 dev: false - /@fluidframework/container-runtime-definitions@2.4.0: - resolution: {integrity: sha512-lyiirgnTFEllddwNZ65zpYkAiCR+4RG16OpUCGCafj+3zM1VIow01Ud4LgTb3RMyJnoOVR0D+c274Kws4VVwWQ==} + /@fluidframework/container-runtime-definitions@2.5.0: + resolution: {integrity: sha512-aoIeyzTLZuTdqTRwuQVo6BAwXWwvWNQjsMgCRi7FyUx+IdIwd0xAwRjLmGmh5fRAW6fA2pQAET7thf1C/e2ECQ==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 transitivePeerDependencies: - supports-color dev: true @@ -19848,21 +19892,21 @@ packages: - supports-color dev: false - /@fluidframework/container-runtime@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-JtPXueIkt05LKqHII/mo+qZaEecO+kPDk46cj9LS5K4aRLtbzz4RJW1jCdxLJlW9y9dHUGTIFCXXkAbzzFHIwA==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/container-runtime@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-YcNdFD/SNAwPStVP85X18Y+HqXS/NSe1jfR75LD2Pi3dT5GHvj7/pdHvJw9O3vv9u1KaG1aPrTml8nkBmEc4XQ==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 '@tylerbu/sorted-btree-es6': 1.8.0 double-ended-queue: 2.1.0-0 lz4js: 0.2.0 @@ -19888,24 +19932,24 @@ packages: resolution: {integrity: sha512-PDIglmsa9BgFh7Xhfs32KA3Q34/arTVHF4m3M0IuAByP4z8Oi2lVuNENZnBEk+IJMcrUhUDk5Q9LH8KGfoAw+Q==} dev: false - /@fluidframework/core-interfaces@2.4.0: - resolution: {integrity: sha512-VoEa4QGfR9Dtwnz2hIvcXft/K37r0k2Ian2hZV03X/mlBeFh7W7kroQUMq3ymdY+qC5WQUEynr143DT30NN/lg==} + /@fluidframework/core-interfaces@2.5.0: + resolution: {integrity: sha512-0/SLUSDu/2kx2wAogAfcPtnL+JFO2EmWL9pBTsw8agdVmsBgUqpjTv/42dlu/3atHeN1ZuVHP8PGtnE06KeZbA==} dev: true - /@fluidframework/core-utils@2.4.0: - resolution: {integrity: sha512-vUFehDNFHdAc+h+wV4KI69DUXOAIpYaqb8teKxtQltnHMdx5RxgN0w28ECoeHTxSdr4sU7vTWov7AhqH51HxfA==} + /@fluidframework/core-utils@2.5.0: + resolution: {integrity: sha512-puBhR1NaDCu48KEi7jEUckRH9fyMy6pGsr5rtzeKiY3dyUT1vKtkBMZESHJ/lhcN93vv9OjRVrG5NOFR8ULjqg==} dev: true - /@fluidframework/counter@2.4.0: - resolution: {integrity: sha512-PfK6DnXGt2y5pTFSgGDi3dHhlwG/9hJWcuyU9DamAm7n4lTOI+p2btXwToDytreXiyMfgKNxOeVvTk2kEVhycA==} + /@fluidframework/counter@2.5.0: + resolution: {integrity: sha512-KvHPQLQMnDUQghQIIvWxba9NXDWo9hlW2JXAGamuc21A1bEIfkNABEWm/pHxJ/XihYF6JpEGHiXKfAZlDpkzcQ==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color @@ -19922,14 +19966,14 @@ packages: '@fluidframework/runtime-definitions': 1.4.0 dev: false - /@fluidframework/datastore-definitions@2.4.0: - resolution: {integrity: sha512-P2E/S5fuOo6CCy3beJAFuzyvqdvR+oyHuqnxTMUy7IqhRr4Og+YKuRnmIf4Ieb+hKJYdblJIOXlgJ+SYC+Y44Q==} + /@fluidframework/datastore-definitions@2.5.0: + resolution: {integrity: sha512-y7vpL3uLj5m14jxM5zgbc/i7wA2c7Cw+kC4eG5cLPYMq2jjAdGUPEbviwNRTUAJHlVjR2N29FzT8mZ4Fpixyrw==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 transitivePeerDependencies: - supports-color dev: true @@ -19958,68 +20002,68 @@ packages: - supports-color dev: false - /@fluidframework/datastore@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-gapyHUKqI951iROJY8B46H1vvB5hg5+794lvmsy+esYv87hA0VZP0rO1Zwp9mbjWPOQ0dYZRKYE3uRYwhuwoqA==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/datastore@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-0YQAOyPUU4Cj43EaHbwM93lAoHW6TMH8SMQlBMRptvhDby/HdRuPHjaP+p7mT0EgRyfxwZk6EQqptufLtjwHrg==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 uuid: 9.0.1 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/debugger@2.4.0: - resolution: {integrity: sha512-fMqcIMp0sU95vZd4s0GHxkl5XGtAHyf0iMLemMjEHEq2GwbwA1x0YT6xYvupEkRUBZ6BaZfXYBF6B7/9ReStRg==} + /@fluidframework/debugger@2.5.0: + resolution: {integrity: sha512-zdxoi1B9LjdFXg/MXFQkvEjHX70M6Mqar7F0IhUz0UiPwmLbb9hIowvXlB1+jWfQ33/DEo+x0+J+/0LfVcyrWQ==} dependencies: - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/replay-driver': 2.4.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/replay-driver': 2.5.0 jsonschema: 1.4.1 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/devtools-core@2.4.0: - resolution: {integrity: sha512-p40lpQABOBQdcaMtpLLai7/dm2706bLaWoIMVLmRc6790l64QaD+GBnsmwATLoa7489Z+tn+pSEsmEW2AZwtZw==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/cell': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/counter': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/matrix': 2.4.0 - '@fluidframework/sequence': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 - '@fluidframework/tree': 2.4.0 + /@fluidframework/devtools-core@2.5.0: + resolution: {integrity: sha512-JFJM1/nykPSmtXcRb6VHFnaCUedwtKbLnOUhFfJ4PGf81/Qf7PfcpE2AkAwRxREemcMkSLviop4OIwnBIcyvPA==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/cell': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/counter': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/matrix': 2.5.0 + '@fluidframework/sequence': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 + '@fluidframework/tree': 2.5.0 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/devtools@2.4.0: - resolution: {integrity: sha512-oj3TXUGCdHNyZYySCYoJVg7+UglcqF3bveuMALPWjf3jyEvrCedeHzN/L0HjwWZkEGHW/yQqHLIGu+QIvfkoqg==} + /@fluidframework/devtools@2.5.0: + resolution: {integrity: sha512-DHk0U3SO+AXFYE8z3J+WmpEb5Ub/u1aXb57c4+rs95UHrcdeeyOKewZge8SCXLKQymF2FvrwQ6o/snOworxQhA==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/devtools-core': 2.4.0 - '@fluidframework/fluid-static': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/devtools-core': 2.5.0 + '@fluidframework/fluid-static': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -20039,15 +20083,15 @@ packages: - supports-color dev: false - /@fluidframework/driver-base@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-jr8G7z7SvZGDViONsZ+lh8HsETdxefd8dgxVyVDlfLRwOiQFK6qB1exgqNo6kTYmhovs7GworzFV780kcyMmCA==} + /@fluidframework/driver-base@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-CM4a9suGs7qnCeOiYYxDgZH/D5J5NuVg0c0Ej2PDvvq9ueEY/IvbtJF4L3aftOURH3/yeI3pXHXLMCvV5yMw6A==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -20061,10 +20105,10 @@ packages: '@fluidframework/protocol-definitions': 0.1028.2000 dev: false - /@fluidframework/driver-definitions@2.4.0: - resolution: {integrity: sha512-0IYc/iJtUMU/4+vNJMjBTN6SKgrvsghRsPBgZGnr5vPP1TaPDN9pM8WkrzoLXv+gZahXjBf6HiDkJd/ikiIR0w==} + /@fluidframework/driver-definitions@2.5.0: + resolution: {integrity: sha512-sIDZvp5+1hr2/DI0yQWWZEgf94KyFVYoVrqiWTN+sJ8s0LAbOiTcUtB635M/VqXWMw1/Brj+eq2SgBv3UZ2GRQ==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 dev: true /@fluidframework/driver-utils@1.4.0: @@ -20085,14 +20129,14 @@ packages: - supports-color dev: false - /@fluidframework/driver-utils@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-aXan9NYRQq51shRcVMAhot32VNMlH2jFLAwJucRyAsyrweny3kFmMrk6fzb6d21K2ey0CP12fbxAInIFr3HQ5w==} + /@fluidframework/driver-utils@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-yMLW1X0JM0JCKYpKL93jln9kNYaqSqxSidKd33czu1Z6oxmWyyr+ZaDh77V/vm10C7GncY1IU/K53nvXN9oKAw==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 axios: 1.7.7(debug@4.3.7) lz4js: 0.2.0 uuid: 9.0.1 @@ -20101,13 +20145,13 @@ packages: - supports-color dev: true - /@fluidframework/driver-web-cache@2.4.0: - resolution: {integrity: sha512-3JCJodBU4pqTBmIvYISdvVnFbeEBv7eW/srtOTidcBr3GATW62oAoXwLad3df/vFrEndQcXkx0s8Fk8/tuet+g==} + /@fluidframework/driver-web-cache@2.5.0: + resolution: {integrity: sha512-WUrSZka2YzNpVOv8mHvtkpySVOTAUfXMIz6o8oAVtb3/bO/1mZehVh+8+509EudAtbtL7ir6lNUlFOakoC0h0w==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/odsp-driver-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/odsp-driver-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 idb: 6.1.5 transitivePeerDependencies: - supports-color @@ -20141,33 +20185,34 @@ packages: - eslint-plugin-import-x - supports-color - typescript + dev: true - /@fluidframework/file-driver@2.4.0: - resolution: {integrity: sha512-vGEarSzIDIo/8LtAx/fjiAgmBG/3TOyI2kN4Q/RIa6qg02EPCYH71ss2nEpnPlwn7edOTXlKGZvOsuwDrRQNsw==} + /@fluidframework/file-driver@2.5.0: + resolution: {integrity: sha512-9sAn1ojCclluSTZVbfFGXG4Zf/HJS+7VCjW844svl8UjJdNlNMPoxcfGiCquY0Ov5I7v3X+CBQB5CZN5Fus+Jg==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/replay-driver': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/replay-driver': 2.5.0 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/fluid-runner@2.4.0: - resolution: {integrity: sha512-2ChK0IcolwgerFJTXMpyPLWAKgrix8fDyhtbCCfwJbIWNQaAfmJrbZskSldZ4JufKmvYuwFEKDJ21TUVHfTPMw==} + /@fluidframework/fluid-runner@2.5.0: + resolution: {integrity: sha512-V7ncFmInvOYXQkWmd549cUrwYTWUgEz7XZqrk1RbF2LLxk6LV1oAdEEhO4ShOOw8TAxQxZszyYT2TwL8vNmG2A==} hasBin: true dependencies: - '@fluidframework/aqueduct': 2.4.0(debug@4.3.7) - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/odsp-driver': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/aqueduct': 2.5.0(debug@4.3.7) + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/odsp-driver': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 json2csv: 5.0.7 yargs: 17.7.2 transitivePeerDependencies: @@ -20198,23 +20243,23 @@ packages: - supports-color dev: false - /@fluidframework/fluid-static@2.4.0: - resolution: {integrity: sha512-pLoxSSEytRMEDgTRMkieURxNFw6oXz0xvYdvtxHVtLvApQ4F6ZaSf43+lTWIdZFuHt9ke5poN8ODcnzfNm31sg==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/aqueduct': 2.4.0(debug@4.3.7) - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/request-handler': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/fluid-static@2.5.0: + resolution: {integrity: sha512-KkZH3RhhwgCVc3NcZtKr4ayhooJ+Crfh7bkiK2O/PHzXDUO4UkPvjQCl6ASqZmZUIlSFhL0uEYq/zFvC0gzcEA==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/aqueduct': 2.5.0(debug@4.3.7) + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/request-handler': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -20235,35 +20280,35 @@ packages: /@fluidframework/gitresources@5.0.0: resolution: {integrity: sha512-nfam1KT+EUqFCENHcxrU9VwUfbhUC83UcLuOsdLpn9I7VuClL+w8KMWosoYauZraO9gDhTo2kCErjgQji5GzGA==} - /@fluidframework/id-compressor@2.4.0: - resolution: {integrity: sha512-xOfD3UMLbNHVvPRKco4NJJnXrvMg+yGsgGQZsJX4KwVbtjf142eFD+TFVh6OdHcHlWbYDQTtg5zkSX+CTXNAOA==} + /@fluidframework/id-compressor@2.5.0: + resolution: {integrity: sha512-QNLNHZLj3wszeqB5/5tBYcGhsrXtyTkT+5w74st29wip6b2CMMJmdfd5t2UXWTKFWQgp4a6gyXrJ1wCLUlBZng==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 '@tylerbu/sorted-btree-es6': 1.8.0 uuid: 9.0.1 transitivePeerDependencies: - supports-color dev: true - /@fluidframework/local-driver@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-AZV/qBsdl3cSua93vM1BS+n/oqxw1sfAELCxozUtPkAEJKbOrsBkJtJ5se6lDwzaKQ1tYEFI7u9HHeaxbBSrQg==} + /@fluidframework/local-driver@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-CulGRDjR9TDl758pBEuLiFh1o1moAk1lkvTPiu7R59+ci26t1x4rb7cnPGpJpdT/Qp8zB5rDwZjy721Arzx+2g==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-base': 2.4.0(debug@4.3.7) - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-base': 2.5.0(debug@4.3.7) + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) '@fluidframework/protocol-base': 5.0.0 - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) '@fluidframework/server-local-server': 5.0.0 '@fluidframework/server-services-client': 5.0.0 '@fluidframework/server-services-core': 5.0.0 '@fluidframework/server-test-utils': 5.0.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/telemetry-utils': 2.5.0 jsrsasign: 11.1.0 uuid: 9.0.1 transitivePeerDependencies: @@ -20293,40 +20338,40 @@ packages: - supports-color dev: false - /@fluidframework/map@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-VOIbEdoZwu9YKe4RiIWyRG8HjqtTilP+Zi7aeIbrK5GP2qX+DN9buOMI3MkgVbVy/96acBcnB878OsECjboKdg==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/merge-tree': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/map@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-mj3t4bBJhQpwNKu5DEfeLk7IBKniX85GLb/0AEC9EJRERxcWhcWPJ/LZpup4eYEzEw7JsMuiAC2RkNAVlt5ySg==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/merge-tree': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 path-browserify: 1.0.1 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/matrix@2.4.0: - resolution: {integrity: sha512-qgId7H/aPgyH3Zzs2uUbSuMamIiHdHwO3KDPTs+cehFFYiZJ/xbuy71G84JGEBe/EF383njhwHMvW9FEQIMIDg==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/merge-tree': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/matrix@2.5.0: + resolution: {integrity: sha512-tkTPlixiQsduE26sq7hgJRmoaa6fDwDgpQU6mfkdkq1EES2at69e48RRIl/4H5XGzzSv00amLU0sO9/7K9sdsQ==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/merge-tree': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 '@tiny-calc/nano': 0.0.0-alpha.5 double-ended-queue: 2.1.0-0 tslib: 1.14.1 @@ -20335,34 +20380,34 @@ packages: - supports-color dev: true - /@fluidframework/merge-tree@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-dVEwFPxUbitYVfk14Htt+A7GjiLsg3+5kEpgh6Ux8/Kv3EBIHviMtDlNHMxtDr7R9H/IxAPpJ0h/4E/5YwH6TA==} + /@fluidframework/merge-tree@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-RfmBlua1rNImr36mvE/gNVMSbj8a8lf9EipVCEbDeF03WIbO5KYEvN0sk2SkJissAFG3nYOj1h6FbFi7iRHfzQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/odsp-doclib-utils@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-UYz3IEIZQdOs/EucCB2Syw7ppDLGPMCcmioQbVhe+3Hd3uhPDXsbKi3r4+ukImRmpTA64vSmMV/7mhw8jWW1+Q==} + /@fluidframework/odsp-doclib-utils@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-99m+Bi0VbIzhATaGI1Y8Cabon5Doskj6025q22N05KQaXyd+Jb8WtaRbcN7r6+lQxsbwiT5WeyQ4yqkAdmyqbw==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 isomorphic-fetch: 3.0.0 transitivePeerDependencies: - debug @@ -20370,24 +20415,24 @@ packages: - supports-color dev: true - /@fluidframework/odsp-driver-definitions@2.4.0: - resolution: {integrity: sha512-/IcKFf9yuLwPc2Ej1DrhJZoy1IXdiDbGLVNg9SmXAmaNuiRnur4Yk4/GLcnU0Agngftl1rz5IDgHoSaiNHh7MA==} + /@fluidframework/odsp-driver-definitions@2.5.0: + resolution: {integrity: sha512-D/SRW+5hfsqCJ6KkYN3Ce3mqQ8T+cggCih78BcF05mlojTxIQI22+P2iHHpr9VjKUBghNxsAZj+1YdcGmg5zDQ==} dependencies: - '@fluidframework/driver-definitions': 2.4.0 + '@fluidframework/driver-definitions': 2.5.0 dev: true - /@fluidframework/odsp-driver@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-rfmfDCgOaLmZ9ZgLN2Ge7I4kT40lt6DY/ibluaX80ZVJeniVmygHhkl3tXUnjCDhph9Jhqjg8ralpjcjgu+JIQ==} + /@fluidframework/odsp-driver@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-+fh2xXV2BERCcqkhrbBRqAftA3PJGcoI/S1+hhsKUlju26XQuufWtju3qz693Bf4VyBbuUPOdq6Qjpfk6W3KnA==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-base': 2.4.0(debug@4.3.7) - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-doclib-utils': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-base': 2.5.0(debug@4.3.7) + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-doclib-utils': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 node-fetch: 2.7.0 socket.io-client: 4.7.5 uuid: 9.0.1 @@ -20399,15 +20444,15 @@ packages: - utf-8-validate dev: true - /@fluidframework/odsp-urlresolver@2.4.0: - resolution: {integrity: sha512-/zDPZmbedfd1+6SmFHsZJ0y2GtkNuc3uKOdYOeeYXSNLCeDdzKxVCkSw3zJAdRGBa9YYUUL1VQExDhqQKiqFkA==} + /@fluidframework/odsp-urlresolver@2.5.0: + resolution: {integrity: sha512-1pdMFQYcOhczteEejqEl8WdVZh2hNI3OdH6cor/jiPdo/OSrHGgNWFMGrQrYAvmdt0HGbEXvqZmyMbmx7M7e6g==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/odsp-driver': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver-definitions': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/odsp-driver': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver-definitions': 2.5.0 transitivePeerDependencies: - bufferutil - debug @@ -20416,18 +20461,18 @@ packages: - utf-8-validate dev: true - /@fluidframework/ordered-collection@2.4.0: - resolution: {integrity: sha512-3hxBBMUirM70CJi7N+ikU0mKgOMAomWmUui83Y9SuRr6s3VNrnoU1Nyhyxt+dyiiqh+Klvdrit1k9wDNGPXYXQ==} + /@fluidframework/ordered-collection@2.5.0: + resolution: {integrity: sha512-sqy/SF9wbTt4XguEqDbCAqdJ6x7sqJ32TPfKyk90zaszZoAHxoWs41zMgXnOVXNzF7aGSgBlVomjoxLVPHA/oQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 uuid: 9.0.1 transitivePeerDependencies: - debug @@ -20466,31 +20511,31 @@ packages: /@fluidframework/protocol-definitions@3.2.0: resolution: {integrity: sha512-xgcyMN4uF6dAp2/XYFSHvGFITIV7JbVt3itA+T0c71/lZjq/HU/a/ClPIxfl9AEN0RbtuR/1n5LP4FXSV9j0hA==} - /@fluidframework/register-collection@2.4.0: - resolution: {integrity: sha512-PXBTpeZqrYb4gv4gVu/s9gsCov198WS8D7+r1fLY1seIZMu6a1dUe8FcWRfJsQCUxtj6AWTcWd1L1qXPsuwypA==} + /@fluidframework/register-collection@2.5.0: + resolution: {integrity: sha512-wojjNL7lDpqWNhRhm1Ykxb2C9ckxfglM/vP/cC38PpDoxRJqZVaiLd9QxaF/m8suRsXM4AUyCrMQkyZWODwRcQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/replay-driver@2.4.0: - resolution: {integrity: sha512-oEVVQg1fYrtJztk1DB1++aT36YNLDhTcGS4Lj7Do70UgIrqCTWMhtkHNtaVx0rhRz7GNYpakuJJywCPPjAI4tw==} + /@fluidframework/replay-driver@2.5.0: + resolution: {integrity: sha512-gb4uUGcNYlR8z9DXKYYgA79PcZ8o3aeZ223TWR5USTGFhq/GvnqDssEq2brFfZB+6oFQIfcOWyiEvNYPRG4qew==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -20508,14 +20553,14 @@ packages: - supports-color dev: false - /@fluidframework/request-handler@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-VhnzmOMhsrSoWhBu55DG6x1FV273WS8NlHlaGUUMDBggzFU86u5AJHvp2v7B+r3LZRCfN/lAQvHuc8SlfFAV4g==} + /@fluidframework/request-handler@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-h4wKN3PZcqv8ype2GbGytUb/20OOmhNA7OUzw+dlPe4qopTeFq5lLuFGZmsqBY1iaZxHf569iSd4SiahNxPcbg==} dependencies: - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color @@ -20548,17 +20593,17 @@ packages: - utf-8-validate dev: false - /@fluidframework/routerlicious-driver@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-WcY2rNtNZN3p6GH1BlB1ZpHh8V2vTsGKagITLtFl3sHmLw+rTqLEHHI+KNzf5ZoKB3E98+yBsp0jAmnHbnxkoQ==} + /@fluidframework/routerlicious-driver@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-I3yOXrnNHiQqLzZuvVeS+cnKyh2PU8ivgtfc89Z2QGZgDoHQP6eibEhGsAgXihgs3VWLBWMwauY2WduV9I6IHA==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-base': 2.4.0(debug@4.3.7) - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-base': 2.5.0(debug@4.3.7) + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) '@fluidframework/server-services-client': 5.0.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/telemetry-utils': 2.5.0 cross-fetch: 3.1.8 json-stringify-safe: 5.0.1 socket.io-client: 4.7.5 @@ -20571,12 +20616,12 @@ packages: - utf-8-validate dev: true - /@fluidframework/routerlicious-urlresolver@2.4.0: - resolution: {integrity: sha512-SDfcnHq2s3q6i7UqC/V15aLSL20l58N20tejgScUNQO8w7pTrYLVjnNTIHu4f7jfhBLdZb0bEvHHEE8NYvobfQ==} + /@fluidframework/routerlicious-urlresolver@2.5.0: + resolution: {integrity: sha512-u4zg3tHn8EPIK7rdsykztkGw5ZWpEe3oE7v19AmjNX/8enPKDVRzbUncoIBv+D3ykwdntxcgReJWJL+SyVfOMA==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 nconf: 0.12.1 dev: true @@ -20591,14 +20636,14 @@ packages: '@fluidframework/protocol-definitions': 0.1028.2000 dev: false - /@fluidframework/runtime-definitions@2.4.0: - resolution: {integrity: sha512-5Vxt6qcVO9F+0WAUrSPX2LvnxF2QNx6TFELmUDTHkUtUEO8Sh1x/h34oINeQKVsD58J6K7sjSGodz8vzTX+aOw==} + /@fluidframework/runtime-definitions@2.5.0: + resolution: {integrity: sha512-aPItFzJ6JLKuOvgTbnG7+VovLMpObZzfbIzTeYzaBIq2msuHHD+z/QWEoAzxNIlxCj5YffjDQlxNrmTd0YqyqA==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - supports-color dev: true @@ -20621,37 +20666,37 @@ packages: - supports-color dev: false - /@fluidframework/runtime-utils@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-xJ2dKxmRgrk7MuMYcNjGOKM5viGyyME6V2YrOcC4h41nzN8Nl29mMrsZDy9ftV2e5oyKsYRC8I9aw9iS3/97ug==} + /@fluidframework/runtime-utils@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-Wrt9jf3QHVlaAit0gY1ZGB/Dr2KrgbCuany43221tQXUECQXR2tCKsfYFzRXgcdXxCHHHJ1D9D7kctnSAAiE3A==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/telemetry-utils': 2.5.0 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/sequence@2.4.0: - resolution: {integrity: sha512-V9WhgPQgUe/vo3H2riXvT3caXIi47LRBJ3xmRfsXwcvlCpYjYV+Alxr4qKxaBINreMeAlLGhJcHi/5bS5E4h1A==} + /@fluidframework/sequence@2.5.0: + resolution: {integrity: sha512-nMeI2ZngwPJvns+xnTP2a6deyrXoepxQRt3If7sHg3LJWp/eQ6aWH0QQsEAzza8IMgpAjqpdcaOeoLzTY6I4rA==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/merge-tree': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/merge-tree': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 double-ended-queue: 2.1.0-0 uuid: 9.0.1 transitivePeerDependencies: @@ -20912,35 +20957,35 @@ packages: - supports-color dev: false - /@fluidframework/shared-object-base@2.4.0(debug@4.3.7): - resolution: {integrity: sha512-RCCy/D4cxkWRLTt0LRUtEdGPYXAlExizTcZZOtfARgpTABwfE4KXGpwRnQwey7yB0DqY6yKf855jYVhF1yKGJA==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/shared-object-base@2.5.0(debug@4.3.7): + resolution: {integrity: sha512-6KdsAqw4BBPqCR4kerFGeZIZ8WqCKER6PI5y7AKojQffXEp2JXf87uuQrOK45AkEhvC9ebhoioAwPR3PzNz3Fg==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 uuid: 9.0.1 transitivePeerDependencies: - debug - supports-color dev: true - /@fluidframework/shared-summary-block@2.4.0: - resolution: {integrity: sha512-Ty8FN4yjdasCCFgBzOVqifSZ4SZW3BqT6ekNdTR5YPd1olFvhmvZJR/HkdUOmvRR4Sr8rRj7pbHKJszOWXtwHQ==} + /@fluidframework/shared-summary-block@2.5.0: + resolution: {integrity: sha512-eVVlvHkhOW7PjIptlSTyuQ1+NPM+cs6VrhmMMLVLHyfCI9M4VY82xS7Zvqjwfn4kmk5uSfQrga56ZOJHeLOc/A==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color @@ -20950,25 +20995,25 @@ packages: resolution: {integrity: sha512-0pdq28pZ/cA/OVOp7KiUv8/bDxltwQy2ca7UJQGuyRDF9n1QcXoWC98liu2fB3/imahdfDi5pZ6l2vw/nIiFkA==} dev: false - /@fluidframework/synthesize@2.4.0: - resolution: {integrity: sha512-DiaYGuHv17WsVZHiBqVw/IcQNnZBOfw50LPPvX9QSOKFXyXO4gM/KrDVmk1trCklC4lfFP3RQ+B81MWBKJKcuQ==} + /@fluidframework/synthesize@2.5.0: + resolution: {integrity: sha512-rrY9uiHrI9PNMGVnwq2dZL4GbCtcTTVhECKiqcmYVnqZhY5VoR+kMXTSA427beQUjYjoimgxZwmATcCc8EqURw==} dependencies: - '@fluidframework/core-utils': 2.4.0 + '@fluidframework/core-utils': 2.5.0 dev: true - /@fluidframework/task-manager@2.4.0: - resolution: {integrity: sha512-OB5tL6fzDZadnOa864fTCWTDq1ZqPvqM+YJXKaOqXDzsvE2Tc1ZRA0nSB7aBHk7ks5IjDWkhumkch7VmtGFa6A==} + /@fluidframework/task-manager@2.5.0: + resolution: {integrity: sha512-n0ebCj4LtMMiSQJWXu/rDotp1KbWiimke9hVgofsmMSSdj1AOkcXCDyHhiV0i/c6yxzu65r0VWyTpkYJ9ffZZQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) transitivePeerDependencies: - debug - supports-color @@ -20986,34 +21031,34 @@ packages: - supports-color dev: false - /@fluidframework/telemetry-utils@2.4.0: - resolution: {integrity: sha512-T2jxXURiEEi0+yc/KBxc4a9ySkuTcYQltgi9OVxvhq/ZLGe2zeOYdffnf61xday+Z9tPV+psuHk44bzDxD3MpA==} + /@fluidframework/telemetry-utils@2.5.0: + resolution: {integrity: sha512-j1maxRsMDbffBgrHh7kqKvEP8hGjSYVCoHks3D9wORAp8kuxCKdWOmss7lUnI7HVfPPv+bxVEd6aw402pWG9NA==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 debug: 4.3.7(supports-color@8.1.1) uuid: 9.0.1 transitivePeerDependencies: - supports-color dev: true - /@fluidframework/test-runtime-utils@2.4.0: - resolution: {integrity: sha512-tvcv+RGkXLY+f7xN1lJy9BT46irG163NflSqwfB8CBYnOH/5imuj6ByG9vzmfH+gByPe6S5iLaIim2Xlb3NiHw==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/test-runtime-utils@2.5.0: + resolution: {integrity: sha512-lh6Hmz1stEJQ6CC4VXyzaKJeFJ6dswjrCgL5RsEmuqB5iqT9EQLZC5tUCegbWDFoH6jycbKEAm84M56vIxqzeg==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 jsrsasign: 11.1.0 uuid: 9.0.1 transitivePeerDependencies: @@ -21029,29 +21074,29 @@ packages: hasBin: true dev: true - /@fluidframework/test-utils@2.4.0: - resolution: {integrity: sha512-Xl+Huf65IFsWbZu1z4Op3s4GAgKVee+cIgXAZ2yhZREatmsYxkYpijAHOsCkPxRFL40E5o2J1POP7+8urA4k7A==} - dependencies: - '@fluid-internal/test-driver-definitions': 2.4.0 - '@fluidframework/aqueduct': 2.4.0(debug@4.3.7) - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/container-runtime-definitions': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore': 2.4.0(debug@4.3.7) - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/local-driver': 2.4.0(debug@4.3.7) - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-driver': 2.4.0(debug@4.3.7) - '@fluidframework/request-handler': 2.4.0(debug@4.3.7) - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/test-utils@2.5.0: + resolution: {integrity: sha512-+J0qcwN/pSQTg8sUyDQkhn+Q4Ck7lbM5g0n2Vn+YHyTX1Gy/gzGvT6v2PX6iribJ/iuJT4xd/xldr6ViJHPZKg==} + dependencies: + '@fluid-internal/test-driver-definitions': 2.5.0 + '@fluidframework/aqueduct': 2.5.0(debug@4.3.7) + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/container-runtime-definitions': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore': 2.5.0(debug@4.3.7) + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/local-driver': 2.5.0(debug@4.3.7) + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-driver': 2.5.0(debug@4.3.7) + '@fluidframework/request-handler': 2.5.0(debug@4.3.7) + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 best-random: 1.0.3 debug: 4.3.7(supports-color@8.1.1) mocha: 10.7.3 @@ -21063,21 +21108,21 @@ packages: - utf-8-validate dev: true - /@fluidframework/tinylicious-client@2.4.0: - resolution: {integrity: sha512-ikbxrw7yWPy7PlUYJ/0pZue9ulTZn+Q+c1LkSqWj6Qp3SUEMrV4hSDlkaQ4JfRiySGJAHS0ALP9kkKIDDzSaQA==} + /@fluidframework/tinylicious-client@2.5.0: + resolution: {integrity: sha512-eL/YFRDohrGKBZS29LkCfbUEicD+noBgGYEKiGxmG/M2wdkS8Y84BFcUVb8cl+f/hwmzXbnl96diiS7Zwpnzjg==} dependencies: - '@fluidframework/container-definitions': 2.4.0 - '@fluidframework/container-loader': 2.4.0 - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/fluid-static': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 - '@fluidframework/tinylicious-driver': 2.4.0 + '@fluidframework/container-definitions': 2.5.0 + '@fluidframework/container-loader': 2.5.0 + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/fluid-static': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 + '@fluidframework/tinylicious-driver': 2.5.0 transitivePeerDependencies: - bufferutil - debug @@ -21086,13 +21131,13 @@ packages: - utf-8-validate dev: true - /@fluidframework/tinylicious-driver@2.4.0: - resolution: {integrity: sha512-u6SvkuSdkJfBZCsLLdFujabqd0AELUO/B8aTCGuU5QOD0k1ZBtxnqglJqw916VlJ4qHoZvc14/hsdCvP62jw9w==} + /@fluidframework/tinylicious-driver@2.5.0: + resolution: {integrity: sha512-u57d/3sumXY38LO6pEkkDnZLrlCOGzh3R1a/6/hMuJaLW7g1m2z5GG41PQEXtkl2t3AFIe09vjIGm/uq8X9rnA==} dependencies: - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/routerlicious-driver': 2.4.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/routerlicious-driver': 2.5.0(debug@4.3.7) jsrsasign: 11.1.0 uuid: 9.0.1 transitivePeerDependencies: @@ -21103,13 +21148,13 @@ packages: - utf-8-validate dev: true - /@fluidframework/tool-utils@2.4.0: - resolution: {integrity: sha512-KjD0Bby1PfffAt19aSNSYtBXa9JQoo5cKJ2n+R52KC6EUNKw5K2NDknV1td1wDf0Ny9ff2A7Ns2EKBY5KumaJg==} + /@fluidframework/tool-utils@2.5.0: + resolution: {integrity: sha512-Zz/ROd7sL9y3z6j2VszUx25+QhyEbmHoOVouHI7TH2bqJE/u4Zk3h6CjaCPhBNDYqYWLnRZVqUImkyT6hqh3Fw==} dependencies: - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/driver-utils': 2.4.0(debug@4.3.7) - '@fluidframework/odsp-doclib-utils': 2.4.0(debug@4.3.7) + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/driver-utils': 2.5.0(debug@4.3.7) + '@fluidframework/odsp-doclib-utils': 2.5.0(debug@4.3.7) async-mutex: 0.3.2 debug: 4.3.7(supports-color@8.1.1) jwt-decode: 4.0.0 @@ -21119,22 +21164,23 @@ packages: - supports-color dev: true - /@fluidframework/tree@2.4.0: - resolution: {integrity: sha512-te/yXq9Vpd3gmy9+HgCuAgzgFqRG1f8O0oubMoknAXpMTmZtf9ZWdd5+RadJ15HqNRT0H/LPnKt/MDrs1lhNJQ==} - dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/container-runtime': 2.4.0(debug@4.3.7) - '@fluidframework/core-interfaces': 2.4.0 - '@fluidframework/core-utils': 2.4.0 - '@fluidframework/datastore-definitions': 2.4.0 - '@fluidframework/driver-definitions': 2.4.0 - '@fluidframework/id-compressor': 2.4.0 - '@fluidframework/runtime-definitions': 2.4.0 - '@fluidframework/runtime-utils': 2.4.0(debug@4.3.7) - '@fluidframework/shared-object-base': 2.4.0(debug@4.3.7) - '@fluidframework/telemetry-utils': 2.4.0 + /@fluidframework/tree@2.5.0: + resolution: {integrity: sha512-Z5RcWxCsNOFezFkZXySHn2ZZf9IDBlzA/Fvdq8loxep1nFf59GOS0oQYk+6DWWDcBht35SYVi3AtQMws2I6adw==} + dependencies: + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/container-runtime': 2.5.0(debug@4.3.7) + '@fluidframework/core-interfaces': 2.5.0 + '@fluidframework/core-utils': 2.5.0 + '@fluidframework/datastore-definitions': 2.5.0 + '@fluidframework/driver-definitions': 2.5.0 + '@fluidframework/id-compressor': 2.5.0 + '@fluidframework/runtime-definitions': 2.5.0 + '@fluidframework/runtime-utils': 2.5.0(debug@4.3.7) + '@fluidframework/shared-object-base': 2.5.0(debug@4.3.7) + '@fluidframework/telemetry-utils': 2.5.0 '@sinclair/typebox': 0.32.35 '@tylerbu/sorted-btree-es6': 1.8.0 + '@types/ungap__structured-clone': 1.2.0 '@ungap/structured-clone': 1.2.0 uuid: 9.0.1 transitivePeerDependencies: @@ -21142,14 +21188,14 @@ packages: - supports-color dev: true - /@fluidframework/undo-redo@2.4.0: - resolution: {integrity: sha512-iApcS2Wef628dEF54J2nxx14/V/sVcjUs/1niAtOP2ExqOm7wRgI4fhan71ncyu6QdVXxJYbBIAINJFCBHHudg==} + /@fluidframework/undo-redo@2.5.0: + resolution: {integrity: sha512-ShEi8AZcQlIu32hCg5wIzWNoyBHiMAL58RboAidLt4YYYv9cB+xiZq5TyRyYHpLMhXj2p3bYvQFXTJAoXHZjxQ==} dependencies: - '@fluid-internal/client-utils': 2.4.0 - '@fluidframework/map': 2.4.0(debug@4.3.7) - '@fluidframework/matrix': 2.4.0 - '@fluidframework/merge-tree': 2.4.0(debug@4.3.7) - '@fluidframework/sequence': 2.4.0 + '@fluid-internal/client-utils': 2.5.0 + '@fluidframework/map': 2.5.0(debug@4.3.7) + '@fluidframework/matrix': 2.5.0 + '@fluidframework/merge-tree': 2.5.0(debug@4.3.7) + '@fluidframework/sequence': 2.5.0 transitivePeerDependencies: - debug - supports-color @@ -21195,6 +21241,15 @@ packages: xcase: 2.0.1 dev: true + /@gitbeaker/core@38.12.1: + resolution: {integrity: sha512-8XMVcBIdVAAoxn7JtqmZ2Ee8f+AZLcCPmqEmPFOXY2jPS84y/DERISg/+sbhhb18iRy+ZsZhpWgQ/r3CkYNJOQ==} + engines: {node: '>=18.0.0'} + dependencies: + '@gitbeaker/requester-utils': 38.12.1 + qs: 6.13.0 + xcase: 2.0.1 + dev: true + /@gitbeaker/node@35.8.1: resolution: {integrity: sha512-g6rX853y61qNhzq9cWtxIEoe2KDeFBtXAeWMGWJnc3nz3WRump2pIICvJqw/yobLZqmTNt+ea6w3/n92Mnbn3g==} engines: {node: '>=14.2.0'} @@ -21216,6 +21271,22 @@ packages: xcase: 2.0.1 dev: true + /@gitbeaker/requester-utils@38.12.1: + resolution: {integrity: sha512-Rc/DgngS0YPN+AY1s9UnexKSy4Lh0bkQVAq9p7PRbRpXb33SlTeCg8eg/8+A/mrMcHgYmP0XhH8lkizyA5tBUQ==} + engines: {node: '>=18.0.0'} + dependencies: + qs: 6.13.0 + xcase: 2.0.1 + dev: true + + /@gitbeaker/rest@38.12.1: + resolution: {integrity: sha512-9KMSDtJ/sIov+5pcH+CAfiJXSiuYgN0KLKQFg0HHWR2DwcjGYkcbmhoZcWsaOWOqq4kihN1l7wX91UoRxxKKTQ==} + engines: {node: '>=18.0.0'} + dependencies: + '@gitbeaker/core': 38.12.1 + '@gitbeaker/requester-utils': 38.12.1 + dev: true + /@griffel/core@1.18.0: resolution: {integrity: sha512-3Dkn6f7ULeSzJ1wLyLfN1vc+v3q5shuEejeMe4XymBozQo0l35WIfH8FWcwB+Xrgip4fLLOy1p3sYN85gFGZxw==} dependencies: @@ -21261,14 +21332,17 @@ packages: minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} + dev: true /@humanwhocodes/object-schema@2.0.3: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + dev: true /@iconify/react@5.0.2(react@18.3.1): resolution: {integrity: sha512-wtmstbYlEbo4NDxFxBJkhkf9gJBDqMGr7FaqLrAUMneRV3Z+fVHLJjOhWbkAF8xDQNFC/wcTYdrWo1lnRhmagQ==} @@ -21294,6 +21368,22 @@ packages: figures: 3.2.0 dev: true + /@inquirer/checkbox@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-ehJjmNPdguajc1hStvjN7DJNVjwG5LC1mgGMGFjCmdkn2fxB2GtULftMnlaqNmvMdPpqdaSoOFpl86VkLtG4pQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + /@inquirer/confirm@2.0.17: resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} engines: {node: '>=14.18.0'} @@ -21309,6 +21399,36 @@ packages: dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 + dev: true + + /@inquirer/confirm@5.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + + /@inquirer/core@10.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==} + engines: {node: '>=18'} + dependencies: + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@18.19.54) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' /@inquirer/core@6.0.0: resolution: {integrity: sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==} @@ -21346,6 +21466,7 @@ packages: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 + dev: true /@inquirer/editor@1.2.15: resolution: {integrity: sha512-gQ77Ls09x5vKLVNMH9q/7xvYPT6sIs5f7URksw+a2iJZ0j48tVS6crLqm2ugG33tgXHIwiEqkytY60Zyh5GkJQ==} @@ -21357,6 +21478,20 @@ packages: external-editor: 3.1.0 dev: true + /@inquirer/editor@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-qAHHJ6hs343eNtCKgV2wV5CImFxYG8J1pl/YCeI5w9VoW7QpulRUU26+4NsMhjR6zDRjKBsH/rRjCIcaAOHsrg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + external-editor: 3.1.0 + /@inquirer/expand@1.1.16: resolution: {integrity: sha512-TGLU9egcuo+s7PxphKUCnJnpCIVY32/EwPCLLuu+gTvYiD8hZgx8Z2niNQD36sa6xcfpdLY6xXDBiL/+g1r2XQ==} engines: {node: '>=14.18.0'} @@ -21367,9 +21502,28 @@ packages: figures: 3.2.0 dev: true + /@inquirer/expand@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-9anjpdc802YInXekwePsa5LWySzVMHbhVS6v6n5IJxrl8w09mODOeP69wZ1d0WrOvot2buQSmYp4lW/pq8y+zQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + yoctocolors-cjs: 2.1.2 + /@inquirer/figures@1.0.6: resolution: {integrity: sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==} engines: {node: '>=18'} + dev: true + + /@inquirer/figures@1.0.7: + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + engines: {node: '>=18'} /@inquirer/input@1.2.16: resolution: {integrity: sha512-Ou0LaSWvj1ni+egnyQ+NBtfM1885UwhRCMtsRt2bBO47DoC1dwtCa+ZUNgrxlnCHHF0IXsbQHYtIIjFGAavI4g==} @@ -21388,6 +21542,32 @@ packages: '@inquirer/type': 1.5.5 dev: true + /@inquirer/input@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-m+SliZ2m43cDRIpAdQxfv5QOeAQCuhS8TGLvtzEP1An4IH1kBES4RLMRgE/fC+z29aN8qYG8Tq/eXQQKTYwqAg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + + /@inquirer/number@3.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-gF3erqfm0snpwBjbyKXUUe17QJ7ebm49btXApajrM0rgCCoYX0o9W5NCuYNae87iPxaIJVjtuoQ42DX32IdbMA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + /@inquirer/password@1.1.16: resolution: {integrity: sha512-aZYZVHLUXZ2gbBot+i+zOJrks1WaiI95lvZCn1sKfcw6MtSSlYC8uDX8sTzQvAsQ8epHoP84UNvAIT0KVGOGqw==} engines: {node: '>=14.18.0'} @@ -21398,6 +21578,20 @@ packages: chalk: 4.1.2 dev: true + /@inquirer/password@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-D7zUuX4l4ZpL3D7/SWu9ibijP09jigwHi/gfUHLx5GMS5oXzuMfPV2xPMG1tskco4enTx70HA0VtMXecerpvbg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + ansi-escapes: 4.3.2 + /@inquirer/prompts@3.3.2: resolution: {integrity: sha512-k52mOMRvTUejrqyF1h8Z07chC+sbaoaUYzzr1KrJXyj7yaX7Nrh0a9vktv8TuocRwIJOQMaj5oZEmkspEcJFYQ==} engines: {node: '>=14.18.0'} @@ -21413,6 +21607,27 @@ packages: '@inquirer/select': 1.3.3 dev: true + /@inquirer/prompts@7.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-cu2CpGC2hz7WTt2VBvdkzahDvYice6vYA/8Dm7Fy3tRNzKuQTF2EY3CV4H2GamveWE6tA2XzyXtbWX8+t4WMQg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/checkbox': 4.0.1(@types/node@18.19.54) + '@inquirer/confirm': 5.0.1(@types/node@18.19.54) + '@inquirer/editor': 4.0.1(@types/node@18.19.54) + '@inquirer/expand': 4.0.1(@types/node@18.19.54) + '@inquirer/input': 4.0.1(@types/node@18.19.54) + '@inquirer/number': 3.0.1(@types/node@18.19.54) + '@inquirer/password': 4.0.1(@types/node@18.19.54) + '@inquirer/rawlist': 4.0.1(@types/node@18.19.54) + '@inquirer/search': 3.0.1(@types/node@18.19.54) + '@inquirer/select': 4.0.1(@types/node@18.19.54) + '@types/node': 18.19.54 + /@inquirer/rawlist@1.2.16: resolution: {integrity: sha512-pZ6TRg2qMwZAOZAV6TvghCtkr53dGnK29GMNQ3vMZXSNguvGqtOVc4j/h1T8kqGJFagjyfBZhUPGwNS55O5qPQ==} engines: {node: '>=14.18.0'} @@ -21422,6 +21637,35 @@ packages: chalk: 4.1.2 dev: true + /@inquirer/rawlist@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-0LuMOgaWs7W8JNcbiKkoFwyWFDEeCmLqDCygF0hidQUVa6J5grFVRZxrpompiWDFM49Km2rf7WoZwRo1uf1yWQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + yoctocolors-cjs: 2.1.2 + + /@inquirer/search@3.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-ehMqjiO0pAf+KtdONKeCLVy4i3fy3feyRRhDrvzWhiwB8JccgKn7eHFr39l+Nx/FaZAhr0YxIJvkK5NuNvG+Ww==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + yoctocolors-cjs: 2.1.2 + /@inquirer/select@1.3.3: resolution: {integrity: sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==} engines: {node: '>=14.18.0'} @@ -21444,17 +21688,46 @@ packages: yoctocolors-cjs: 2.1.2 dev: true + /@inquirer/select@4.0.1(@types/node@18.19.54): + resolution: {integrity: sha512-tVRatFRGU49bxFCKi/3P+C0E13KZduNFbWuHWRx0L2+jbiyKRpXgHp9qiRHWRk/KarhYBXzH/di6w3VQ5aJd5w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.0.1(@types/node@18.19.54) + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@18.19.54) + '@types/node': 18.19.54 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + /@inquirer/type@1.5.5: resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} engines: {node: '>=18'} dependencies: mute-stream: 1.0.0 + dev: true /@inquirer/type@2.0.0: resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} engines: {node: '>=18'} dependencies: mute-stream: 1.0.0 + dev: true + + /@inquirer/type@3.0.0(@types/node@18.19.54): + resolution: {integrity: sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@types/node': 18.19.54 /@ioredis/as-callback@3.0.0: resolution: {integrity: sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==} @@ -21814,6 +22087,7 @@ packages: engines: {node: '>=14.18.0'} dependencies: '@manypkg/tools': 1.1.2 + dev: true /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} @@ -21831,6 +22105,7 @@ packages: dependencies: '@manypkg/find-root': 2.2.3 '@manypkg/tools': 1.1.2 + dev: true /@manypkg/tools@1.1.2: resolution: {integrity: sha512-3lBouSuF7CqlseLB+FKES0K4FQ02JrbEoRtJhxnsyB1s5v4AP03gsoohN8jp7DcOImhaR9scYdztq3/sLfk/qQ==} @@ -21839,6 +22114,7 @@ packages: fast-glob: 3.3.2 jju: 1.4.0 js-yaml: 4.1.0 + dev: true /@microsoft/1ds-core-js@3.2.18(tslib@1.14.1): resolution: {integrity: sha512-ytlFv3dfb8OGqvbZP8tSIlNvn3QNYxdsF0k6ikRMWSr6CmBxBi1sliaxc2Q5KuYOuaeWkd8WRm25Rx/UtHcyMg==} @@ -21864,10 +22140,10 @@ packages: resolution: {integrity: sha512-/pPYePc1niY1cjryKcxA39NIGbva70LazDh1u+9T0hRzACCzzfJKu9qZvPJwnrhVGHKBKxUtKJ0fGOaEXqpXZw==} hasBin: true dependencies: - '@microsoft/api-extractor-model': 7.29.8 + '@microsoft/api-extractor-model': 7.29.8(@types/node@18.19.54) '@microsoft/tsdoc': 0.15.0 - '@rushstack/node-core-library': 5.9.0 - '@rushstack/terminal': 0.14.2 + '@rushstack/node-core-library': 5.9.0(@types/node@18.19.54) + '@rushstack/terminal': 0.14.2(@types/node@18.19.54) '@rushstack/ts-command-line': 4.22.8 js-yaml: 3.13.1 resolve: 1.22.8 @@ -21885,12 +22161,33 @@ packages: - '@types/node' dev: true - /@microsoft/api-extractor-model@7.29.8: + /@microsoft/api-extractor-model@7.29.8(@types/node@18.19.54): resolution: {integrity: sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==} dependencies: '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 - '@rushstack/node-core-library': 5.9.0 + '@rushstack/node-core-library': 5.9.0(@types/node@18.19.54) + transitivePeerDependencies: + - '@types/node' + dev: true + + /@microsoft/api-extractor@7.47.11(@types/node@18.19.54): + resolution: {integrity: sha512-lrudfbPub5wzBhymfFtgZKuBvXxoSIAdrvS2UbHjoMT2TjIEddq6Z13pcve7A03BAouw0x8sW8G4txdgfiSwpQ==} + hasBin: true + dependencies: + '@microsoft/api-extractor-model': 7.29.8(@types/node@18.19.54) + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + '@rushstack/node-core-library': 5.9.0(@types/node@18.19.54) + '@rushstack/rig-package': 0.5.3 + '@rushstack/terminal': 0.14.2(@types/node@18.19.54) + '@rushstack/ts-command-line': 4.23.0(@types/node@18.19.54) + lodash: 4.17.21 + minimatch: 3.0.8 + resolve: 1.22.8 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.4.2 transitivePeerDependencies: - '@types/node' dev: true @@ -22049,6 +22346,7 @@ packages: ajv: 6.12.6 jju: 1.4.0 resolve: 1.19.0 + dev: true /@microsoft/tsdoc-config@0.17.0: resolution: {integrity: sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==} @@ -22061,6 +22359,7 @@ packages: /@microsoft/tsdoc@0.14.2: resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + dev: true /@microsoft/tsdoc@0.15.0: resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} @@ -22450,6 +22749,7 @@ packages: /@nolyfill/is-core-module@1.0.39: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + dev: true /@npmcli/fs@2.1.2: resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} @@ -22526,8 +22826,8 @@ packages: - supports-color dev: true - /@oclif/core@4.0.25: - resolution: {integrity: sha512-3rX1dA40cZWzH6NKcNJGXrJn30XaxnkFXfapLMX0wwSWvBfXE/zIduHt03kLRxeFuEBUR5FBnU64odDWKhtFKg==} + /@oclif/core@4.0.31: + resolution: {integrity: sha512-7oyIZv/C1TP+fPc2tSzVPYqG1zU+nel1QvJxjAWyVhud0J8B5SpKZnryedxs3nlSVPJ6K1MT31C9esupCBYgZw==} engines: {node: '>=18.0.0'} dependencies: ansi-escapes: 4.3.2 @@ -22549,46 +22849,52 @@ packages: wordwrap: 1.0.0 wrap-ansi: 7.0.0 - /@oclif/plugin-autocomplete@3.2.5: - resolution: {integrity: sha512-JJWctXyk/RkA+KuLsRkYopNvs8E2K3ZFTVOt1lJR+jVwH7VnOqN1iVl19GkzVzvbsGcGvVaBCDS82cvpeST7Nw==} + /@oclif/plugin-autocomplete@3.2.8: + resolution: {integrity: sha512-ECqUJj/IBHU0sjeKtGWnjGqdrdevoP9frHNBzl+wRqwqlOiT2jSbkMqvdHN4kiZEB9jEmkgMbRNmNpx5oJnBSw==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.25 + '@oclif/core': 4.0.31 ansis: 3.3.2 debug: 4.3.7(supports-color@8.1.1) ejs: 3.1.10 transitivePeerDependencies: - supports-color - /@oclif/plugin-commands@4.0.16: - resolution: {integrity: sha512-d8ZcO8w7HjLo6Vms3bUxpWVbBmjK75ptXHggx/GIV0bIWoq0wMr6RzrDCdIkGG9m+qIf0nNVe4I4AWrGVsEn/A==} + /@oclif/plugin-commands@4.1.7: + resolution: {integrity: sha512-BdC857uNq2Ar7pwDYubAoVM8pkl6nTwjieBsvUQt/mUYdHaYZEZRmZ/J2ZnqK0BcDit4VlLdK+y5E8h30jcCYw==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.25 + '@oclif/core': 4.0.31 + '@oclif/table': 0.3.3 lodash: 4.17.21 object-treeify: 4.0.1 - tty-table: 4.2.3 + transitivePeerDependencies: + - bufferutil + - react-devtools-core + - utf-8-validate - /@oclif/plugin-help@6.2.13: - resolution: {integrity: sha512-IgLytXXYfuTs6dV7xhgt4xhE4Mjx8/29/4iH6jp9TKDblopCBnZ3q1VMlHfhhLsJ4Q+rSjP/QCHtiO3HnXk69Q==} + /@oclif/plugin-help@6.2.16: + resolution: {integrity: sha512-1x/Bm0LebDouDOfsjkOz+6AXqY6gIZ6JmmU/KuF/GnUmowDvj5i3MFlP9uBTiN8UsOUeT9cdLwnc1kmitHWFTg==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.25 + '@oclif/core': 4.0.31 - /@oclif/plugin-not-found@3.2.22: - resolution: {integrity: sha512-snd/gmYjTYIa8vr5rPLNus0ymKhhITRTFWLlYCJvAZTs2kX+vUMMdpKId9pEPSzoVGmyddNVshWSCJ2FSgR0mg==} + /@oclif/plugin-not-found@3.2.25(@types/node@18.19.54): + resolution: {integrity: sha512-Hm07ouLZq8I9/V46F2BqWEzFexdjaxGHFbwckxXu3YlVq4/xp6lOJXlF5olu4dbTUaJs532Hth4Uh0OIsp9CSw==} engines: {node: '>=18.0.0'} dependencies: - '@inquirer/confirm': 3.2.0 - '@oclif/core': 4.0.25 + '@inquirer/prompts': 7.0.1(@types/node@18.19.54) + '@oclif/core': 4.0.31 ansis: 3.3.2 fast-levenshtein: 3.0.0 + transitivePeerDependencies: + - '@types/node' - /@oclif/plugin-warn-if-update-available@3.1.18: - resolution: {integrity: sha512-bsqmIBktptHsFOy5H8G2XZZUckxXdKHjVFjy8EyPyVT9rq+qZAxD+7cYNI/TQMXGONDYY9iV4I00gASOVKCCAA==} + /@oclif/plugin-warn-if-update-available@3.1.21: + resolution: {integrity: sha512-yG03rR6Z795lSlkuS+6A9JBSq/VQZ40XspTsKdXa/PUJl52RTeZeOHlaecuv4TddAE6T8VsPdWvry68q5TPE4w==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.25 + '@oclif/core': 4.0.31 ansis: 3.3.2 debug: 4.3.7(supports-color@8.1.1) http-call: 5.3.0 @@ -22598,6 +22904,25 @@ packages: - supports-color dev: true + /@oclif/table@0.3.3: + resolution: {integrity: sha512-sz6gGT1JAPP743vxl1491hxboIu0ZFHaP3gyvhz5Prgsuljp2NGyyu7JPEMeVImCnZ9N3K9cy3VXxRFEwRH/ig==} + engines: {node: '>=18.0.0'} + dependencies: + '@oclif/core': 4.0.31 + '@types/react': 18.3.12 + change-case: 5.4.4 + cli-truncate: 4.0.0 + ink: 5.0.1(@types/react@18.3.12)(react@18.3.1) + natural-orderby: 3.0.2 + object-hash: 3.0.0 + react: 18.3.1 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + transitivePeerDependencies: + - bufferutil + - react-devtools-core + - utf-8-validate + /@octokit/auth-token@2.5.0: resolution: {integrity: sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==} dependencies: @@ -22609,6 +22934,11 @@ packages: engines: {node: '>= 14'} dev: true + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: true + /@octokit/auth-token@5.1.1: resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} engines: {node: '>= 18'} @@ -22643,6 +22973,19 @@ packages: - encoding dev: true + /@octokit/core@5.2.0: + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.6.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + dev: true + /@octokit/core@6.1.2: resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} engines: {node: '>= 18'} @@ -22681,6 +23024,14 @@ packages: universal-user-agent: 6.0.1 dev: true + /@octokit/endpoint@9.0.5: + resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.6.0 + universal-user-agent: 6.0.1 + dev: true + /@octokit/graphql@4.8.0: resolution: {integrity: sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==} dependencies: @@ -22702,6 +23053,15 @@ packages: - encoding dev: true + /@octokit/graphql@7.1.0: + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request': 8.4.0 + '@octokit/types': 13.6.0 + universal-user-agent: 6.0.1 + dev: true + /@octokit/graphql@8.1.1: resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} engines: {node: '>= 18'} @@ -22796,6 +23156,15 @@ packages: once: 1.4.0 dev: true + /@octokit/request-error@5.1.0: + resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.6.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: true + /@octokit/request-error@6.1.5: resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} engines: {node: '>= 18'} @@ -22830,6 +23199,16 @@ packages: - encoding dev: true + /@octokit/request@8.4.0: + resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.5 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.6.0 + universal-user-agent: 6.0.1 + dev: true + /@octokit/request@9.1.3: resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} engines: {node: '>= 18'} @@ -23171,6 +23550,7 @@ packages: /@rushstack/eslint-patch@1.4.0: resolution: {integrity: sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==} + dev: true /@rushstack/eslint-plugin-security@0.7.1(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-84N42tlONhcbXdlk5Rkb+/pVxPnH+ojX8XwtFoecCRV88/4Ii7eGEyJPb73lOpHaE3NJxLzLVIeixKYQmdjImA==} @@ -23183,6 +23563,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@rushstack/eslint-plugin@0.13.1(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-qQ6iPCm8SFuY+bpcSv5hlYtdwDHcFlE6wlpUHa0ywG9tGVBYM5But8S4qVRFq1iejAuFX+ubNUOyFJHvxpox+A==} @@ -23195,6 +23576,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@rushstack/node-core-library@3.66.1(@types/node@18.19.54): resolution: {integrity: sha512-ker69cVKAoar7MMtDFZC4CzcDxjwqIhFzqEnYI5NRN/8M3om6saWCVx/A7vL2t/jFCJsnzQplRDqA7c78pytng==} @@ -23232,7 +23614,7 @@ packages: semver: 7.5.4 dev: true - /@rushstack/node-core-library@5.9.0: + /@rushstack/node-core-library@5.9.0(@types/node@18.19.54): resolution: {integrity: sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==} peerDependencies: '@types/node': ^18.19.0 @@ -23240,6 +23622,7 @@ packages: '@types/node': optional: true dependencies: + '@types/node': 18.19.54 ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) ajv-formats: 3.0.1(ajv@8.13.0) @@ -23270,7 +23653,7 @@ packages: supports-color: 8.1.1 dev: true - /@rushstack/terminal@0.14.2: + /@rushstack/terminal@0.14.2(@types/node@18.19.54): resolution: {integrity: sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==} peerDependencies: '@types/node': ^18.19.0 @@ -23278,12 +23661,14 @@ packages: '@types/node': optional: true dependencies: - '@rushstack/node-core-library': 5.9.0 + '@rushstack/node-core-library': 5.9.0(@types/node@18.19.54) + '@types/node': 18.19.54 supports-color: 8.1.1 dev: true /@rushstack/tree-pattern@0.3.1: resolution: {integrity: sha512-2yn4qTkXZTByQffL3ymS6viYuyZk3YnJT49bopGBlm9Thtyfa7iuFUV6tt+09YIRO1sjmSWILf4dPj6+Dr5YVA==} + dev: true /@rushstack/ts-command-line@4.22.7(@types/node@18.19.54): resolution: {integrity: sha512-kgb8hjEzKucH200xiUecU/VeA/Wzk4BBMRWP2ZLH2XGC/PEYzPih5IyYFtK1QCYboeRYTxoNYwSG6UQlstBaLg==} @@ -23299,7 +23684,18 @@ packages: /@rushstack/ts-command-line@4.22.8: resolution: {integrity: sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==} dependencies: - '@rushstack/terminal': 0.14.2 + '@rushstack/terminal': 0.14.2(@types/node@18.19.54) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + dev: true + + /@rushstack/ts-command-line@4.23.0(@types/node@18.19.54): + resolution: {integrity: sha512-jYREBtsxduPV6ptNq8jOKp9+yx0ld1Tb/Tkdnlj8gTjazl1sF3DwX2VbluyYrNd0meWIL0bNeer7WDf5tKFjaQ==} + dependencies: + '@rushstack/terminal': 0.14.2(@types/node@18.19.54) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -23358,11 +23754,6 @@ packages: /@sinclair/typebox@0.32.35: resolution: {integrity: sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA==} - /@sindresorhus/is@0.14.0: - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - dev: true - /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -23466,13 +23857,6 @@ packages: tslib: 2.7.0 dev: true - /@szmarczak/http-timer@1.1.2: - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} - dependencies: - defer-to-connect: 1.1.3 - dev: true - /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -23624,6 +24008,7 @@ packages: minimatch: 9.0.5 mkdirp: 3.0.1 path-browserify: 1.0.1 + dev: true /@tsconfig/node10@1.0.11: resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -24047,6 +24432,7 @@ packages: /@types/minimatch@3.0.5: resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + dev: true /@types/minimatch@5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -24062,6 +24448,7 @@ packages: resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} dependencies: '@types/node': 18.19.54 + dev: true /@types/nconf@0.10.7: resolution: {integrity: sha512-ltJgbQX0XgjkeDrz0anTCXLBLatppWYFCxp88ILEwybfAuyNWr0Qb+ceFFqZ0VDR8fguEjr0hH37ZF+AF4gsxw==} @@ -24077,7 +24464,6 @@ packages: dependencies: '@types/node': 18.19.54 form-data: 4.0.0 - dev: true /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -24099,9 +24485,11 @@ packages: resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} dependencies: undici-types: 6.19.8 + dev: true /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + dev: true /@types/orderedmap@1.0.0: resolution: {integrity: sha512-dxKo80TqYx3YtBipHwA/SdFmMMyLCnP+5mkEqN0eMjcTBzHkiiX0ES118DsjDBjvD+zeSsSU9jULTZ+frog+Gw==} @@ -24172,6 +24560,12 @@ packages: '@types/prop-types': 15.7.13 csstype: 3.1.3 + /@types/react@18.3.12: + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + /@types/recharts@1.8.29: resolution: {integrity: sha512-ulKklaVsnFIIhTQsQw226TnOibrddW1qUQNFVhoQEyY1Z7FRQrNecFCGt7msRuJseudzE9czVawZb17dK/aPXw==} dependencies: @@ -24307,6 +24701,7 @@ packages: /@types/wrap-ansi@3.0.0: resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + dev: true /@types/ws@6.0.4: resolution: {integrity: sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==} @@ -24369,6 +24764,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/experimental-utils@5.59.11(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-GkQGV0UF/V5Ra7gZMBmiD1WrYUFOJNvCZs+XQnUyJoxmqfWMXVNyB2NVCPRKefoQcpvTv9UpJyfCvsJFs8NzzQ==} @@ -24381,6 +24777,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/parser@6.21.0(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} @@ -24401,6 +24798,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/parser@6.7.5(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==} @@ -24421,6 +24819,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/scope-manager@5.59.11: resolution: {integrity: sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==} @@ -24428,6 +24827,7 @@ packages: dependencies: '@typescript-eslint/types': 5.59.11 '@typescript-eslint/visitor-keys': 5.59.11 + dev: true /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} @@ -24443,6 +24843,7 @@ packages: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 + dev: true /@typescript-eslint/scope-manager@6.7.5: resolution: {integrity: sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==} @@ -24450,6 +24851,7 @@ packages: dependencies: '@typescript-eslint/types': 6.7.5 '@typescript-eslint/visitor-keys': 6.7.5 + dev: true /@typescript-eslint/type-utils@6.7.5(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==} @@ -24469,10 +24871,12 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/types@5.59.11: resolution: {integrity: sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /@typescript-eslint/types@5.62.0: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} @@ -24482,10 +24886,12 @@ packages: /@typescript-eslint/types@6.21.0: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} + dev: true /@typescript-eslint/types@6.7.5: resolution: {integrity: sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==} engines: {node: ^16.0.0 || >=18.0.0} + dev: true /@typescript-eslint/typescript-estree@5.59.11(typescript@5.4.5): resolution: {integrity: sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==} @@ -24506,6 +24912,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} @@ -24548,6 +24955,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/typescript-estree@6.7.5(typescript@5.4.5): resolution: {integrity: sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==} @@ -24568,6 +24976,7 @@ packages: typescript: 5.4.5 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/utils@5.59.11(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==} @@ -24587,6 +24996,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/utils@5.62.0(eslint@8.55.0)(typescript@5.4.5): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -24644,6 +25054,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/visitor-keys@5.59.11: resolution: {integrity: sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==} @@ -24651,6 +25062,7 @@ packages: dependencies: '@typescript-eslint/types': 5.59.11 eslint-visitor-keys: 3.4.3 + dev: true /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} @@ -24666,6 +25078,7 @@ packages: dependencies: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 + dev: true /@typescript-eslint/visitor-keys@6.7.5: resolution: {integrity: sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==} @@ -24673,6 +25086,7 @@ packages: dependencies: '@typescript-eslint/types': 6.7.5 eslint-visitor-keys: 3.4.3 + dev: true /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -24995,6 +25409,7 @@ packages: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.12.1 + dev: true /acorn-loose@8.3.0: resolution: {integrity: sha512-75lAs9H19ldmW+fAbyqHdjgdCrz0pWGXKmnqFoh8PyVd1L2RIb4RzYrSjmopeqv3E1G3/Pimu6GgLlrGbrkF7w==} @@ -25203,7 +25618,6 @@ packages: engines: {node: '>=18'} dependencies: environment: 1.1.0 - dev: true /ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} @@ -25227,7 +25641,6 @@ packages: /ansi-regex@6.1.0: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -25249,7 +25662,6 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /ansis@3.3.2: resolution: {integrity: sha512-cFthbBlt+Oi0i9Pv/j6YdVWJh54CtjGACaMPCIrEV4Ha7HWsIjXDwseYV79TIL0B4+KfSwD5S70PeQDkPUd1rA==} @@ -25273,6 +25685,7 @@ packages: /are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} + dev: true /are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} @@ -25324,6 +25737,7 @@ packages: /array-differ@3.0.0: resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} engines: {node: '>=8'} + dev: true /array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -25342,6 +25756,7 @@ packages: es-object-atoms: 1.0.0 get-intrinsic: 1.2.4 is-string: 1.0.7 + dev: true /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} @@ -25355,6 +25770,7 @@ packages: define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 + dev: true /array.prototype.flatmap@1.3.2: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} @@ -25364,6 +25780,7 @@ packages: define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 + dev: true /array.prototype.tosorted@1.1.4: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} @@ -25374,6 +25791,7 @@ packages: es-abstract: 1.23.3 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 + dev: true /arraybuffer.prototype.slice@1.0.3: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} @@ -25391,6 +25809,7 @@ packages: /arrify@2.0.1: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + dev: true /asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -25476,6 +25895,10 @@ packages: engines: {node: '>=8.0.0'} dev: true + /auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /auto-changelog@2.5.0: resolution: {integrity: sha512-UTnLjT7I9U2U/xkCUH5buDlp8C7g0SGChfib+iDrJkamcj5kaMqNKHNfbKJw1kthJUq8sUo3i3q2S6FzO/l/wA==} engines: {node: '>=8.3'} @@ -25827,11 +26250,6 @@ packages: dependencies: fill-range: 7.1.1 - /breakword@1.0.6: - resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} - dependencies: - wcwidth: 1.0.1 - /browser-level@1.0.1: resolution: {integrity: sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==} dependencies: @@ -25895,6 +26313,7 @@ packages: /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + dev: true /builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} @@ -26012,19 +26431,6 @@ packages: responselike: 3.0.0 dev: true - /cacheable-request@6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.1.1 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - dev: true - /cacheable-request@7.0.4: resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} engines: {node: '>=8'} @@ -26051,6 +26457,7 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + dev: true /camel-case@3.0.0: resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} @@ -26074,6 +26481,7 @@ packages: /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} + dev: true /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} @@ -26149,7 +26557,6 @@ packages: /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true /change-case@3.1.0: resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==} @@ -26191,6 +26598,9 @@ packages: tslib: 2.7.0 dev: true + /change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + /changesets-format-with-issue-links@0.3.0(@changesets/cli@2.27.8): resolution: {integrity: sha512-+GmelydJ+bYPVQyapFIELe/bM2+98cBIG7gb9raBxzx5MiBY0cDZnj4ii7xkrgpQLx96PWH3O9VA7Mhag0gs3w==} peerDependencies: @@ -26332,6 +26742,7 @@ packages: engines: {node: '>=4'} dependencies: escape-string-regexp: 1.0.5 + dev: true /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} @@ -26347,7 +26758,6 @@ packages: /cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} - dev: true /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} @@ -26356,6 +26766,12 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + /cli-highlight@2.1.11: resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} engines: {node: '>=8.0.0', npm: '>=5.0.0'} @@ -26396,6 +26812,13 @@ packages: chalk: 3.0.0 dev: true + /cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} @@ -26409,13 +26832,6 @@ packages: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: true - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: @@ -26449,6 +26865,7 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} requiresBuild: true + optional: true /clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} @@ -26478,6 +26895,13 @@ packages: /code-block-writer@13.0.2: resolution: {integrity: sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==} + dev: true + + /code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + convert-to-spaces: 2.0.1 /codemirror-spell-checker@1.1.2: resolution: {integrity: sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==} @@ -26606,6 +27030,7 @@ packages: /comment-parser@1.4.0: resolution: {integrity: sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw==} engines: {node: '>= 12.0.0'} + dev: true /component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} @@ -26723,6 +27148,10 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /cookie-parser@1.4.7: resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} engines: {node: '>= 0.8.0'} @@ -26823,6 +27252,7 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.4.5 + dev: true /cosmiconfig@9.0.0(typescript@5.4.5): resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} @@ -27010,24 +27440,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /csv-generate@3.4.3: - resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} - - /csv-parse@4.16.3: - resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} - - /csv-stringify@5.6.5: - resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} - - /csv@5.5.3: - resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} - engines: {node: '>= 0.1.90'} - dependencies: - csv-generate: 3.4.3 - csv-parse: 4.16.3 - csv-stringify: 5.6.5 - stream-transform: 2.1.3 - /culvert@0.1.2: resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} dev: true @@ -27159,6 +27571,53 @@ packages: - supports-color dev: true + /danger@12.3.3: + resolution: {integrity: sha512-nZKzpgXN21rr4dwa6bFhM7G2JEa79dZRJiT3RVRSyi4yk1/hgZ2f8HDGoa7tMladTmu8WjJFyE3LpBIihh+aDw==} + engines: {node: '>=18'} + hasBin: true + dependencies: + '@gitbeaker/rest': 38.12.1 + '@octokit/rest': 18.12.0 + async-retry: 1.2.3 + chalk: 2.4.2 + commander: 2.20.3 + core-js: 3.38.1 + debug: 4.3.7(supports-color@8.1.1) + fast-json-patch: 3.1.1 + get-stdin: 6.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + hyperlinker: 1.0.0 + json5: 2.2.3 + jsonpointer: 5.0.1 + jsonwebtoken: 9.0.2 + lodash.find: 4.6.0 + lodash.includes: 4.3.0 + lodash.isobject: 3.0.2 + lodash.keys: 4.2.0 + lodash.mapvalues: 4.6.0 + lodash.memoize: 4.1.2 + memfs-or-file-map-to-github-branch: 1.2.1 + micromatch: 4.0.8 + node-cleanup: 2.1.2 + node-fetch: 2.7.0 + override-require: 1.1.1 + p-limit: 2.3.0 + parse-diff: 0.7.1 + parse-git-config: 2.0.3 + parse-github-url: 1.0.3 + parse-link-header: 2.0.0 + pinpoint: 1.1.0 + prettyjson: 1.2.5 + readline-sync: 1.4.10 + regenerator-runtime: 0.13.11 + require-from-string: 2.0.2 + supports-hyperlinks: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -27211,6 +27670,7 @@ packages: engines: {node: '>=0.11'} dependencies: '@babel/runtime': 7.25.7 + dev: true /dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -27247,6 +27707,7 @@ packages: optional: true dependencies: ms: 2.1.3 + dev: true /debug@4.3.7(supports-color@8.1.1): resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} @@ -27260,10 +27721,6 @@ packages: ms: 2.1.3 supports-color: 8.1.1 - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - /decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} @@ -27287,13 +27744,6 @@ packages: engines: {node: '>=0.10'} dev: true - /decompress-response@3.3.0: - resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} - engines: {node: '>=4'} - dependencies: - mimic-response: 1.0.1 - dev: true - /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -27321,6 +27771,7 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} @@ -27337,10 +27788,7 @@ packages: requiresBuild: true dependencies: clone: 1.0.4 - - /defer-to-connect@1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - dev: true + optional: true /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} @@ -27478,6 +27926,7 @@ packages: /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + dev: true /detect-newline@4.0.1: resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} @@ -27554,12 +28003,14 @@ packages: engines: {node: '>=0.10.0'} dependencies: esutils: 2.0.3 + dev: true /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true /dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -27693,10 +28144,6 @@ packages: /double-ended-queue@2.1.0-0: resolution: {integrity: sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==} - /duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - dev: true - /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -27736,7 +28183,6 @@ packages: /emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} - dev: false /emoji-regex@7.0.3: resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} @@ -27881,7 +28327,6 @@ packages: /environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - dev: true /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -27897,6 +28342,7 @@ packages: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true /es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} @@ -27977,6 +28423,7 @@ packages: internal-slot: 1.0.7 iterator.prototype: 1.1.2 safe-array-concat: 1.1.2 + dev: true /es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} @@ -27999,6 +28446,7 @@ packages: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: hasown: 2.0.2 + dev: true /es-to-primitive@1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} @@ -28057,7 +28505,6 @@ packages: /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -28100,6 +28547,7 @@ packages: eslint: '>=7.0.0' dependencies: eslint: 8.55.0 + dev: true /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -28109,6 +28557,7 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color + dev: true /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0): resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} @@ -28138,6 +28587,7 @@ packages: - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color + dev: true /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} @@ -28167,6 +28617,7 @@ packages: eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.7.5)(eslint-plugin-i@2.29.1)(eslint@8.55.0) transitivePeerDependencies: - supports-color + dev: true /eslint-plugin-chai-expect@3.0.0(eslint@8.55.0): resolution: {integrity: sha512-NS0YBcToJl+BRKBSMCwRs/oHJIX67fG5Gvb4tGked+9Wnd1/PzKijd82B2QVKcSSOwRe+pp4RAJ2AULeck4eQw==} @@ -28201,6 +28652,7 @@ packages: escape-string-regexp: 1.0.5 eslint: 8.55.0 ignore: 5.3.2 + dev: true /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-typescript@3.6.3)(eslint@8.55.0): resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==} @@ -28222,6 +28674,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true /eslint-plugin-jest@27.4.3(eslint@8.55.0)(jest@29.7.0)(typescript@5.4.5): resolution: {integrity: sha512-7S6SmmsHsgIm06BAGCAxL+ABd9/IB3MWkz2pudj6Qqor2y1qQpWPfuFU4SG9pWj4xDjF0e+D7Llh5useuSzAZw==} @@ -28262,6 +28715,7 @@ packages: spdx-expression-parse: 3.0.1 transitivePeerDependencies: - supports-color + dev: true /eslint-plugin-promise@6.1.1(eslint@8.55.0): resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} @@ -28270,6 +28724,7 @@ packages: eslint: ^7.0.0 || ^8.0.0 dependencies: eslint: 8.55.0 + dev: true /eslint-plugin-react-hooks@4.6.2(eslint@8.55.0): resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} @@ -28278,6 +28733,7 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 8.55.0 + dev: true /eslint-plugin-react@7.33.2(eslint@8.55.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} @@ -28302,12 +28758,14 @@ packages: resolve: 2.0.0-next.5 semver: 6.3.1 string.prototype.matchall: 4.0.11 + dev: true /eslint-plugin-tsdoc@0.2.17: resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 + dev: true /eslint-plugin-unicorn@48.0.1(eslint@8.55.0): resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} @@ -28331,6 +28789,7 @@ packages: regjsparser: 0.10.0 semver: 7.6.3 strip-indent: 3.0.0 + dev: true /eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@6.7.5)(eslint@8.55.0): resolution: {integrity: sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==} @@ -28345,10 +28804,12 @@ packages: '@typescript-eslint/eslint-plugin': 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.55.0)(typescript@5.4.5) eslint: 8.55.0 eslint-rule-composer: 0.3.0 + dev: true /eslint-rule-composer@0.3.0: resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} engines: {node: '>=4.0.0'} + dev: true /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -28363,6 +28824,7 @@ packages: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true /eslint-utils@1.4.3: resolution: {integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==} @@ -28379,10 +28841,12 @@ packages: /eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /eslint@6.8.0: resolution: {integrity: sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true dependencies: '@babel/code-frame': 7.25.7 @@ -28471,6 +28935,7 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color + dev: true /esm-loader-css@1.0.6: resolution: {integrity: sha512-AAnoj627sbCmI3FKvIpkPsKovhvdgMi2Za6rs6kM5vTfJ4ZlzzVT7YzQvriPpAPJbHVAcmzv5R2/WYxrMJ/KlA==} @@ -28496,6 +28961,7 @@ packages: acorn: 8.12.1 acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.4.3 + dev: true /esprima@1.2.2: resolution: {integrity: sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==} @@ -28513,6 +28979,7 @@ packages: engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -28535,6 +29002,7 @@ packages: /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + dev: true /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} @@ -28778,6 +29246,7 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true /fast-levenshtein@3.0.0: resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} @@ -28902,6 +29371,7 @@ packages: engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.2.0 + dev: true /file-loader@3.0.1(webpack@5.95.0): resolution: {integrity: sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==} @@ -29003,6 +29473,7 @@ packages: locate-path: 7.2.0 path-exists: 5.0.0 unicorn-magic: 0.1.0 + dev: true /find-yarn-workspace-root@2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} @@ -29035,6 +29506,7 @@ packages: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 + dev: true /flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} @@ -29046,6 +29518,7 @@ packages: /flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true /fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -29082,6 +29555,10 @@ packages: signal-exit: 4.1.0 dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} @@ -29117,6 +29594,14 @@ packages: engines: {node: '>=0.4.x'} dev: false + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /formidable@1.2.6: resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' @@ -29162,6 +29647,7 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: true /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -29267,6 +29753,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + /get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + /get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} @@ -29304,13 +29794,6 @@ packages: engines: {node: '>=12'} dev: true - /get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - dependencies: - pump: 3.0.2 - dev: true - /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -29334,6 +29817,7 @@ packages: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} dependencies: resolve-pkg-maps: 1.0.0 + dev: true /get-uri@6.0.3: resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==} @@ -29358,6 +29842,7 @@ packages: /git-hooks-list@1.0.3: resolution: {integrity: sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==} + dev: true /git-hooks-list@3.1.0: resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} @@ -29405,6 +29890,7 @@ packages: engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true /glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -29451,6 +29937,7 @@ packages: minimatch: 8.0.4 minipass: 4.2.8 path-scurry: 1.11.1 + dev: true /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -29494,6 +29981,7 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true /globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} @@ -29514,6 +30002,7 @@ packages: ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 + dev: true /globby@10.0.2: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} @@ -29646,25 +30135,6 @@ packages: responselike: 3.0.0 dev: true - /got@9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.3 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.5 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - dev: true - /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -29672,11 +30142,9 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true /gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} @@ -29807,6 +30275,7 @@ packages: /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true /hosted-git-info@5.2.1: resolution: {integrity: sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==} @@ -30145,6 +30614,7 @@ packages: /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + requiresBuild: true dependencies: safer-buffer: 2.1.2 dev: true @@ -30213,6 +30683,7 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: true /import-from@3.0.0: resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} @@ -30236,6 +30707,7 @@ packages: /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + dev: true /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -30244,7 +30716,6 @@ packages: /indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - dev: true /infer-owner@1.0.4: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} @@ -30275,6 +30746,49 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /ink@5.0.1(@types/react@18.3.12)(react@18.3.1): + resolution: {integrity: sha512-ae4AW/t8jlkj/6Ou21H2av0wxTk8vrGzXv+v2v7j4in+bl1M5XRMVbfNghzhBokV++FjF8RBDJvYo+ttR9YVRg==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=18.0.0' + react: '>=18.0.0 || 18.3.1' + react-devtools-core: ^4.19.1 + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + dependencies: + '@alcalzone/ansi-tokenize': 0.1.3 + '@types/react': 18.3.12 + ansi-escapes: 7.0.0 + ansi-styles: 6.2.1 + auto-bind: 5.0.1 + chalk: 5.3.0 + cli-boxes: 3.0.0 + cli-cursor: 4.0.0 + cli-truncate: 4.0.0 + code-excerpt: 4.0.0 + indent-string: 5.0.0 + is-in-ci: 0.1.0 + lodash: 4.17.21 + patch-console: 2.0.0 + react: 18.3.1 + react-reconciler: 0.29.2(react@18.3.1) + scheduler: 0.23.2 + signal-exit: 3.0.7 + slice-ansi: 7.1.0 + stack-utils: 2.0.6 + string-width: 7.2.0 + type-fest: 4.26.1 + widest-line: 5.0.0 + wrap-ansi: 9.0.0 + ws: 8.18.0 + yoga-wasm-web: 0.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + /inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} @@ -30294,27 +30808,6 @@ packages: through: 2.3.8 dev: true - /inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.1 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - dev: true - /int64-buffer@0.1.10: resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} dev: true @@ -30410,6 +30903,7 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true /is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} @@ -30419,6 +30913,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.2 + dev: true /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -30451,11 +30946,13 @@ packages: engines: {node: '>=6'} dependencies: builtin-modules: 3.3.0 + dev: true /is-bun-module@1.2.1: resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} dependencies: semver: 7.6.3 + dev: true /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -30508,6 +31005,7 @@ packages: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} dependencies: call-bind: 1.0.7 + dev: true /is-fullwidth-code-point@2.0.0: resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} @@ -30518,6 +31016,16 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + /is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + dependencies: + get-east-asian-width: 1.3.0 + /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -30539,6 +31047,11 @@ packages: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} dev: false + /is-in-ci@0.1.0: + resolution: {integrity: sha512-d9PXLEY0v1iJ64xLiQMJ51J128EYHAaOR4yZqQi8aHGfw6KgifM3/Viw1oZZ1GCVmb3gBuyhLyHj0HgR2DhSXQ==} + engines: {node: '>=18'} + hasBin: true + /is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} @@ -30547,11 +31060,6 @@ packages: is-path-inside: 3.0.3 dev: true - /is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - dev: true - /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: true @@ -30569,6 +31077,7 @@ packages: /is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + dev: true /is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} @@ -30608,6 +31117,7 @@ packages: /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + dev: true /is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} @@ -30652,6 +31162,7 @@ packages: /is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} + dev: true /is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} @@ -30709,6 +31220,7 @@ packages: /is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} + dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -30721,6 +31233,7 @@ packages: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 + dev: true /is-windows@0.2.0: resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} @@ -30862,6 +31375,7 @@ packages: has-symbols: 1.0.3 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 + dev: true /jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -31463,6 +31977,7 @@ packages: /jsdoc-type-pratt-parser@4.0.0: resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} engines: {node: '>=12.0.0'} + dev: true /jsdom-global@3.0.2(jsdom@16.7.0): resolution: {integrity: sha512-t1KMcBkz/pT5JrvcJbpUR2u/w1kO9jXctaaGJ0vZDzwFnIvGWw9IDSRciT83kIs8Bnw4qpOl8bQK08V01YgMPg==} @@ -31558,18 +32073,17 @@ packages: /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true + dev: true /jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} hasBin: true - - /json-buffer@3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} dev: true /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} @@ -31597,6 +32111,7 @@ packages: /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true /json-stable-stringify@1.1.1: resolution: {integrity: sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==} @@ -31632,6 +32147,7 @@ packages: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + dev: true /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -31644,6 +32160,7 @@ packages: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + dev: true /jsonify@0.0.1: resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} @@ -31691,8 +32208,8 @@ packages: /jsrsasign@11.1.0: resolution: {integrity: sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==} - /jssm@5.98.2: - resolution: {integrity: sha512-O2xBFBzZjLIN+wA5IA85XIaEan7wjvM2qegwVrVQPClbH/IEhI1GLkLEEfk+TPeHeW8pGOlrd84MnmTcDyxSIQ==} + /jssm@5.104.1: + resolution: {integrity: sha512-YpW2Y5Wlln8GqULzGE40B05oiiZKeIhzZLR2eymRg8cVoGan+jMQ/cRuVa1AxP3J72olx6/4RwRsEchDw0HPbw==} engines: {node: '>=10.0.0'} dependencies: better_git_changelog: 1.6.3 @@ -31708,6 +32225,7 @@ packages: array.prototype.flat: 1.3.2 object.assign: 4.1.5 object.values: 1.2.0 + dev: true /jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -31770,16 +32288,11 @@ packages: node-addon-api: 4.3.0 prebuild-install: 7.1.2 - /keyv@3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} - dependencies: - json-buffer: 3.0.0 - dev: true - /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 + dev: true /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} @@ -31793,15 +32306,14 @@ packages: /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + dev: true /kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - /latest-version@5.1.0: - resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} - engines: {node: '>=8'} - dependencies: - package-json: 6.5.0 + /ky@1.7.2: + resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} + engines: {node: '>=18'} dev: true /latest-version@7.0.0: @@ -31811,6 +32323,13 @@ packages: package-json: 8.1.1 dev: true + /latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + dependencies: + package-json: 10.0.1 + dev: true + /launch-editor@2.9.1: resolution: {integrity: sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==} dependencies: @@ -31933,6 +32452,7 @@ packages: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true /li@1.3.0: resolution: {integrity: sha512-z34TU6GlMram52Tss5mt1m//ifRIpKH5Dqm7yUVOdHI+BQCs9qGPHFaCUTIzsWX7edN30aa2WrPwR7IO10FHaw==} @@ -31955,6 +32475,7 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true /load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} @@ -32005,6 +32526,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-locate: 6.0.0 + dev: true /lodash.capitalize@4.2.1: resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} @@ -32074,6 +32596,7 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true /lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} @@ -32160,11 +32683,6 @@ packages: tslib: 2.7.0 dev: true - /lowercase-keys@1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - dev: true - /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -32177,6 +32695,7 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -33012,6 +33531,7 @@ packages: /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + dev: true /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -33052,12 +33572,14 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true /minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} @@ -33140,6 +33662,7 @@ packages: /minipass@4.2.8: resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} engines: {node: '>=8'} + dev: true /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} @@ -33149,6 +33672,7 @@ packages: /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -33162,10 +33686,6 @@ packages: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} dev: true - /mixme@0.5.10: - resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} - engines: {node: '>= 8.0.0'} - /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -33191,6 +33711,7 @@ packages: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true + dev: true /mocha-json-output-reporter@2.1.0(mocha@10.7.3)(moment@2.30.1): resolution: {integrity: sha512-FF2BItlMo8nK9+SgN/WAD01ue7G+qI1Po0U3JCZXQiiyTJ5OV3KcT1mSoZKirjYP73JFZznaaPC6Mp052PF3Vw==} @@ -33362,6 +33883,7 @@ packages: array-union: 2.1.0 arrify: 2.0.1 minimatch: 3.1.2 + dev: true /murmurhash3js@3.0.1: resolution: {integrity: sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow==} @@ -33375,6 +33897,11 @@ packages: /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -33403,6 +33930,11 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /natural-orderby@3.0.2: + resolution: {integrity: sha512-x7ZdOwBxZCEm9MM7+eQCjkrNLrW3rkBKNHVr78zbtqnMGVNlnDi6C/eUEYgxHNrcbu0ymvjzcwIL/6H1iHri9g==} + engines: {node: '>=18'} /nconf@0.12.1: resolution: {integrity: sha512-p2cfF+B3XXacQdswUYWZ0w6Vld0832A/tuqjLBu3H1sfUcby4N2oVbGhyuCkZv+t3iY3aiFEj7gZGqax9Q2c1w==} @@ -33539,6 +34071,11 @@ packages: resolution: {integrity: sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==} dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + /node-emoji@2.1.3: resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} engines: {node: '>=18'} @@ -33627,6 +34164,7 @@ packages: resolve: 1.22.8 semver: 5.7.2 validate-npm-package-license: 3.0.4 + dev: true /normalize-package-data@5.0.0: resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} @@ -33651,11 +34189,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - /normalize-url@4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - dev: true - /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -33849,7 +34382,6 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: true /object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} @@ -33886,6 +34418,7 @@ packages: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: true /object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} @@ -33895,6 +34428,7 @@ packages: define-properties: 1.2.1 es-abstract: 1.23.3 es-object-atoms: 1.0.0 + dev: true /object.hasown@1.1.4: resolution: {integrity: sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==} @@ -33903,6 +34437,7 @@ packages: define-properties: 1.2.1 es-abstract: 1.23.3 es-object-atoms: 1.0.0 + dev: true /object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} @@ -33911,12 +34446,13 @@ packages: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 + dev: true /obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - /oclif@4.15.0: - resolution: {integrity: sha512-o0gq69z5DrKl7KD5Ib/0csB92ewCougttbXfSoyFOvAD2GbR+RVZUptqCDry5xVM0vq49syOT5EWczJsfj5r2w==} + /oclif@4.15.20(@types/node@18.19.54): + resolution: {integrity: sha512-QQC1k+GNj1grEZMwIrE2RGRnckzDx4+jMK4P0w7eWSoq0EbiG1Pr0CioWRbA5wNnUo5oQx4DyxDMq5sVpxHZgw==} engines: {node: '>=18.0.0'} hasBin: true dependencies: @@ -33925,10 +34461,10 @@ packages: '@inquirer/confirm': 3.2.0 '@inquirer/input': 2.3.0 '@inquirer/select': 2.5.0 - '@oclif/core': 4.0.25 - '@oclif/plugin-help': 6.2.13 - '@oclif/plugin-not-found': 3.2.22 - '@oclif/plugin-warn-if-update-available': 3.1.18 + '@oclif/core': 4.0.31 + '@oclif/plugin-help': 6.2.16 + '@oclif/plugin-not-found': 3.2.25(@types/node@18.19.54) + '@oclif/plugin-warn-if-update-available': 3.1.21 async-retry: 1.3.3 chalk: 4.1.2 change-case: 4.1.2 @@ -33945,6 +34481,7 @@ packages: tiny-jsonc: 1.0.1 validate-npm-package-name: 5.0.1 transitivePeerDependencies: + - '@types/node' - supports-color dev: true @@ -33993,6 +34530,27 @@ packages: is-docker: 2.2.1 is-wsl: 2.2.0 + /openai@4.68.0(zod@3.23.8): + resolution: {integrity: sha512-cVH0WMKd4cColyorwqo+Gn08lN8LQ8uKLMfWXFfvnedrLq3lCH6lRd0Rd0XJRunyfgNve/L9E7uZLAii39NBkw==} + hasBin: true + peerDependencies: + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + dependencies: + '@types/node': 18.19.54 + '@types/node-fetch': 2.6.11 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + zod: 3.23.8 + transitivePeerDependencies: + - encoding + dev: false + /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -34020,20 +34578,6 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 - - /ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 dev: true /orderedmap@2.1.1: @@ -34067,11 +34611,6 @@ packages: resolution: {integrity: sha512-eoJ9YWxFcXbrn2U8FKT6RV+/Kj7fiGAB1VvHzbYKt8xM5ZuKZgCGvnHzDxmreEjcBH28ejg5MiOH4iyY1mQnkg==} dev: true - /p-cancelable@1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} - dev: true - /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -34110,6 +34649,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.1.1 + dev: true /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} @@ -34128,6 +34668,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-limit: 4.0.0 + dev: true /p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} @@ -34193,14 +34734,14 @@ packages: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} dev: true - /package-json@6.5.0: - resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} - engines: {node: '>=8'} + /package-json@10.0.1: + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} + engines: {node: '>=18'} dependencies: - got: 9.6.0 - registry-auth-token: 4.2.2 - registry-url: 5.1.0 - semver: 6.3.1 + ky: 1.7.2 + registry-auth-token: 5.0.2 + registry-url: 6.0.1 + semver: 7.6.3 dev: true /package-json@8.1.1: @@ -34272,6 +34813,7 @@ packages: engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: true /parse-cache-control@1.0.1: resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} @@ -34323,6 +34865,7 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true /parse-link-header@2.0.0: resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==} @@ -34373,6 +34916,10 @@ packages: tslib: 2.7.0 dev: true + /patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -34396,6 +34943,7 @@ packages: /path-exists@5.0.0: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -34419,6 +34967,7 @@ packages: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 + dev: true /path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} @@ -34595,6 +35144,7 @@ packages: /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + dev: true /pm2-axon-rpc@0.7.1: resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} @@ -34830,10 +35380,6 @@ packages: /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - - /prepend-http@2.0.0: - resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} - engines: {node: '>=4'} dev: true /prettier@2.8.8: @@ -35438,6 +35984,16 @@ packages: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} dev: false + /react-reconciler@0.29.2(react@18.3.1): + resolution: {integrity: sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^18.3.1 || 18.3.1 + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + /react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -35538,6 +36094,7 @@ packages: find-up: 4.1.0 read-pkg: 5.2.0 type-fest: 0.8.1 + dev: true /read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} @@ -35556,6 +36113,7 @@ packages: normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 + dev: true /read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} @@ -35712,6 +36270,7 @@ packages: get-intrinsic: 1.2.4 globalthis: 1.0.4 which-builtin-type: 1.1.4 + dev: true /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -35723,6 +36282,7 @@ packages: /regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true + dev: true /regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} @@ -35738,13 +36298,6 @@ packages: engines: {node: '>=6.5.0'} dev: true - /registry-auth-token@4.2.2: - resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} - engines: {node: '>=6.0.0'} - dependencies: - rc: 1.2.8 - dev: true - /registry-auth-token@5.0.2: resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} engines: {node: '>=14'} @@ -35752,13 +36305,6 @@ packages: '@pnpm/npm-conf': 2.3.1 dev: true - /registry-url@5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} - dependencies: - rc: 1.2.8 - dev: true - /registry-url@6.0.1: resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} engines: {node: '>=12'} @@ -35771,6 +36317,7 @@ packages: hasBin: true dependencies: jsesc: 0.5.0 + dev: true /relateurl@0.2.7: resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} @@ -35936,9 +36483,6 @@ packages: - supports-color dev: true - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -35967,6 +36511,7 @@ packages: /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + dev: true /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} @@ -35974,6 +36519,7 @@ packages: /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} @@ -35985,6 +36531,7 @@ packages: dependencies: is-core-module: 2.15.1 path-parse: 1.0.7 + dev: true /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -36001,11 +36548,6 @@ packages: is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - - /responselike@1.0.2: - resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - dependencies: - lowercase-keys: 1.0.1 dev: true /responselike@2.0.1: @@ -36029,6 +36571,13 @@ packages: signal-exit: 3.0.7 dev: true + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -36070,6 +36619,7 @@ packages: hasBin: true dependencies: glob: 9.3.5 + dev: true /rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} @@ -36330,10 +36880,12 @@ packages: /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true + dev: true /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + dev: true /semver@7.5.0: resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} @@ -36428,6 +36980,7 @@ packages: /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -36647,23 +37200,25 @@ packages: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + /slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true - /smartwrap@2.0.2: - resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} - engines: {node: '>=6'} - hasBin: true - dependencies: - array.prototype.flat: 1.3.2 - breakword: 1.0.6 - grapheme-splitter: 1.0.4 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 15.4.1 - /snake-case@2.1.0: resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==} dependencies: @@ -36779,6 +37334,7 @@ packages: /sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} + dev: true /sort-package-json@1.57.0: resolution: {integrity: sha512-FYsjYn2dHTRb41wqnv+uEqCUvBpK3jZcTp9rbz2qDTmel7Pmdtf+i2rLaaPMRZeSVM60V3Se31GyWFpmKs4Q5Q==} @@ -36790,6 +37346,7 @@ packages: globby: 10.0.0 is-plain-obj: 2.1.0 sort-object-keys: 1.1.3 + dev: true /sort-package-json@2.10.1: resolution: {integrity: sha512-d76wfhgUuGypKqY72Unm5LFnMpACbdxXsLPcL27pOsSrmVqH3PztFp1uq+Z22suk15h7vXmTesuh2aEjdCqb5w==} @@ -36881,18 +37438,22 @@ packages: dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.20 + dev: true /spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + dev: true /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.20 + dev: true /spdx-license-ids@3.0.20: resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + dev: true /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} @@ -36970,7 +37531,6 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: true /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} @@ -37029,11 +37589,6 @@ packages: looper: 3.0.0 pull-stream: 3.7.0 - /stream-transform@2.1.3: - resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} - dependencies: - mixme: 0.5.10 - /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -37106,6 +37661,14 @@ packages: strip-ansi: 7.1.0 dev: true + /string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + /string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} @@ -37122,6 +37685,7 @@ packages: regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 side-channel: 1.0.6 + dev: true /string.prototype.padend@3.1.6: resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} @@ -37196,7 +37760,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.1.0 - dev: true /strip-bom-string@1.0.0: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} @@ -37221,6 +37784,7 @@ packages: engines: {node: '>=8'} dependencies: min-indent: 1.0.1 + dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -37579,6 +38143,7 @@ packages: /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true /then-request@6.0.2: resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} @@ -37720,11 +38285,6 @@ packages: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - /to-readable-stream@1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - dev: true - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -37823,10 +38383,12 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.4.5 + dev: true /ts-deepmerge@7.0.1: resolution: {integrity: sha512-JBFCmNenZdUCc+TRNCtXVM6N8y/nDQHAcpj5BlwXG/gnogjam1NunulB9ia68mnqYI446giMfpqeBFFkOleh+g==} engines: {node: '>=14.13.1'} + dev: true /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -37898,6 +38460,7 @@ packages: dependencies: '@ts-morph/common': 0.23.0 code-block-writer: 13.0.2 + dev: true /ts-node@10.9.2(@types/node@18.19.54)(typescript@5.4.5): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -38000,19 +38563,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.4.5 - - /tty-table@4.2.3: - resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} - engines: {node: '>=8.0.0'} - hasBin: true - dependencies: - chalk: 4.1.2 - csv: 5.5.3 - kleur: 4.1.5 - smartwrap: 2.0.2 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 17.7.2 + dev: true /tuf-js@1.1.7: resolution: {integrity: sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==} @@ -38060,6 +38611,7 @@ packages: engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} @@ -38080,10 +38632,12 @@ packages: /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} + dev: true /type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + dev: true /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} @@ -38093,6 +38647,11 @@ packages: /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + dev: true + + /type-fest@4.26.1: + resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} + engines: {node: '>=16'} /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} @@ -38101,7 +38660,7 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 - /typechat@0.1.1(typescript@5.4.5): + /typechat@0.1.1(typescript@5.4.5)(zod@3.23.8): resolution: {integrity: sha512-Sw96vmkYqbAahqam7vCp8P/MjIGsR26Odz17UHpVGniYN5ir2B37nRRkoDuRpA5djwNQB+W5TB7w2xoF6kwbHQ==} engines: {node: '>=18'} peerDependencies: @@ -38114,7 +38673,7 @@ packages: optional: true dependencies: typescript: 5.4.5 - dev: true + zod: 3.23.8 /typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} @@ -38281,6 +38840,7 @@ packages: /undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + dev: true /unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} @@ -38290,6 +38850,7 @@ packages: /unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + dev: true /unicount@1.1.0: resolution: {integrity: sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==} @@ -38418,6 +38979,7 @@ packages: /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + dev: true /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -38510,13 +39072,6 @@ packages: webpack: 5.95.0(webpack-cli@5.1.4) dev: true - /url-parse-lax@3.0.0: - resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} - engines: {node: '>=4'} - dependencies: - prepend-http: 2.0.0 - dev: true - /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: @@ -38629,6 +39184,7 @@ packages: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + dev: true /validate-npm-package-name@5.0.1: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} @@ -38832,8 +39388,15 @@ packages: /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + requiresBuild: true dependencies: defaults: 1.0.4 + optional: true + + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -39268,6 +39831,7 @@ packages: which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 + dev: true /which-collection@1.0.2: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} @@ -39277,9 +39841,7 @@ packages: is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.3 - - /which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: true /which-typed-array@1.1.15: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} @@ -39331,6 +39893,12 @@ packages: string-width: 5.1.2 dev: true + /widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + dependencies: + string-width: 7.2.0 + /wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -39361,6 +39929,7 @@ packages: /word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + dev: true /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -39393,6 +39962,14 @@ packages: strip-ansi: 7.1.0 dev: true + /wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -39503,9 +40080,6 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -39529,13 +40103,13 @@ packages: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} hasBin: true + dev: true - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 + /yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true + dev: true /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} @@ -39554,22 +40128,6 @@ packages: flat: 5.0.2 is-plain-obj: 2.1.0 - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -39613,11 +40171,15 @@ packages: /yocto-queue@1.1.1: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} + dev: true /yoctocolors-cjs@2.1.2: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} + /yoga-wasm-web@0.3.3: + resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + /z-schema@5.0.5: resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} engines: {node: '>=8.0.0'} diff --git a/server/gitrest/packages/gitrest-base/src/routes/summaries.ts b/server/gitrest/packages/gitrest-base/src/routes/summaries.ts index d9fbc1f130a5..728fb057d10d 100644 --- a/server/gitrest/packages/gitrest-base/src/routes/summaries.ts +++ b/server/gitrest/packages/gitrest-base/src/routes/summaries.ts @@ -35,7 +35,8 @@ import { logAndThrowApiError, persistLatestFullSummaryInStorage, retrieveLatestFullSummaryFromStorage, - SystemErrors, + isFilesystemError, + throwFileSystemErrorAsNetworkError, } from "../utils"; function getFullSummaryDirectory(repoManager: IRepositoryManager, documentId: string): string { @@ -79,14 +80,11 @@ async function getSummary( error, ); if (enforceStrictPersistedFullSummaryReads) { - if (isNetworkError(error) && error.code === 413) { + if (isNetworkError(error)) { throw error; } - if ( - typeof (error as any).code === "string" && - (error as any).code === SystemErrors.EFBIG.code - ) { - throw new NetworkError(413, "Full summary too large."); + if (isFilesystemError(error)) { + throwFileSystemErrorAsNetworkError(error); } } } diff --git a/server/gitrest/packages/gitrest-base/src/utils/fileSystemBase.ts b/server/gitrest/packages/gitrest-base/src/utils/fileSystemBase.ts index 97c0084b96c6..56a0882512e2 100644 --- a/server/gitrest/packages/gitrest-base/src/utils/fileSystemBase.ts +++ b/server/gitrest/packages/gitrest-base/src/utils/fileSystemBase.ts @@ -9,7 +9,7 @@ import type { Stream } from "node:stream"; import type { Abortable } from "node:events"; import sizeof from "object-sizeof"; import { type IFileSystemPromises } from "./definitions"; -import { FilesystemError, SystemErrors } from "./fileSystemHelper"; +import { filepathToString, FilesystemError, SystemErrors } from "./fileSystemHelper"; export abstract class FsPromisesBase implements IFileSystemPromises { public readonly promises: IFileSystemPromises; @@ -88,8 +88,12 @@ export abstract class FsPromisesBase implements IFileSystemPromises { this.maxFileSizeBytes > 0 && sizeof(data) > this.maxFileSizeBytes ) { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - throw new FilesystemError(SystemErrors.EFBIG, filepath.toString()); + throw new FilesystemError( + SystemErrors.EFBIG, + `Attempted write size (${sizeof(data)} bytes) to ${filepathToString( + filepath, + )} exceeds limit (${this.maxFileSizeBytes} bytes).`, + ); } return this.writeFileCore(filepath, data, options); } diff --git a/server/gitrest/packages/gitrest-base/src/utils/fileSystemHelper.ts b/server/gitrest/packages/gitrest-base/src/utils/fileSystemHelper.ts index 12ef3eec18e1..b4a77b49bfa9 100644 --- a/server/gitrest/packages/gitrest-base/src/utils/fileSystemHelper.ts +++ b/server/gitrest/packages/gitrest-base/src/utils/fileSystemHelper.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ -import fs from "fs"; +import { NetworkError } from "@fluidframework/server-services-client"; +import fs, { type PathLike } from "fs"; +import type { FileHandle } from "fs/promises"; export const packedRefsFileName = "packed-refs"; @@ -12,43 +14,54 @@ export type FsEntityType = "file" | "directory" | "symlink"; export interface ISystemError { code: string; description: string; + httpStatusCode: number; } export const SystemErrors: Record = { EEXIST: { code: "EEXIST", description: "File already exists", + httpStatusCode: 409, }, EINVAL: { code: "EINVAL", description: "Invalid argument", + httpStatusCode: 400, }, EISDIR: { code: "EISDIR", description: "Illegal operation on a directory", + httpStatusCode: 405, }, ENOENT: { code: "ENOENT", description: "No such file or directory", + httpStatusCode: 404, }, ENOTDIR: { code: "ENOTDIR", description: "Not a directory", + httpStatusCode: 406, }, ENOTEMPTY: { code: "ENOTEMPTY", description: "Directory not empty", + httpStatusCode: 409, }, EFBIG: { code: "EFBIG", description: "File too large", + httpStatusCode: 413, }, UNKNOWN: { code: "UNKNOWN", description: "Unknown error", + httpStatusCode: 500, }, }; +const KnownSystemErrorCodes = new Set(Object.keys(SystemErrors)); + export class FilesystemError extends Error { public get code() { return this.err.code; @@ -63,6 +76,63 @@ export class FilesystemError extends Error { } } +/** + * Check if an error is a recognized FilesystemError (or RedisFsError). + * + * @param err - An unknown error object + * @returns Whether the error object is a FilesystemError (or RedisFsError) + */ +export function isFilesystemError(err: unknown): err is FilesystemError { + // This also works for RedisFsError which exposes a compatible code property. + return ( + typeof err === "object" && + err !== null && + "code" in err && + typeof err.code === "string" && + KnownSystemErrorCodes.has(err.code) + ); +} + +/** + * If the error is a FilesystemError, throw it as a NetworkError with the appropriate status code. + * Otherwise, rethrow the error as-is. + * + * @param err - An unknown error object + */ +export function throwFileSystemErrorAsNetworkError(err: FilesystemError): never { + const systemError = SystemErrors[err.code] ?? SystemErrors.UNKNOWN; + const error = new NetworkError( + systemError.httpStatusCode, + // Only use SystemError.description, not the message, to protect against leaking sensitive information. + systemError.description, + systemError.httpStatusCode === 500, + undefined /* isFatal */, + undefined /* retryAfterMs */, + "Gitrest filesystem error", + ); + throw error; +} + +function isFileHandle(filepath: PathLike | FileHandle): filepath is FileHandle { + return typeof filepath !== "string" && !Buffer.isBuffer(filepath) && "fd" in filepath; +} + +/** + * Convert a PathLike or FileHandle to a string path. + * @remarks + * This is useful for logging and debugging. + * If the input is a FileHandle, the path is unknown and a generic message is returned, rather than using readLink. + * + * @param filepath - A PathLike or FileHandle + * @returns The string representation of the path + */ +export function filepathToString(filepath: PathLike | FileHandle): string { + if (isFileHandle(filepath)) { + return "Unknown file handle path"; + } + return filepath.toString(); +} + /** * Creates an `fs.Stats` object using the information retrieved through the filesystem APIs. * GitRest and isomorphic-git expect `fs.Stats` objects, but we don't use `fs` to obtain them. diff --git a/server/gitrest/packages/gitrest-base/src/utils/helpers.ts b/server/gitrest/packages/gitrest-base/src/utils/helpers.ts index 3bf8acc44bda..dc38c4e12c3b 100644 --- a/server/gitrest/packages/gitrest-base/src/utils/helpers.ts +++ b/server/gitrest/packages/gitrest-base/src/utils/helpers.ts @@ -30,6 +30,7 @@ import { BaseGitRestTelemetryProperties, GitRestLumberEventName, } from "./gitrestTelemetryDefinitions"; +import { isFilesystemError, throwFileSystemErrorAsNetworkError } from "./fileSystemHelper"; /** * Validates that the input encoding is valid @@ -139,11 +140,14 @@ export async function persistLatestFullSummaryInStorage( persistLatestFullSummaryInStorageMetric.success( "Successfully persisted latest full summary in storage", ); - } catch (error: any) { + } catch (error: unknown) { persistLatestFullSummaryInStorageMetric.error( "Failed to persist latest full summary in storage", error, ); + if (isFilesystemError(error)) { + throwFileSystemErrorAsNetworkError(error); + } throw error; } } @@ -261,6 +265,9 @@ export function logAndThrowApiError( if (isNetworkError(error)) { throw error; } + if (isFilesystemError(error)) { + throwFileSystemErrorAsNetworkError(error); + } // TODO: some APIs might expect 400 responses by default, like GetRef in GitManager. Since `handleResponse` uses // 400 by default, using something different here would override the expected behavior and cause issues. Because // of that, for now, we use 400 here. But ideally, we would revisit every RepoManager API and make sure that API diff --git a/server/gitrest/packages/gitrest-base/src/utils/index.ts b/server/gitrest/packages/gitrest-base/src/utils/index.ts index e729ce444242..6260b38babbc 100644 --- a/server/gitrest/packages/gitrest-base/src/utils/index.ts +++ b/server/gitrest/packages/gitrest-base/src/utils/index.ts @@ -19,7 +19,12 @@ export { IStorageRoutingId, } from "./definitions"; export { FsPromisesBase } from "./fileSystemBase"; -export { SystemErrors } from "./fileSystemHelper"; +export { + SystemErrors, + isFilesystemError, + throwFileSystemErrorAsNetworkError, + filepathToString, +} from "./fileSystemHelper"; export { MemFsManagerFactory, NodeFsManagerFactory, RedisFsManagerFactory } from "./filesystems"; export { BaseGitRestTelemetryProperties, diff --git a/tools/markdown-magic/CHANGELOG.md b/tools/markdown-magic/CHANGELOG.md index 14ad49ecc2cc..2acc6502870a 100644 --- a/tools/markdown-magic/CHANGELOG.md +++ b/tools/markdown-magic/CHANGELOG.md @@ -1,5 +1,9 @@ # @fluid-tools/markdown-magic +## 2.5.0 + +Dependency updates only. + ## 2.4.0 Dependency updates only. diff --git a/tools/markdown-magic/package.json b/tools/markdown-magic/package.json index 27a59e05ae6f..d3ee6f1f097a 100644 --- a/tools/markdown-magic/package.json +++ b/tools/markdown-magic/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/markdown-magic", - "version": "2.5.0", + "version": "2.10.0", "private": true, "description": "Contains shared utilities for Markdown content generation and embedding using markdown-magic.", "homepage": "https://fluidframework.com", diff --git a/tools/pipelines/build-docs.yml b/tools/pipelines/build-docs.yml index 11279b6cb1ac..9fbd128ed17c 100644 --- a/tools/pipelines/build-docs.yml +++ b/tools/pipelines/build-docs.yml @@ -331,11 +331,13 @@ stages: displayName: 'Deploy website' pool: Small dependsOn: ['build', 'guardian', 'check_branch_version'] + variables: + deployOverrideVar: ${{ parameters.deployOverride }} condition: and( - not(eq(parameters.deployOverride, 'skip')), + not(eq(variables['deployOverrideVar'], 'skip')), or( eq(dependencies.check_branch_version.outputs['check_branch_version.SetShouldDeploy.shouldDeploy'], 'true'), - eq(parameters.deployOverride, 'force') + eq(variables['deployOverrideVar'], 'force') )) jobs: - job: deploy_site diff --git a/tools/pipelines/templates/include-test-perf-benchmarks-install-package.yml b/tools/pipelines/templates/include-test-perf-benchmarks-install-package.yml index dfee6fecd9f8..c5f588a85362 100644 --- a/tools/pipelines/templates/include-test-perf-benchmarks-install-package.yml +++ b/tools/pipelines/templates/include-test-perf-benchmarks-install-package.yml @@ -41,7 +41,32 @@ steps: # available at runtime. Also, using ! as separator instead of the usual / because forward slash is something we # need to replace. TEST_PACKAGE_NAME=$(echo "${{ parameters.testPackageName }}" | sed -r 's!@!!g' | sed -r 's!/!-!g') - echo "##vso[task.setvariable variable=itpbip_sanitizedPackageName]${TEST_PACKAGE_NAME}-?.?.?-*.tgz" + + # There's some thought behind the value for the itpbip_sanitizedPackageName to make sure that two packages with + # partially overlapping names (e.g. @fluid-experimental/tree and @fluid-experimental/tree-react-api) can be + # differentiated, so the pipeline knows which of the two tgz files it is supposed to unpack, while also avoiding + # these issues we've run into in the past: + # - A pattern like `${TEST_PACKAGE_NAME}-?.?.?-*.tgz` stops working for two-digit major/minor/patch version numbers. + # It also doesn't work when the pipeline runs for a release/* branch because in that case the tgz filename looks + # like `-...tgz` instead of `-..-.tgz`, + # so the final dash in the pattern causes it to not match. + # - A pattern like `${TEST_PACKAGE_NAME}-*.*.*-*.tgz` won't differentiate packages in all cases because extra + # parts of the package name are separated with dashes (e.g.fluidexperimental-tree and fluidexperimental-tree-react-api), + # which are also used to separate the package name from the major version number. + # The logic behind the current pattern is that we want to find these components: + # - `${TEST_PACKAGE_NAME}` - Matching package name. + # - `-` A dash. + # - `[0-9]*.[0-9]*.[0-9]*` - The version. While the glob syntax technically allows alpha-numerics for `*`, we + # don't expect this will be an issue, and still allows us to match multi-digit version numbers (e.g. 2.10.0). + # This component *must not* match another bit of package name. As long as we don't put numbers in package names, + # the current pattern should keep working. + # Also, the trailing `*` is important to handle the case where "-" is appended to the version number, + # but it also handles the case where it isn't because it can match 0 characters. + # - `.tgz` - The extension. + # NOTE: this ends up used as an input to an ADO task and in an argument in a call to `ls` so the syntax must work + # in both cases. We might not be able to leverage all of ADOs globbing capabilities as described in + # https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/file-matching-patterns?view=azure-devops#pattern-syntax. + echo "##vso[task.setvariable variable=itpbip_sanitizedPackageName]${TEST_PACKAGE_NAME}-[0-9]*.[0-9]*.[0-9]*.tgz" echo "##vso[task.setvariable variable=itpbip_downloadPath]$(Pipeline.Workspace)/downloadedPackages" # Download package that has performance tests